2021-07-30 12:31:17 -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 datetime import datetime
|
|
|
|
from decimal import Decimal
|
|
|
|
from solana.publickey import PublicKey
|
|
|
|
|
|
|
|
from .accountinfo import AccountInfo
|
|
|
|
from .addressableaccount import AddressableAccount
|
|
|
|
from .context import Context
|
2021-11-08 03:39:09 -08:00
|
|
|
from .instrumentvalue import InstrumentValue
|
2021-07-30 12:31:17 -07:00
|
|
|
from .layouts import layouts
|
|
|
|
from .metadata import Metadata
|
2021-11-08 03:39:09 -08:00
|
|
|
from .token import Instrument, Token
|
2021-07-30 12:31:17 -07:00
|
|
|
from .version import Version
|
|
|
|
|
|
|
|
|
2021-11-04 08:36:14 -07:00
|
|
|
# # 🥭 PriceCache class
|
2021-07-30 12:31:17 -07:00
|
|
|
#
|
2021-11-04 08:36:14 -07:00
|
|
|
# `PriceCache` stores a cached price.
|
2021-07-30 12:31:17 -07:00
|
|
|
#
|
|
|
|
class PriceCache:
|
2021-11-09 05:23:36 -08:00
|
|
|
def __init__(self, price: Decimal, last_update: datetime) -> None:
|
2021-07-30 12:31:17 -07:00
|
|
|
self.price: Decimal = price
|
|
|
|
self.last_update: datetime = last_update
|
|
|
|
|
|
|
|
@staticmethod
|
2021-08-27 12:37:23 -07:00
|
|
|
def from_layout(layout: typing.Any) -> typing.Optional["PriceCache"]:
|
2021-07-30 12:31:17 -07:00
|
|
|
if layout.last_update.timestamp() == 0:
|
|
|
|
return None
|
|
|
|
return PriceCache(layout.price, layout.last_update)
|
|
|
|
|
2021-08-01 10:03:46 -07:00
|
|
|
def __str__(self) -> str:
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"« PriceCache [{self.last_update}] {self.price:,.20f} »"
|
2021-07-30 12:31:17 -07:00
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|
|
|
|
|
|
|
|
|
2021-11-04 08:36:14 -07:00
|
|
|
# # 🥭 RootBankCache class
|
|
|
|
#
|
|
|
|
# `RootBankCache` stores cached details of deposits and borrows.
|
|
|
|
#
|
2021-07-30 12:31:17 -07:00
|
|
|
class RootBankCache:
|
2021-11-09 05:23:36 -08:00
|
|
|
def __init__(self, deposit_index: Decimal, borrow_index: Decimal, last_update: datetime) -> None:
|
2021-07-30 12:31:17 -07:00
|
|
|
self.deposit_index: Decimal = deposit_index
|
|
|
|
self.borrow_index: Decimal = borrow_index
|
|
|
|
self.last_update: datetime = last_update
|
|
|
|
|
|
|
|
@staticmethod
|
2021-08-27 12:37:23 -07:00
|
|
|
def from_layout(layout: typing.Any) -> typing.Optional["RootBankCache"]:
|
2021-07-30 12:31:17 -07:00
|
|
|
if layout.last_update.timestamp() == 0:
|
|
|
|
return None
|
|
|
|
return RootBankCache(layout.deposit_index, layout.borrow_index, layout.last_update)
|
|
|
|
|
2021-08-01 10:03:46 -07:00
|
|
|
def __str__(self) -> str:
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"« RootBankCache [{self.last_update}] {self.deposit_index:,.20f} / {self.borrow_index:,.20f} »"
|
2021-07-30 12:31:17 -07:00
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|
|
|
|
|
|
|
|
|
2021-11-04 08:36:14 -07:00
|
|
|
# # 🥭 PerpMarketCache class
|
|
|
|
#
|
|
|
|
# `PerpMarketCache` stores cached details of long and short funding.
|
|
|
|
#
|
2021-07-30 12:31:17 -07:00
|
|
|
class PerpMarketCache:
|
2021-11-09 05:23:36 -08:00
|
|
|
def __init__(self, long_funding: Decimal, short_funding: Decimal, last_update: datetime) -> None:
|
2021-07-30 12:31:17 -07:00
|
|
|
self.long_funding: Decimal = long_funding
|
|
|
|
self.short_funding: Decimal = short_funding
|
|
|
|
self.last_update: datetime = last_update
|
|
|
|
|
|
|
|
@staticmethod
|
2021-08-27 12:37:23 -07:00
|
|
|
def from_layout(layout: typing.Any) -> typing.Optional["PerpMarketCache"]:
|
2021-07-30 12:31:17 -07:00
|
|
|
if layout.last_update.timestamp() == 0:
|
|
|
|
return None
|
|
|
|
return PerpMarketCache(layout.long_funding, layout.short_funding, layout.last_update)
|
|
|
|
|
2021-08-01 10:03:46 -07:00
|
|
|
def __str__(self) -> str:
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"« PerpMarketCache [{self.last_update}] {self.long_funding:,.20f} / {self.short_funding:,.20f} »"
|
2021-11-04 08:36:14 -07:00
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|
|
|
|
|
|
|
|
|
|
|
|
# # 🥭 MarketCache class
|
|
|
|
#
|
|
|
|
# `MarketCache` stores cached details of price, root bank, and perp market, for a particular market.
|
|
|
|
#
|
|
|
|
class MarketCache:
|
2021-11-09 05:23:36 -08:00
|
|
|
def __init__(self, price: typing.Optional[PriceCache], root_bank: typing.Optional[RootBankCache], perp_market: typing.Optional[PerpMarketCache]) -> None:
|
2021-11-04 08:36:14 -07:00
|
|
|
self.price: typing.Optional[PriceCache] = price
|
|
|
|
self.root_bank: typing.Optional[RootBankCache] = root_bank
|
|
|
|
self.perp_market: typing.Optional[PerpMarketCache] = perp_market
|
|
|
|
|
2021-11-08 03:39:09 -08:00
|
|
|
def adjusted_price(self, token: Instrument, quote_token: Token) -> InstrumentValue:
|
2021-11-04 08:36:14 -07:00
|
|
|
if token == quote_token:
|
|
|
|
# The price of 1 unit of the shared quote token is always 1.
|
2021-11-08 03:39:09 -08:00
|
|
|
return InstrumentValue(quote_token, Decimal(1))
|
2021-11-04 08:36:14 -07:00
|
|
|
|
|
|
|
if self.price is None:
|
|
|
|
raise Exception(f"Could not find price index of basket token {token.symbol}.")
|
|
|
|
|
|
|
|
price: Decimal = self.price.price
|
|
|
|
decimals_difference = token.decimals - quote_token.decimals
|
|
|
|
if decimals_difference != 0:
|
|
|
|
adjustment = 10 ** decimals_difference
|
|
|
|
price = price * adjustment
|
|
|
|
|
2021-11-08 03:39:09 -08:00
|
|
|
return InstrumentValue(quote_token, price)
|
2021-11-04 08:36:14 -07:00
|
|
|
|
|
|
|
def __str__(self) -> str:
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"""« MarketCache
|
2021-11-04 08:36:14 -07:00
|
|
|
{self.price}
|
|
|
|
{self.root_bank}
|
|
|
|
{self.perp_market}
|
|
|
|
»"""
|
2021-07-30 12:31:17 -07:00
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|
|
|
|
|
|
|
|
|
|
|
|
# # 🥭 Cache class
|
|
|
|
#
|
|
|
|
# `Cache` stores cache details of prices, root banks and perp markets.
|
|
|
|
#
|
|
|
|
class Cache(AddressableAccount):
|
|
|
|
def __init__(self, account_info: AccountInfo, version: Version, meta_data: Metadata,
|
|
|
|
price_cache: typing.Sequence[typing.Optional[PriceCache]],
|
|
|
|
root_bank_cache: typing.Sequence[typing.Optional[RootBankCache]],
|
2021-11-09 05:23:36 -08:00
|
|
|
perp_market_cache: typing.Sequence[typing.Optional[PerpMarketCache]]) -> None:
|
2021-07-30 12:31:17 -07:00
|
|
|
super().__init__(account_info)
|
|
|
|
self.version: Version = version
|
|
|
|
|
|
|
|
self.meta_data: Metadata = meta_data
|
|
|
|
self.price_cache: typing.Sequence[typing.Optional[PriceCache]] = price_cache
|
|
|
|
self.root_bank_cache: typing.Sequence[typing.Optional[RootBankCache]] = root_bank_cache
|
|
|
|
self.perp_market_cache: typing.Sequence[typing.Optional[PerpMarketCache]] = perp_market_cache
|
|
|
|
|
|
|
|
@staticmethod
|
2021-08-27 12:37:23 -07:00
|
|
|
def from_layout(layout: typing.Any, account_info: AccountInfo, version: Version) -> "Cache":
|
2021-07-30 12:31:17 -07:00
|
|
|
meta_data: Metadata = Metadata.from_layout(layout.meta_data)
|
|
|
|
price_cache: typing.Sequence[typing.Optional[PriceCache]] = list(
|
|
|
|
map(PriceCache.from_layout, layout.price_cache))
|
|
|
|
root_bank_cache: typing.Sequence[typing.Optional[RootBankCache]] = list(
|
|
|
|
map(RootBankCache.from_layout, layout.root_bank_cache))
|
|
|
|
perp_market_cache: typing.Sequence[typing.Optional[PerpMarketCache]] = list(
|
|
|
|
map(PerpMarketCache.from_layout, layout.perp_market_cache))
|
|
|
|
|
|
|
|
return Cache(account_info, version, meta_data, price_cache, root_bank_cache, perp_market_cache)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def parse(account_info: AccountInfo) -> "Cache":
|
|
|
|
data = account_info.data
|
|
|
|
if len(data) != layouts.CACHE.sizeof():
|
|
|
|
raise Exception(
|
|
|
|
f"Cache data length ({len(data)}) does not match expected size ({layouts.CACHE.sizeof()})")
|
|
|
|
|
|
|
|
layout = layouts.CACHE.parse(data)
|
|
|
|
return Cache.from_layout(layout, account_info, Version.V1)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def load(context: Context, address: PublicKey) -> "Cache":
|
|
|
|
account_info = AccountInfo.load(context, address)
|
|
|
|
if account_info is None:
|
|
|
|
raise Exception(f"Cache account not found at address '{address}'")
|
|
|
|
return Cache.parse(account_info)
|
|
|
|
|
2021-11-04 08:36:14 -07:00
|
|
|
def market_cache_for_index(self, index: int) -> MarketCache:
|
|
|
|
return MarketCache(self.price_cache[index], self.root_bank_cache[index], self.perp_market_cache[index])
|
|
|
|
|
2021-08-01 10:03:46 -07:00
|
|
|
def __str__(self) -> str:
|
2021-11-09 05:23:36 -08:00
|
|
|
def _render_list(items: typing.Sequence[typing.Any], stub: str) -> typing.Sequence[str]:
|
2021-07-30 12:31:17 -07:00
|
|
|
rendered = []
|
|
|
|
for index, item in enumerate(items):
|
|
|
|
rendered += [f"{index}: {(item or stub)}".replace("\n", "\n ")]
|
|
|
|
return rendered
|
2021-12-13 04:06:42 -08:00
|
|
|
prices = "\n ".join(_render_list(self.price_cache, "« No PriceCache »"))
|
|
|
|
root_banks = "\n ".join(_render_list(self.root_bank_cache, "« No RootBankCache »"))
|
|
|
|
perp_markets = "\n ".join(_render_list(self.perp_market_cache, "« No PerpMarketCache »"))
|
|
|
|
return f"""« Cache [{self.version}] {self.address}
|
2021-07-30 12:31:17 -07:00
|
|
|
{self.meta_data}
|
|
|
|
Prices:
|
|
|
|
{prices}
|
|
|
|
Root Banks:
|
|
|
|
{root_banks}
|
|
|
|
Perp Markets:
|
|
|
|
{perp_markets}
|
|
|
|
»"""
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|