chore: update to solanapy 0.29
This commit is contained in:
parent
74152dd8a2
commit
595946a215
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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())
|
||||
),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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()):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue