mango-explorer/mango/serumeventqueue.py

157 lines
6.8 KiB
Python

# # ⚠ 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 .accountflags import AccountFlags
from .accountinfo import AccountInfo
from .addressableaccount import AddressableAccount
from .context import Context
from .layouts import layouts
from .version import Version
# # 🥭 SerumEventQueue class
#
# `SerumEventQueue` stores details of how to reach `SerumEventQueue```.
#
class SerumEventFlags:
def __init__(self, version: Version, fill: bool, out: bool, bid: bool, maker: bool):
self.version: Version = version
self.fill: bool = fill
self.out: bool = out
self.bid: bool = bid
self.maker: bool = maker
@staticmethod
def from_layout(layout: layouts.SERUM_EVENT_FLAGS) -> "SerumEventFlags":
return SerumEventFlags(Version.UNSPECIFIED, bool(layout.fill), bool(layout.out), bool(layout.bid), bool(layout.maker))
def __str__(self) -> str:
flags: typing.List[typing.Optional[str]] = []
flags += ["fill" if self.fill else None]
flags += ["out" if self.out else None]
flags += ["bid" if self.bid else None]
flags += ["maker" if self.maker else None]
flag_text = " | ".join(flag for flag in flags if flag is not None) or "None"
return f"« 𝚂𝚎𝚛𝚞𝚖𝙴𝚟𝚎𝚗𝚝𝙵𝚕𝚊𝚐𝚜: {flag_text} »"
def __repr__(self) -> str:
return f"{self}"
class SerumEvent:
def __init__(self, version: Version, event_flags: SerumEventFlags, open_order_slot: Decimal, fee_tier: Decimal,
native_quantity_released: Decimal, native_quantity_paid: Decimal, native_fee_or_rebate: Decimal,
order_id: Decimal, public_key: PublicKey, client_order_id: Decimal):
self.version: Version = version
self.event_flags: SerumEventFlags = event_flags
self.open_order_slot: Decimal = open_order_slot
self.fee_tier: Decimal = fee_tier
self.native_quantity_released: Decimal = native_quantity_released
self.native_quantity_paid: Decimal = native_quantity_paid
self.native_fee_or_rebate: Decimal = native_fee_or_rebate
self.order_id: Decimal = order_id
self.public_key: PublicKey = public_key
self.client_order_id: Decimal = client_order_id
@staticmethod
def from_layout(layout: layouts.SERUM_EVENT) -> "SerumEvent":
event_flags: SerumEventFlags = SerumEventFlags.from_layout(layout.event_flags)
return SerumEvent(Version.UNSPECIFIED, event_flags, layout.open_order_slot, layout.fee_tier,
layout.native_quantity_released, layout.native_quantity_paid, layout.native_fee_or_rebate,
layout.order_id, layout.public_key, layout.client_order_id)
def __str__(self):
return f"""« 𝚂𝚎𝚛𝚞𝚖𝙴𝚟𝚎𝚗𝚝 {self.event_flags}
Order ID: {self.order_id}
Client Order ID: {self.client_order_id}
Public Key: {self.public_key}
OpenOrder Slot: {self.open_order_slot}
Native Quantity Released: {self.native_quantity_released}
Native Quantity Paid: {self.native_quantity_paid}
Native Fee Or Rebate: {self.native_fee_or_rebate}
Fee Tier: {self.fee_tier}
»"""
class SerumEventQueue(AddressableAccount):
def __init__(self, account_info: AccountInfo, version: Version, account_flags: AccountFlags,
head: Decimal, count: Decimal, sequence_number: Decimal,
events: typing.Sequence[typing.Optional[SerumEvent]]):
super().__init__(account_info)
self.version: Version = version
self.account_flags: AccountFlags = account_flags
self.head: Decimal = head
self.count: Decimal = count
self.sequence_number: Decimal = sequence_number
self.events: typing.Sequence[typing.Optional[SerumEvent]] = events
@staticmethod
def from_layout(layout: layouts.SERUM_EVENT_QUEUE, account_info: AccountInfo, version: Version) -> "SerumEventQueue":
account_flags: AccountFlags = AccountFlags.from_layout(layout.account_flags)
head: Decimal = layout.head
count: Decimal = layout.count
seq_num: Decimal = layout.next_seq_num
events: typing.Sequence[typing.Optional[SerumEvent]] = list(map(SerumEvent.from_layout, layout.events))
return SerumEventQueue(account_info, version, account_flags, head, count, seq_num, events)
@ staticmethod
def parse(account_info: AccountInfo) -> "SerumEventQueue":
# Data length isn't fixed so can't check we get the right value the way we normally do.
layout = layouts.SERUM_EVENT_QUEUE.parse(account_info.data)
return SerumEventQueue.from_layout(layout, account_info, Version.V1)
@ staticmethod
def load(context: Context, address: PublicKey) -> "SerumEventQueue":
account_info = AccountInfo.load(context, address)
if account_info is None:
raise Exception(f"SerumEventQueue account not found at address '{address}'")
return SerumEventQueue.parse(account_info)
@property
def capacity(self) -> int:
return len(self.events)
def unprocessed_events(self) -> typing.Sequence[SerumEvent]:
unprocessed: typing.List[SerumEvent] = []
for index in range(int(self.count)):
modulo_index = (self.head + index) % self.capacity
event: typing.Optional[SerumEvent] = self.events[int(modulo_index)]
if event is None:
raise Exception(f"Event at index {index} should not be None.")
unprocessed += [event]
return unprocessed
def __str__(self):
events = "\n ".join([f"{event}".replace("\n", "\n ")
for event in self.events if event is not None]) or "None"
return f"""« 𝚂𝚎𝚛𝚞𝚖𝙴𝚟𝚎𝚗𝚝𝚀𝚞𝚎𝚞𝚎 [{self.version}] {self.address}
{self.account_flags}
Head: {self.head}
Count: {self.count}
Sequence Number: {self.sequence_number}
Capacity: {self.capacity}
Events:
{events}
»"""