chore: update to solanapy 0.29

This commit is contained in:
Ennio Nasca 2023-02-02 15:03:00 +01:00
parent 74152dd8a2
commit 595946a215
20 changed files with 400 additions and 119 deletions

View File

@ -1,4 +1,15 @@
from construct import BitsInteger, BitsSwapped, BitStruct, Bytes, Const, Flag, Int8ul, Int32ul, Int64ul, Padding
from construct import (
BitsInteger,
BitsSwapped,
BitStruct,
Bytes,
Const,
Flag,
Int8ul,
Int32ul,
Int64ul,
Padding,
)
from construct import Struct as cStruct
from .account_flags import ACCOUNT_FLAGS_LAYOUT

View File

@ -33,7 +33,9 @@ class NodeType(IntEnum):
# Different node types, we pad it all to size of 68 bytes.
UNINTIALIZED = cStruct(Padding(68))
INNER_NODE = cStruct("prefix_len" / Int32ul, "key" / KEY, "children" / Int32ul[2], Padding(40))
INNER_NODE = cStruct(
"prefix_len" / Int32ul, "key" / KEY, "children" / Int32ul[2], Padding(40)
)
LEAF_NODE = cStruct(
"owner_slot" / Int8ul,
"fee_tier" / Int8ul,
@ -61,6 +63,14 @@ SLAB_NODE_LAYOUT = cStruct(
),
)
SLAB_LAYOUT = cStruct("header" / SLAB_HEADER_LAYOUT, "nodes" / SLAB_NODE_LAYOUT[lambda this: this.header.bump_index])
SLAB_LAYOUT = cStruct(
"header" / SLAB_HEADER_LAYOUT,
"nodes" / SLAB_NODE_LAYOUT[lambda this: this.header.bump_index],
)
ORDER_BOOK_LAYOUT = cStruct(Padding(5), "account_flags" / ACCOUNT_FLAGS_LAYOUT, "slab_layout" / SLAB_LAYOUT, Padding(7))
ORDER_BOOK_LAYOUT = cStruct(
Padding(5),
"account_flags" / ACCOUNT_FLAGS_LAYOUT,
"slab_layout" / SLAB_LAYOUT,
Padding(7),
)

View File

@ -1,9 +1,16 @@
from typing import List
import httpx
from solana.rpc.async_api import AsyncClient as async_conn # pylint: disable=unused-import # noqa:F401
from solana.rpc.async_api import (
AsyncClient as async_conn,
) # pylint: disable=unused-import # noqa:F401
from .connection import LIVE_MARKETS_URL, TOKEN_MINTS_URL, parse_live_markets, parse_token_mints
from .connection import (
LIVE_MARKETS_URL,
TOKEN_MINTS_URL,
parse_live_markets,
parse_token_mints,
)
from .market.types import MarketInfo, TokenInfo

View File

@ -12,12 +12,17 @@ TOKEN_MINTS_URL = "https://raw.githubusercontent.com/project-serum/serum-ts/mast
def parse_live_markets(data: List[Dict[str, Any]]) -> List[MarketInfo]:
return [
MarketInfo(name=m["name"], address=m["address"], program_id=m["programId"]) for m in data if not m["deprecated"]
MarketInfo(name=m["name"], address=m["address"], program_id=m["programId"])
for m in data
if not m["deprecated"]
]
def parse_token_mints(data: List[Dict[str, str]]) -> List[TokenInfo]:
return [TokenInfo(name=t["name"], address=Pubkey.from_string(t["address"])) for t in data]
return [
TokenInfo(name=t["name"], address=Pubkey.from_string(t["address"]))
for t in data
]
def get_live_markets() -> List[MarketInfo]:

View File

@ -13,7 +13,9 @@ from ._layouts.instructions import INSTRUCTIONS_LAYOUT, InstructionType
from .enums import OrderType, SelfTradeBehavior, Side
# V3
DEFAULT_DEX_PROGRAM_ID = Pubkey.from_string("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
DEFAULT_DEX_PROGRAM_ID = Pubkey.from_string(
"9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
)
class InitializeMarketParams(NamedTuple):
@ -316,7 +318,9 @@ def __parse_and_validate_instruction(
InstructionType.CLOSE_OPEN_ORDERS: 4,
InstructionType.INIT_OPEN_ORDERS: 3,
}
validate_instruction_keys(instruction, instruction_type_to_length_map[instruction_type])
validate_instruction_keys(
instruction, instruction_type_to_length_map[instruction_type]
)
data = INSTRUCTIONS_LAYOUT.parse(instruction.data)
validate_instruction_type(data, instruction_type)
return data
@ -326,7 +330,9 @@ def decode_initialize_market(
instruction: Instruction,
) -> InitializeMarketParams:
"""Decode an instialize market instruction and retrieve the instruction params."""
data = __parse_and_validate_instruction(instruction, InstructionType.INITIALIZE_MARKET)
data = __parse_and_validate_instruction(
instruction, InstructionType.INITIALIZE_MARKET
)
return InitializeMarketParams(
market=instruction.accounts[0].pubkey,
request_queue=instruction.accounts[1].pubkey,
@ -421,7 +427,9 @@ def decode_settle_funds(instruction: Instruction) -> SettleFundsParams:
def decode_cancel_order_by_client_id(
instruction: Instruction,
) -> CancelOrderByClientIDParams:
data = __parse_and_validate_instruction(instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID)
data = __parse_and_validate_instruction(
instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID
)
return CancelOrderByClientIDParams(
market=instruction.accounts[0].pubkey,
open_orders=instruction.accounts[1].pubkey,
@ -456,7 +464,9 @@ def decode_new_order_v3(instruction: Instruction) -> NewOrderV3Params:
def decode_cancel_order_v2(instruction: Instruction) -> CancelOrderV2Params:
data = __parse_and_validate_instruction(instruction, InstructionType.CANCEL_ORDER_V2)
data = __parse_and_validate_instruction(
instruction, InstructionType.CANCEL_ORDER_V2
)
return CancelOrderV2Params(
market=instruction.accounts[0].pubkey,
bids=instruction.accounts[1].pubkey,
@ -470,8 +480,12 @@ def decode_cancel_order_v2(instruction: Instruction) -> CancelOrderV2Params:
)
def decode_cancel_order_by_client_id_v2(instruction: Instruction) -> CancelOrderByClientIDV2Params:
data = __parse_and_validate_instruction(instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID_V2)
def decode_cancel_order_by_client_id_v2(
instruction: Instruction,
) -> CancelOrderByClientIDV2Params:
data = __parse_and_validate_instruction(
instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID_V2
)
return CancelOrderByClientIDV2Params(
market=instruction.accounts[0].pubkey,
bids=instruction.accounts[1].pubkey,
@ -497,7 +511,9 @@ def decode_close_open_orders(
def decode_init_open_orders(
instruction: Instruction,
) -> InitOpenOrdersParams:
market_authority = instruction.accounts[-1].pubkey if len(instruction.accounts) == 5 else None
market_authority = (
instruction.accounts[-1].pubkey if len(instruction.accounts) == 5 else None
)
return InitOpenOrdersParams(
open_orders=instruction.accounts[0].pubkey,
owner=instruction.accounts[1].pubkey,
@ -593,7 +609,8 @@ def consume_events(params: ConsumeEventsParams) -> Instruction:
accounts = [
AccountMeta(pubkey=pubkey, is_signer=False, is_writable=True)
# NOTE - last two accounts are required for backwards compatibility but are ignored
for pubkey in params.open_orders_accounts + (2 * [params.market, params.event_queue])
for pubkey in params.open_orders_accounts
+ (2 * [params.market, params.event_queue])
]
return Instruction(
accounts=accounts,
@ -646,7 +663,9 @@ def settle_funds(params: SettleFundsParams) -> Instruction:
AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False),
],
program_id=params.program_id,
data=INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.SETTLE_FUNDS, args=dict())),
data=INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.SETTLE_FUNDS, args=dict())
),
)
@ -691,7 +710,9 @@ def new_order_v3(params: NewOrderV3Params) -> Instruction:
]
if params.fee_discount_pubkey:
touched_accounts.append(
AccountMeta(pubkey=params.fee_discount_pubkey, is_signer=False, is_writable=False),
AccountMeta(
pubkey=params.fee_discount_pubkey, is_signer=False, is_writable=False
),
)
return Instruction(
accounts=touched_accounts,
@ -773,7 +794,9 @@ def close_open_orders(params: CloseOpenOrdersParams) -> Instruction:
AccountMeta(pubkey=params.market, is_signer=False, is_writable=False),
],
program_id=params.program_id,
data=INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.CLOSE_OPEN_ORDERS, args=dict())),
data=INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.CLOSE_OPEN_ORDERS, args=dict())
),
)
@ -787,10 +810,14 @@ def init_open_orders(params: InitOpenOrdersParams) -> Instruction:
]
if params.market_authority:
touched_accounts.append(
AccountMeta(pubkey=params.market_authority, is_signer=False, is_writable=False),
AccountMeta(
pubkey=params.market_authority, is_signer=False, is_writable=False
),
)
return Instruction(
accounts=touched_accounts,
program_id=params.program_id,
data=INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.INIT_OPEN_ORDERS, args=dict())),
data=INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.INIT_OPEN_ORDERS, args=dict())
),
)

View File

@ -17,19 +17,27 @@ def __from_bytes(
buffer: bytes, queue_type: QueueType, history: Optional[int]
) -> Tuple[Container, List[Union[Event, Request]]]:
header = QUEUE_HEADER_LAYOUT.parse(buffer)
layout_size = EVENT_LAYOUT.sizeof() if queue_type == QueueType.EVENT else REQUEST_LAYOUT.sizeof()
layout_size = (
EVENT_LAYOUT.sizeof()
if queue_type == QueueType.EVENT
else REQUEST_LAYOUT.sizeof()
)
alloc_len = math.floor((len(buffer) - QUEUE_HEADER_LAYOUT.sizeof()) / layout_size)
nodes: List[Union[Event, Request]] = []
if history:
for i in range(min(history, alloc_len)):
node_index = (header.head + header.count + alloc_len - 1 - i) % alloc_len
offset = QUEUE_HEADER_LAYOUT.sizeof() + node_index * layout_size
nodes.append(__parse_queue_item(buffer[offset : offset + layout_size], queue_type)) # noqa: E203
nodes.append(
__parse_queue_item(buffer[offset : offset + layout_size], queue_type)
) # noqa: E203
else:
for i in range(header.count):
node_index = (header.head + i) % alloc_len
offset = QUEUE_HEADER_LAYOUT.sizeof() + node_index * layout_size
nodes.append(__parse_queue_item(buffer[offset : offset + layout_size], queue_type)) # noqa: E203
nodes.append(
__parse_queue_item(buffer[offset : offset + layout_size], queue_type)
) # noqa: E203
return header, nodes
@ -81,12 +89,16 @@ def __parse_queue_item(buffer: bytes, queue_type: QueueType) -> Union[Event, Req
def decode_request_queue(buffer: bytes, history: Optional[int] = None) -> List[Request]:
header, nodes = __from_bytes(buffer, QueueType.REQUEST, history)
if not header.account_flags.initialized or not header.account_flags.request_queue:
raise Exception("Invalid requests queue, either not initialized or not a request queue.")
raise Exception(
"Invalid requests queue, either not initialized or not a request queue."
)
return cast(List[Request], nodes)
def decode_event_queue(buffer: bytes, history: Optional[int] = None) -> List[Event]:
header, nodes = __from_bytes(buffer, QueueType.EVENT, history)
if not header.account_flags.initialized or not header.account_flags.event_queue:
raise Exception("Invalid events queue, either not initialized or not a event queue.")
raise Exception(
"Invalid events queue, either not initialized or not a event queue."
)
return cast(List[Event], nodes)

View File

@ -35,7 +35,9 @@ class AsyncMarket(MarketCore):
market_state: MarketState,
force_use_request_queue: bool = False,
) -> None:
super().__init__(market_state=market_state, force_use_request_queue=force_use_request_queue)
super().__init__(
market_state=market_state, force_use_request_queue=force_use_request_queue
)
self._conn = conn
@classmethod
@ -56,7 +58,9 @@ class AsyncMarket(MarketCore):
market_state = await MarketState.async_load(conn, market_address, program_id)
return cls(conn, market_state, force_use_request_queue)
async def find_open_orders_accounts_for_owner(self, owner_address: Pubkey) -> List[AsyncOpenOrdersAccount]:
async def find_open_orders_accounts_for_owner(
self, owner_address: Pubkey
) -> List[AsyncOpenOrdersAccount]:
return await AsyncOpenOrdersAccount.find_for_market_and_owner(
self._conn, self.state.public_key(), owner_address, self.state.program_id()
)
@ -75,7 +79,9 @@ class AsyncMarket(MarketCore):
"""Load orders for owner."""
bids = await self.load_bids()
asks = await self.load_asks()
open_orders_accounts = await self.find_open_orders_accounts_for_owner(owner_address)
open_orders_accounts = await self.find_open_orders_accounts_for_owner(
owner_address
)
return self._parse_orders_for_owner(bids, asks, open_orders_accounts)
async def load_event_queue(self) -> List[t.Event]:
@ -107,11 +113,15 @@ class AsyncMarket(MarketCore):
) -> SendTransactionResp: # TODO: Add open_orders_address_key param and fee_discount_pubkey
transaction = Transaction()
signers: List[Keypair] = [owner]
open_order_accounts = await self.find_open_orders_accounts_for_owner(owner.pubkey())
open_order_accounts = await self.find_open_orders_accounts_for_owner(
owner.pubkey()
)
if open_order_accounts:
place_order_open_order_account = open_order_accounts[0].address
else:
mbfre_resp = await self._conn.get_minimum_balance_for_rent_exemption(OPEN_ORDERS_LAYOUT.sizeof())
mbfre_resp = await self._conn.get_minimum_balance_for_rent_exemption(
OPEN_ORDERS_LAYOUT.sizeof()
)
place_order_open_order_account = self._after_oo_mbfre_resp(
mbfre_resp=mbfre_resp,
owner=owner,
@ -148,11 +158,15 @@ class AsyncMarket(MarketCore):
)
return await self._conn.send_transaction(txs, owner, opts=opts)
async def cancel_order(self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()) -> SendTransactionResp:
async def cancel_order(
self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()
) -> SendTransactionResp:
txn = self._build_cancel_order_tx(owner=owner, order=order)
return await self._conn.send_transaction(txn, owner, opts=opts)
async def match_orders(self, fee_payer: Keypair, limit: int, opts: TxOpts = TxOpts()) -> SendTransactionResp:
async def match_orders(
self, fee_payer: Keypair, limit: int, opts: TxOpts = TxOpts()
) -> SendTransactionResp:
txn = self._build_match_orders_tx(limit)
return await self._conn.send_transaction(txn, fee_payer, opts=opts)

View File

@ -38,20 +38,25 @@ class MarketCore:
logger = logging.getLogger("pyserum.market.Market")
def __init__(self, market_state: MarketState, force_use_request_queue: bool = False) -> None:
def __init__(
self, market_state: MarketState, force_use_request_queue: bool = False
) -> None:
self.state = market_state
self.force_use_request_queue = force_use_request_queue
def _use_request_queue(self) -> bool:
return (
# DEX Version 1
self.state.program_id == Pubkey.from_string("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn")
self.state.program_id
== Pubkey.from_string("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn")
or
# DEX Version 1
self.state.program_id == Pubkey.from_string("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg")
self.state.program_id
== Pubkey.from_string("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg")
or
# DEX Version 2
self.state.program_id == Pubkey.from_string("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o")
self.state.program_id
== Pubkey.from_string("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o")
or self.force_use_request_queue
)
@ -64,7 +69,9 @@ class MarketCore:
def find_best_fee_discount_key(self, owner: Pubkey, cache_duration: int):
raise NotImplementedError("find_best_fee_discount_key not implemented")
def find_quote_token_accounts_for_owner(self, owner_address: Pubkey, include_unwrapped_sol: bool = False):
def find_quote_token_accounts_for_owner(
self, owner_address: Pubkey, include_unwrapped_sol: bool = False
):
raise NotImplementedError("find_quote_token_accounts_for_owner not implemented")
def _parse_bids_or_asks(self, bytes_data: bytes) -> OrderBook:
@ -77,7 +84,9 @@ class MarketCore:
all_orders = itertools.chain(bids.orders(), asks.orders())
open_orders_addresses = {str(o.address) for o in open_orders_accounts}
orders = [o for o in all_orders if str(o.open_order_address) in open_orders_addresses]
orders = [
o for o in all_orders if str(o.open_order_address) in open_orders_addresses
]
return orders
def load_base_token_for_owner(self):
@ -116,7 +125,8 @@ class MarketCore:
side=side,
price=price,
size=size,
fee_cost=event.native_fee_or_rebate * (1 if event.event_flags.maker else -1),
fee_cost=event.native_fee_or_rebate
* (1 if event.event_flags.maker else -1),
)
def _prepare_new_oo_account(
@ -151,7 +161,9 @@ class MarketCore:
limit_price: float,
max_quantity: float,
client_id: int,
open_order_accounts: Union[List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]],
open_order_accounts: Union[
List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]
],
place_order_open_order_account: Pubkey,
) -> None:
# unwrapped SOL cannot be used for payment
@ -159,9 +171,9 @@ class MarketCore:
raise ValueError("Invalid payer account. Cannot use unwrapped SOL.")
# TODO: add integration test for SOL wrapping.
should_wrap_sol = (side == Side.BUY and self.state.quote_mint() == WRAPPED_SOL_MINT) or (
side == Side.SELL and self.state.base_mint() == WRAPPED_SOL_MINT
)
should_wrap_sol = (
side == Side.BUY and self.state.quote_mint() == WRAPPED_SOL_MINT
) or (side == Side.SELL and self.state.base_mint() == WRAPPED_SOL_MINT)
if should_wrap_sol:
# wrapped_sol_account = Account()
@ -225,7 +237,9 @@ class MarketCore:
transaction: Transaction,
) -> Pubkey:
balance_needed = mbfre_resp.value
place_order_open_order_account = self._prepare_new_oo_account(owner, balance_needed, signers, transaction)
place_order_open_order_account = self._prepare_new_oo_account(
owner, balance_needed, signers, transaction
)
return place_order_open_order_account
@staticmethod
@ -233,7 +247,9 @@ class MarketCore:
price: float,
size: float,
side: Side,
open_orders_accounts: Union[List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]],
open_orders_accounts: Union[
List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]
],
) -> int:
lamports = 0
if side == Side.BUY:
@ -311,7 +327,11 @@ class MarketCore:
def _build_cancel_order_by_client_id_tx(
self, owner: Keypair, open_orders_account: Pubkey, client_id: int
) -> Transaction:
return Transaction().add(self.make_cancel_order_by_client_id_instruction(owner, open_orders_account, client_id))
return Transaction().add(
self.make_cancel_order_by_client_id_instruction(
owner, open_orders_account, client_id
)
)
def make_cancel_order_by_client_id_instruction(
self, owner: Keypair, open_orders_account: Pubkey, client_id: int
@ -341,9 +361,13 @@ class MarketCore:
)
def _build_cancel_order_tx(self, owner: Keypair, order: t.Order) -> Transaction:
return Transaction().add(self.make_cancel_order_instruction(owner.pubkey(), order))
return Transaction().add(
self.make_cancel_order_instruction(owner.pubkey(), order)
)
def make_cancel_order_instruction(self, owner: Pubkey, order: t.Order) -> Instruction:
def make_cancel_order_instruction(
self, owner: Pubkey, order: t.Order
) -> Instruction:
if self._use_request_queue():
return instructions.cancel_order(
instructions.CancelOrderParams(
@ -442,8 +466,12 @@ class MarketCore:
transaction.add(
self.make_settle_funds_instruction(
open_orders,
base_wallet if self.state.base_mint() != WRAPPED_SOL_MINT else wrapped_sol_account.pubkey(),
quote_wallet if self.state.quote_mint() != WRAPPED_SOL_MINT else wrapped_sol_account.pubkey(),
base_wallet
if self.state.base_mint() != WRAPPED_SOL_MINT
else wrapped_sol_account.pubkey(),
quote_wallet
if self.state.quote_mint() != WRAPPED_SOL_MINT
else wrapped_sol_account.pubkey(),
vault_signer,
)
)
@ -463,7 +491,9 @@ class MarketCore:
return transaction
def _settle_funds_should_wrap_sol(self) -> bool:
return (self.state.quote_mint() == WRAPPED_SOL_MINT) or (self.state.base_mint() == WRAPPED_SOL_MINT)
return (self.state.quote_mint() == WRAPPED_SOL_MINT) or (
self.state.base_mint() == WRAPPED_SOL_MINT
)
def make_settle_funds_instruction(
self,

View File

@ -35,7 +35,9 @@ class Market(MarketCore):
market_state: MarketState,
force_use_request_queue: bool = False,
) -> None:
super().__init__(market_state=market_state, force_use_request_queue=force_use_request_queue)
super().__init__(
market_state=market_state, force_use_request_queue=force_use_request_queue
)
self._conn = conn
@classmethod
@ -56,7 +58,9 @@ class Market(MarketCore):
market_state = MarketState.load(conn, market_address, program_id)
return cls(conn, market_state, force_use_request_queue)
def find_open_orders_accounts_for_owner(self, owner_address: Pubkey) -> List[OpenOrdersAccount]:
def find_open_orders_accounts_for_owner(
self, owner_address: Pubkey
) -> List[OpenOrdersAccount]:
return OpenOrdersAccount.find_for_market_and_owner(
self._conn, self.state.public_key(), owner_address, self.state.program_id()
)
@ -111,7 +115,9 @@ class Market(MarketCore):
if open_order_accounts:
place_order_open_order_account = open_order_accounts[0].address
else:
mbfre_resp = self._conn.get_minimum_balance_for_rent_exemption(OPEN_ORDERS_LAYOUT.sizeof())
mbfre_resp = self._conn.get_minimum_balance_for_rent_exemption(
OPEN_ORDERS_LAYOUT.sizeof()
)
place_order_open_order_account = self._after_oo_mbfre_resp(
mbfre_resp=mbfre_resp,
owner=owner,
@ -148,11 +154,15 @@ class Market(MarketCore):
)
return self._conn.send_transaction(txs, owner, opts=opts)
def cancel_order(self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()) -> SendTransactionResp:
def cancel_order(
self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()
) -> SendTransactionResp:
txn = self._build_cancel_order_tx(owner=owner, order=order)
return self._conn.send_transaction(txn, owner, opts=opts)
def match_orders(self, fee_payer: Keypair, limit: int, opts: TxOpts = TxOpts()) -> SendTransactionResp:
def match_orders(
self, fee_payer: Keypair, limit: int, opts: TxOpts = TxOpts()
) -> SendTransactionResp:
txn = self._build_match_orders_tx(limit)
return self._conn.send_transaction(txn, fee_payer, opts=opts)
@ -167,7 +177,9 @@ class Market(MarketCore):
# TODO: Handle wrapped sol accounts
should_wrap_sol = self._settle_funds_should_wrap_sol()
min_bal_for_rent_exemption = (
self._conn.get_minimum_balance_for_rent_exemption(165).value if should_wrap_sol else 0
self._conn.get_minimum_balance_for_rent_exemption(165).value
if should_wrap_sol
else 0
) # value only matters if should_wrap_sol
signers = [owner]
transaction = self._build_settle_funds_tx(

View File

@ -16,9 +16,13 @@ class OrderBook:
_is_bids: bool
_slab: Slab
def __init__(self, market_state: MarketState, account_flags: t.AccountFlags, slab: Slab) -> None:
def __init__(
self, market_state: MarketState, account_flags: t.AccountFlags, slab: Slab
) -> None:
if not account_flags.initialized or not account_flags.bids ^ account_flags.asks:
raise Exception("Invalid order book, either not initialized or neither of bids or asks")
raise Exception(
"Invalid order book, either not initialized or neither of bids or asks"
)
self._market_state = market_state
self._is_bids = account_flags.bids
self._slab = slab

View File

@ -15,7 +15,11 @@ from .types import AccountFlags
class MarketState: # pylint: disable=too-many-public-methods
def __init__(
self, parsed_market: Container, program_id: Pubkey, base_mint_decimals: int, quote_mint_decimals: int
self,
parsed_market: Container,
program_id: Pubkey,
base_mint_decimals: int,
quote_mint_decimals: int,
) -> None:
self._decoded = parsed_market
self._program_id = program_id
@ -32,35 +36,57 @@ class MarketState: # pylint: disable=too-many-public-methods
parsed_market = MARKET_LAYOUT.parse(bytes_data)
# TODO: add ownAddress check!
if not parsed_market.account_flags.initialized or not parsed_market.account_flags.market:
if (
not parsed_market.account_flags.initialized
or not parsed_market.account_flags.market
):
raise Exception("Invalid market")
return parsed_market
@classmethod
def load(cls, conn: Client, market_address: Pubkey, program_id: Pubkey) -> MarketState:
def load(
cls, conn: Client, market_address: Pubkey, program_id: Pubkey
) -> MarketState:
bytes_data = utils.load_bytes_data(market_address, conn)
parsed_market = cls._make_parsed_market(bytes_data)
base_mint_decimals = utils.get_mint_decimals(conn, Pubkey.from_bytes(parsed_market.base_mint))
quote_mint_decimals = utils.get_mint_decimals(conn, Pubkey.from_bytes(parsed_market.quote_mint))
base_mint_decimals = utils.get_mint_decimals(
conn, Pubkey.from_bytes(parsed_market.base_mint)
)
quote_mint_decimals = utils.get_mint_decimals(
conn, Pubkey.from_bytes(parsed_market.quote_mint)
)
return cls(parsed_market, program_id, base_mint_decimals, quote_mint_decimals)
@classmethod
async def async_load(cls, conn: AsyncClient, market_address: Pubkey, program_id: Pubkey) -> MarketState:
async def async_load(
cls, conn: AsyncClient, market_address: Pubkey, program_id: Pubkey
) -> MarketState:
bytes_data = await async_utils.load_bytes_data(market_address, conn)
parsed_market = cls._make_parsed_market(bytes_data)
base_mint_decimals = await async_utils.get_mint_decimals(conn, Pubkey.from_bytes(parsed_market.base_mint))
quote_mint_decimals = await async_utils.get_mint_decimals(conn, Pubkey.from_bytes(parsed_market.quote_mint))
base_mint_decimals = await async_utils.get_mint_decimals(
conn, Pubkey.from_bytes(parsed_market.base_mint)
)
quote_mint_decimals = await async_utils.get_mint_decimals(
conn, Pubkey.from_bytes(parsed_market.quote_mint)
)
return cls(parsed_market, program_id, base_mint_decimals, quote_mint_decimals)
@classmethod
def from_bytes(
cls, program_id: Pubkey, base_mint_decimals: int, quote_mint_decimals: int, buffer: bytes
cls,
program_id: Pubkey,
base_mint_decimals: int,
quote_mint_decimals: int,
buffer: bytes,
) -> MarketState:
parsed_market = MARKET_LAYOUT.parse(buffer)
# TODO: add ownAddress check!
if not parsed_market.account_flags.initialized or not parsed_market.account_flags.market:
if (
not parsed_market.account_flags.initialized
or not parsed_market.account_flags.market
):
raise Exception("Invalid market")
return cls(parsed_market, program_id, base_mint_decimals, quote_mint_decimals)
@ -144,9 +170,9 @@ class MarketState: # pylint: disable=too-many-public-methods
return self._decoded.quote_lot_size
def price_lots_to_number(self, price: int) -> float:
return float(price * self.quote_lot_size() * self.base_spl_token_multiplier()) / (
self.base_lot_size() * self.quote_spl_token_multiplier()
)
return float(
price * self.quote_lot_size() * self.base_spl_token_multiplier()
) / (self.base_lot_size() * self.quote_spl_token_multiplier())
def price_number_to_lots(self, price: float) -> int:
return int(
@ -160,10 +186,14 @@ class MarketState: # pylint: disable=too-many-public-methods
return float(size * self.base_lot_size()) / self.base_spl_token_multiplier()
def base_size_number_to_lots(self, size: float) -> int:
return int(math.floor(size * self.base_spl_token_multiplier()) / self.base_lot_size())
return int(
math.floor(size * self.base_spl_token_multiplier()) / self.base_lot_size()
)
def quote_size_lots_to_number(self, size: int) -> float:
return float(size * self.quote_lot_size()) / self.quote_spl_token_multiplier()
def quote_size_number_to_lots(self, size: float) -> int:
return int(math.floor(size * self.quote_spl_token_multiplier()) / self.quote_lot_size())
return int(
math.floor(size * self.quote_spl_token_multiplier()) / self.quote_lot_size()
)

View File

@ -57,7 +57,10 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
@classmethod
def from_bytes(cls: Type[_T], address: Pubkey, buffer: bytes) -> _T:
open_order_decoded = OPEN_ORDERS_LAYOUT.parse(buffer)
if not open_order_decoded.account_flags.open_orders or not open_order_decoded.account_flags.initialized:
if (
not open_order_decoded.account_flags.open_orders
or not open_order_decoded.account_flags.initialized
):
raise Exception("Not an open order account or not initialized.")
return cls(
@ -70,12 +73,16 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
quote_token_total=open_order_decoded.quote_token_total,
free_slot_bits=int.from_bytes(open_order_decoded.free_slot_bits, "little"),
is_bid_bits=int.from_bytes(open_order_decoded.is_bid_bits, "little"),
orders=[int.from_bytes(order, "little") for order in open_order_decoded.orders],
orders=[
int.from_bytes(order, "little") for order in open_order_decoded.orders
],
client_ids=open_order_decoded.client_ids,
)
@classmethod
def _process_get_program_accounts_resp(cls: Type[_T], resp: GetProgramAccountsResp) -> List[_T]:
def _process_get_program_accounts_resp(
cls: Type[_T], resp: GetProgramAccountsResp
) -> List[_T]:
accounts = []
for keyed_account in resp.value:
account_details = keyed_account.account
@ -89,7 +96,9 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
)
)
return [cls.from_bytes(account.public_key, account.data) for account in accounts]
return [
cls.from_bytes(account.public_key, account.data) for account in accounts
]
@staticmethod
def _build_get_program_accounts_args(
@ -104,7 +113,9 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
bytes=str(market),
),
MemcmpOpts(
offset=5 + 8 + 32, # 5 bytes of padding, 8 bytes of account flag, 32 bytes of market public key
offset=5
+ 8
+ 32, # 5 bytes of padding, 8 bytes of account flag, 32 bytes of market public key
bytes=str(owner),
),
]

View File

@ -52,7 +52,9 @@ def stubbed_payer(__bs_params) -> Keypair:
@pytest.fixture(scope="session")
def stubbed_base_mint(__bs_params) -> Keypair:
"""Bootstrapped base mint account."""
return __bootstrap_account(__bs_params["coin_mint"], __bs_params["coin_mint_secret"])
return __bootstrap_account(
__bs_params["coin_mint"], __bs_params["coin_mint_secret"]
)
@pytest.mark.integration
@ -66,14 +68,18 @@ def stubbed_quote_mint(__bs_params) -> Keypair:
@pytest.fixture(scope="session")
def stubbed_base_wallet(__bs_params) -> Keypair:
"""Bootstrapped base mint account."""
return __bootstrap_account(__bs_params["coin_wallet"], __bs_params["coin_wallet_secret"])
return __bootstrap_account(
__bs_params["coin_wallet"], __bs_params["coin_wallet_secret"]
)
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_quote_wallet(__bs_params) -> Keypair:
"""Bootstrapped quote mint account."""
return __bootstrap_account(__bs_params["pc_wallet"], __bs_params["pc_wallet_secret"])
return __bootstrap_account(
__bs_params["pc_wallet"], __bs_params["pc_wallet_secret"]
)
@pytest.mark.integration
@ -152,7 +158,9 @@ def http_client() -> Client:
"""Solana http client."""
cc = conn("http://localhost:8899") # pylint: disable=invalid-name
if not cc.is_connected():
raise Exception("Could not connect to local node. Please run `make int-tests` to run integration tests.")
raise Exception(
"Could not connect to local node. Please run `make int-tests` to run integration tests."
)
return cc
@ -166,7 +174,9 @@ def event_loop():
@pytest.mark.async_integration
@pytest.fixture(scope="session")
def async_http_client(event_loop) -> AsyncClient: # pylint: disable=redefined-outer-name
def async_http_client(
event_loop,
) -> AsyncClient: # pylint: disable=redefined-outer-name
"""Solana async http client."""
cc = async_conn("http://localhost:8899") # pylint: disable=invalid-name
if not event_loop.run_until_complete(cc.is_connected()):

View File

@ -13,10 +13,18 @@ from pyserum.market import AsyncMarket
@pytest.mark.async_integration
@pytest.fixture(scope="module")
def bootstrapped_market(
async_http_client: AsyncClient, stubbed_market_pk: Pubkey, stubbed_dex_program_pk: Pubkey, event_loop
async_http_client: AsyncClient,
stubbed_market_pk: Pubkey,
stubbed_dex_program_pk: Pubkey,
event_loop,
) -> AsyncMarket:
return event_loop.run_until_complete(
AsyncMarket.load(async_http_client, stubbed_market_pk, stubbed_dex_program_pk, force_use_request_queue=True)
AsyncMarket.load(
async_http_client,
stubbed_market_pk,
stubbed_dex_program_pk,
force_use_request_queue=True,
)
)
@ -70,7 +78,9 @@ async def test_market_load_requests(bootstrapped_market: AsyncMarket):
@pytest.mark.async_integration
@pytest.mark.asyncio
async def test_match_order(bootstrapped_market: AsyncMarket, stubbed_payer: Keypair):
await bootstrapped_market.match_orders(stubbed_payer, 2, TxOpts(skip_confirmation=False))
await bootstrapped_market.match_orders(
stubbed_payer, 2, TxOpts(skip_confirmation=False)
)
request_queue = await bootstrapped_market.load_request_queue()
# 0 request after matching.
@ -97,7 +107,9 @@ async def test_settle_fund(
stubbed_quote_wallet: Keypair,
stubbed_base_wallet: Keypair,
):
open_order_accounts = await bootstrapped_market.find_open_orders_accounts_for_owner(stubbed_payer.pubkey())
open_order_accounts = await bootstrapped_market.find_open_orders_accounts_for_owner(
stubbed_payer.pubkey()
)
with pytest.raises(ValueError):
# Should not allow base_wallet to be base_vault
@ -186,18 +198,26 @@ async def test_order_placement_cancellation_cycle(
assert sum(1 for _ in asks) == 1
for bid in bids:
await bootstrapped_market.cancel_order(stubbed_payer, bid, opts=TxOpts(skip_confirmation=False))
await bootstrapped_market.cancel_order(
stubbed_payer, bid, opts=TxOpts(skip_confirmation=False)
)
await bootstrapped_market.match_orders(stubbed_payer, 1, opts=TxOpts(skip_confirmation=False))
await bootstrapped_market.match_orders(
stubbed_payer, 1, opts=TxOpts(skip_confirmation=False)
)
# All bid order should have been cancelled.
bids = await bootstrapped_market.load_bids()
assert sum(1 for _ in bids) == 0
for ask in asks:
await bootstrapped_market.cancel_order(stubbed_payer, ask, opts=TxOpts(skip_confirmation=False))
await bootstrapped_market.cancel_order(
stubbed_payer, ask, opts=TxOpts(skip_confirmation=False)
)
await bootstrapped_market.match_orders(stubbed_payer, 1, opts=TxOpts(skip_confirmation=False))
await bootstrapped_market.match_orders(
stubbed_payer, 1, opts=TxOpts(skip_confirmation=False)
)
# All ask order should have been cancelled.
asks = await bootstrapped_market.load_asks()

View File

@ -7,7 +7,9 @@ from pyserum.market.types import MarketInfo, TokenInfo
@pytest.mark.integration
def test_get_live_markets():
"""Test get_live_markets."""
assert all(isinstance(market_info, MarketInfo) for market_info in get_live_markets())
assert all(
isinstance(market_info, MarketInfo) for market_info in get_live_markets()
)
@pytest.mark.integration

View File

@ -12,8 +12,15 @@ from pyserum.market import Market
@pytest.mark.integration
@pytest.fixture(scope="module")
def bootstrapped_market(http_client: Client, stubbed_market_pk: Pubkey, stubbed_dex_program_pk: Pubkey) -> Market:
return Market.load(http_client, stubbed_market_pk, stubbed_dex_program_pk, force_use_request_queue=True)
def bootstrapped_market(
http_client: Client, stubbed_market_pk: Pubkey, stubbed_dex_program_pk: Pubkey
) -> Market:
return Market.load(
http_client,
stubbed_market_pk,
stubbed_dex_program_pk,
force_use_request_queue=True,
)
@pytest.mark.integration
@ -86,7 +93,9 @@ def test_settle_fund(
stubbed_quote_wallet: Keypair,
stubbed_base_wallet: Keypair,
):
open_order_accounts = bootstrapped_market.find_open_orders_accounts_for_owner(stubbed_payer.pubkey())
open_order_accounts = bootstrapped_market.find_open_orders_accounts_for_owner(
stubbed_payer.pubkey()
)
with pytest.raises(ValueError):
# Should not allow base_wallet to be base_vault
@ -174,18 +183,26 @@ def test_order_placement_cancellation_cycle(
assert sum(1 for _ in asks) == 1
for bid in bids:
bootstrapped_market.cancel_order(stubbed_payer, bid, opts=TxOpts(skip_confirmation=False))
bootstrapped_market.cancel_order(
stubbed_payer, bid, opts=TxOpts(skip_confirmation=False)
)
bootstrapped_market.match_orders(stubbed_payer, 1, opts=TxOpts(skip_confirmation=False))
bootstrapped_market.match_orders(
stubbed_payer, 1, opts=TxOpts(skip_confirmation=False)
)
# All bid order should have been cancelled.
bids = bootstrapped_market.load_bids()
assert sum(1 for _ in bids) == 0
for ask in asks:
bootstrapped_market.cancel_order(stubbed_payer, ask, opts=TxOpts(skip_confirmation=False))
bootstrapped_market.cancel_order(
stubbed_payer, ask, opts=TxOpts(skip_confirmation=False)
)
bootstrapped_market.match_orders(stubbed_payer, 1, opts=TxOpts(skip_confirmation=False))
bootstrapped_market.match_orders(
stubbed_payer, 1, opts=TxOpts(skip_confirmation=False)
)
# All ask order should have been cancelled.
asks = bootstrapped_market.load_asks()

View File

@ -27,7 +27,12 @@ def test_parse_initialize_market():
expected = bytes.fromhex(
"000000000001000000000000000200000000000000030004000000000000000500000000000000"
) # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.INITIALIZE_MARKET, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.INITIALIZE_MARKET, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.INITIALIZE_MARKET, args, expected)
@ -43,7 +48,12 @@ def test_parse_new_order():
expected = bytes.fromhex(
"00010000000100000001000000000000000200000000000000020000000300000000000000"
) # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.NEW_ORDER, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.NEW_ORDER, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.NEW_ORDER, args, expected)
@ -51,7 +61,12 @@ def test_parse_match_orders():
"""Test parsing raw match orders data."""
args = {"limit": 1}
expected = bytes.fromhex("00020000000100") # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.MATCH_ORDER, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.MATCH_ORDER, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.MATCH_ORDER, args, expected)
@ -59,7 +74,12 @@ def test_parse_consume_events():
"""Test parsing raw consume events data."""
args = {"limit": 1}
expected = bytes.fromhex("00030000000100") # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.CONSUME_EVENTS, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.CONSUME_EVENTS, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.CONSUME_EVENTS, args, expected)
@ -75,14 +95,24 @@ def test_parse_cancel_order():
"000400000000000000d202964900000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000007b7b"
) # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.CANCEL_ORDER, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.CANCEL_ORDER, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.CANCEL_ORDER, args, expected)
def test_parse_settle_funds():
"""Test parsing raw settle funds data."""
expected = bytes.fromhex("0005000000") # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.SETTLE_FUNDS, args=None)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.SETTLE_FUNDS, args=None)
)
== expected
)
assert_parsed_layout(InstructionType.SETTLE_FUNDS, None, expected)
@ -91,7 +121,9 @@ def test_parse_cancel_order_by_client_id():
args = {"client_id": 123}
expected = bytes.fromhex("00060000007b00000000000000") # Raw hex from serum.js
assert (
INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.CANCEL_ORDER_BY_CLIENT_ID, args=args))
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.CANCEL_ORDER_BY_CLIENT_ID, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.CANCEL_ORDER_BY_CLIENT_ID, args, expected)

View File

@ -68,7 +68,10 @@ def test_consume_events():
params = inlib.ConsumeEventsParams(
market=Pubkey.from_string("11111111111111111111111111111112"),
event_queue=Pubkey.from_string("11111111111111111111111111111113"),
open_orders_accounts=[Pubkey.from_string("1111111111111111111111111111111{:X}".format(i+2)) for i in range(8)],
open_orders_accounts=[
Pubkey.from_string("1111111111111111111111111111111{:X}".format(i + 2))
for i in range(8)
],
limit=1,
)
instruction = inlib.consume_events(params)
@ -97,7 +100,7 @@ def test_cancel_order_by_client_id():
request_queue=Pubkey.from_string("11111111111111111111111111111113"),
owner=Pubkey.from_string("11111111111111111111111111111114"),
open_orders=Pubkey.from_string("11111111111111111111111111111115"),
client_id=1
client_id=1,
)
instruction = inlib.cancel_order_by_client_id(params)
assert inlib.decode_cancel_order_by_client_id(instruction) == params
@ -137,7 +140,7 @@ def test_init_open_orders():
open_orders=Pubkey.from_string("11111111111111111111111111111112"),
owner=Pubkey.from_string("11111111111111111111111111111113"),
market=Pubkey.from_string("11111111111111111111111111111114"),
market_authority=None
market_authority=None,
)
instruction = inlib.init_open_orders(params)
assert inlib.decode_init_open_orders(instruction) == params

View File

@ -19,10 +19,23 @@ def test_decode_open_order_account_layout():
open_order_account = OPEN_ORDERS_LAYOUT.parse(data)
assert open_order_account.account_flags.open_orders
assert open_order_account.account_flags.initialized
assert Pubkey.from_bytes(open_order_account.market) == Pubkey.from_string("4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2")
assert Pubkey.from_bytes(open_order_account.owner) == Pubkey.from_string("7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q")
assert Pubkey.from_bytes(open_order_account.market) == Pubkey.from_string(
"4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2"
)
assert Pubkey.from_bytes(open_order_account.owner) == Pubkey.from_string(
"7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q"
)
# if there is no order the byte returned here will be all 0. In this case we have three orders.
assert len([order for order in open_order_account.orders if int.from_bytes(order, "little") != 0]) == 3
assert (
len(
[
order
for order in open_order_account.orders
if int.from_bytes(order, "little") != 0
]
)
== 3
)
# the first three order are bid order
assert int.from_bytes(open_order_account.is_bid_bits, "little") == 0b111
@ -35,9 +48,15 @@ def test_decode_open_order_account():
with open(OPEN_ORDER_ACCOUNT_BIN_PATH, "r") as input_file:
base64_res = input_file.read()
data = base64.decodebytes(base64_res.encode("ascii"))
open_order_account = OpenOrdersAccount.from_bytes(Pubkey.from_string("11111111111111111111111111111112"), data)
assert open_order_account.market == Pubkey.from_string("4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2")
assert open_order_account.owner == Pubkey.from_string("7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q")
open_order_account = OpenOrdersAccount.from_bytes(
Pubkey.from_string("11111111111111111111111111111112"), data
)
assert open_order_account.market == Pubkey.from_string(
"4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2"
)
assert open_order_account.owner == Pubkey.from_string(
"7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q"
)
assert len([order for order in open_order_account.orders if order != 0]) == 3
# the first three order are bid order
assert open_order_account.is_bid_bits == 0b111

View File

@ -2,7 +2,12 @@
import base64
from pyserum._layouts.slab import ORDER_BOOK_LAYOUT, SLAB_HEADER_LAYOUT, SLAB_LAYOUT, SLAB_NODE_LAYOUT
from pyserum._layouts.slab import (
ORDER_BOOK_LAYOUT,
SLAB_HEADER_LAYOUT,
SLAB_LAYOUT,
SLAB_NODE_LAYOUT,
)
from pyserum.market._internal.slab import Slab
from .binary_file_path import ASK_ORDER_BIN_PATH