# # # โš  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 # from .layouts import layouts # from .metadata import Metadata # from .version import Version # # # ๐Ÿฅญ BankBalances class # # # # A simple way to package borrow and deposit balances together in a single object. # # # class BankBalances(typing.NamedTuple): # deposits: Decimal # borrows: Decimal # def __str__(self) -> str: # return f"ยซ ๐™ฑ๐šŠ๐š—๐š”๐™ฑ๐šŠ๐š•๐šŠ๐š—๐šŒ๐šŽ๐šœ Deposits: {self.deposits:,.8f} Borrows: {self.borrows:,.8f} ยป" # def __repr__(self) -> str: # return f"{self}" # # # ๐Ÿฅญ NodeBank class # # # # `NodeBank` stores details of deposits/borrows and vault. # # # class NodeBank(AddressableAccount): # def __init__(self, account_info: AccountInfo, version: Version, meta_data: Metadata, # vault: PublicKey, balances: BankBalances) -> None: # super().__init__(account_info) # self.version: Version = version # self.meta_data: Metadata = meta_data # self.vault: PublicKey = vault # self.balances: BankBalances = balances # @staticmethod # def from_layout(layout: typing.Any, account_info: AccountInfo, version: Version) -> "NodeBank": # meta_data: Metadata = layout.meta_data # deposits: Decimal = layout.deposits # borrows: Decimal = layout.borrows # balances: BankBalances = BankBalances(deposits=deposits, borrows=borrows) # vault: PublicKey = layout.vault # return NodeBank(account_info, version, meta_data, vault, balances) # @staticmethod # def parse(account_info: AccountInfo) -> "NodeBank": # data = account_info.data # if len(data) != layouts.NODE_BANK.sizeof(): # raise Exception( # f"NodeBank data length ({len(data)}) does not match expected size ({layouts.NODE_BANK.sizeof()})") # layout = layouts.NODE_BANK.parse(data) # return NodeBank.from_layout(layout, account_info, Version.V1) # @staticmethod # def load(context: Context, address: PublicKey) -> "NodeBank": # account_info = AccountInfo.load(context, address) # if account_info is None: # raise Exception(f"NodeBank account not found at address '{address}'") # return NodeBank.parse(account_info) # def __str__(self) -> str: # return f"""ยซ ๐™ฝ๐š˜๐š๐šŽ๐™ฑ๐šŠ๐š—๐š” [{self.version}] {self.address} # {self.meta_data} # Balances: {self.balances} # Vault: {self.vault} # ยป""" # def __repr__(self) -> str: # return f"{self}" # # # ๐Ÿฅญ RootBank class # # # # `RootBank` stores details of how to reach `NodeBank`. # # # class RootBank(AddressableAccount): # def __init__(self, account_info: AccountInfo, version: Version, meta_data: Metadata, # optimal_util: Decimal, optimal_rate: Decimal, max_rate: Decimal, # node_banks: typing.Sequence[PublicKey], deposit_index: Decimal, # borrow_index: Decimal, last_updated: datetime) -> None: # super().__init__(account_info) # self.version: Version = version # self.meta_data: Metadata = meta_data # self.optimal_util: Decimal = optimal_util # self.optimal_rate: Decimal = optimal_rate # self.max_rate: Decimal = max_rate # self.node_banks: typing.Sequence[PublicKey] = node_banks # self.deposit_index: Decimal = deposit_index # self.borrow_index: Decimal = borrow_index # self.last_updated: datetime = last_updated # def load_node_banks(self, context: Context) -> typing.Sequence[NodeBank]: # node_bank_account_infos = AccountInfo.load_multiple(context, self.node_banks) # return list(map(NodeBank.parse, node_bank_account_infos)) # def pick_node_bank(self, context: Context) -> NodeBank: # return self.load_node_banks(context)[0] # def fetch_balances(self, context: Context) -> BankBalances: # node_banks: typing.Sequence[NodeBank] = self.load_node_banks(context) # deposits_in_node_banks: Decimal = Decimal(0) # borrows_in_node_banks: Decimal = Decimal(0) # for node_bank in node_banks: # deposits_in_node_banks += node_bank.balances.deposits # borrows_in_node_banks += node_bank.balances.borrows # total_deposits: Decimal = deposits_in_node_banks * self.deposit_index # total_borrows: Decimal = borrows_in_node_banks * self.borrow_index # return BankBalances(deposits=total_deposits, borrows=total_borrows) # @staticmethod # def from_layout(layout: typing.Any, account_info: AccountInfo, version: Version) -> "RootBank": # meta_data: Metadata = Metadata.from_layout(layout.meta_data) # optimal_util: Decimal = layout.optimal_util # optimal_rate: Decimal = layout.optimal_rate # max_rate: Decimal = layout.max_rate # num_node_banks: Decimal = layout.num_node_banks # node_banks: typing.Sequence[PublicKey] = layout.node_banks[0:int(num_node_banks)] # deposit_index: Decimal = layout.deposit_index # borrow_index: Decimal = layout.borrow_index # last_updated: datetime = layout.last_updated # return RootBank(account_info, version, meta_data, optimal_util, optimal_rate, max_rate, node_banks, deposit_index, borrow_index, last_updated) # @staticmethod # def parse(account_info: AccountInfo) -> "RootBank": # data = account_info.data # if len(data) != layouts.ROOT_BANK.sizeof(): # raise Exception( # f"RootBank data length ({len(data)}) does not match expected size ({layouts.ROOT_BANK.sizeof()})") # layout = layouts.ROOT_BANK.parse(data) # return RootBank.from_layout(layout, account_info, Version.V1) # @staticmethod # def load(context: Context, address: PublicKey) -> "RootBank": # account_info = AccountInfo.load(context, address) # if account_info is None: # raise Exception(f"RootBank account not found at address '{address}'") # return RootBank.parse(account_info) # @staticmethod # def load_multiple(context: Context, addresses: typing.Sequence[PublicKey]) -> typing.Sequence["RootBank"]: # account_infos = AccountInfo.load_multiple(context, addresses) # root_banks = [] # for account_info in account_infos: # root_bank = RootBank.parse(account_info) # root_banks += [root_bank] # return root_banks # @staticmethod # def find_by_address(values: typing.Sequence["RootBank"], address: PublicKey) -> "RootBank": # found = [value for value in values if value.address == address] # if len(found) == 0: # raise Exception(f"RootBank '{address}' not found in root banks: {values}") # if len(found) > 1: # raise Exception(f"RootBank '{address}' matched multiple root banks in: {values}") # return found[0] # def __str__(self) -> str: # return f"""ยซ ๐š๐š˜๐š˜๐š๐™ฑ๐šŠ๐š—๐š” [{self.version}] {self.address} # {self.meta_data} # Optimal Util: {self.optimal_util:,.4f} # Optimal Rate: {self.optimal_rate:,.4f} # Max Rate: {self.max_rate} # Node Banks: # {self.node_banks} # Deposit Index: {self.deposit_index} # Borrow Index: {self.borrow_index} # Last Updated: {self.last_updated} # ยป""" # def __repr__(self) -> str: # return f"{self}"