mango-explorer/Layouts.ipynb

1585 lines
53 KiB
Plaintext
Raw Normal View History

2021-04-14 08:51:39 -07:00
{
"cells": [
{
"cell_type": "markdown",
"id": "alpine-terrain",
2021-04-15 02:59:04 -07:00
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
"\n",
2021-04-23 01:28:09 -07:00
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=Layouts.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"\n",
"[🥭 Mango Markets](https://mango.markets/) support is available at: [Docs](https://docs.mango.markets/) | [Discord](https://discord.gg/67jySBhxrg) | [Twitter](https://twitter.com/mangomarkets) | [Github](https://github.com/blockworks-foundation) | [Email](mailto:hello@blockworks.foundation)"
2021-04-15 02:59:04 -07:00
]
},
{
"cell_type": "markdown",
"id": "compressed-contractor",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"# 🥭 Layouts\n",
2021-04-15 02:59:04 -07:00
"\n",
"This notebook contains structure layouts to load the sometimes-opaque data blobs from Solana accounts.\n",
2021-04-14 08:51:39 -07:00
"\n",
"The idea is to have one data-encapsulating class (in [BaseModel](BaseModel.ipynb)) for each type, as well as one or more LAYOUT structures. So for example `Group` loads `GROUP` but in future will also be able to load `GROUP_V2`.\n",
2021-04-14 08:51:39 -07:00
"\n",
"The general approach is:\n",
"* Define one (or more) layouts to read in the data blob\n",
"* Use the data from the data blob to construct a more useful strongly-typed object\n",
"\n",
"So for example `GROUP` is defined below but it's a low-level dependency. In general, code should depend and work with the `Group` class, not the `GROUP` structure.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "developing-indonesia",
"metadata": {},
2021-04-14 08:51:39 -07:00
"outputs": [],
"source": [
"import construct\n",
"import datetime\n",
"import itertools\n",
"import typing\n",
2021-04-14 08:51:39 -07:00
"\n",
"from decimal import Decimal\n",
"from solana.publickey import PublicKey\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "fifteen-plymouth",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"# Adapters\n",
"\n",
"These are adapters for the construct package to simplify our struct declarations."
]
},
{
"cell_type": "markdown",
"id": "decent-sunglasses",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## DecimalAdapter class\n",
"\n",
"A simple construct `Adapter` that lets us use `Decimal`s directly in our structs."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "acute-treasure",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"class DecimalAdapter(construct.Adapter):\n",
" def __init__(self, size: int = 8):\n",
" construct.Adapter.__init__(self, construct.BytesInteger(size, swapped=True))\n",
"\n",
" def _decode(self, obj, context, path) -> Decimal:\n",
" return Decimal(obj)\n",
"\n",
" def _encode(self, obj, context, path) -> int:\n",
" # Can only encode int values.\n",
" return int(obj)\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "boxed-grant",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## FloatAdapter class\n",
2021-04-14 08:51:39 -07:00
"\n",
"Some numbers are packaged as 16-bytes to represent a `float`. The way to get the `float` is to take the 16-byte int value and divide it by 2 to the power 64. In Javascript this would be:\n",
"```\n",
"return intValue / Math.pow(2, 64);\n",
"```\n",
"From [Daffy on Discord](https://discordapp.com/channels/791995070613159966/820390560085835786/841327936383614987):\n",
"> It's a u128 underneath. Interpreted as a binary fixed point number where teh fixed point is right at the middle\n",
2021-04-14 08:51:39 -07:00
"\n",
"> Just interpret as u128 then divide by 2 ^ 64. Also make sure there are enough units of precision available\n",
"\n",
"> If you look at our mango-client-ts we just use the javascript number which is a float64 and that has caused some issues for us because of rounding issues.\n",
"\n",
"This is a simple construct `Adapter` that lets us use these float values directly in our structs. We do as Daffy says, but we can handle arbitrary sizes, not just u128. (The constructor takes a size - in bytes, like the rest of our code - and calculates the divisor so the mid-point of the whole sequence of bits is the fixed point."
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "japanese-consumption",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"class FloatAdapter(construct.Adapter):\n",
2021-04-14 08:51:39 -07:00
" def __init__(self, size: int = 16):\n",
" self.size = size\n",
2021-04-14 08:51:39 -07:00
" construct.Adapter.__init__(self, construct.BytesInteger(size, swapped=True))\n",
"\n",
2021-05-11 12:13:07 -07:00
" # Our size is in bytes but we want to work with bits here.\n",
" bit_size = self.size * 8\n",
2021-05-11 12:13:07 -07:00
"\n",
" # For our string of bits, our 'fixed point' is right in the middle.\n",
" fixed_point = bit_size / 2\n",
"\n",
" # So our divisor is 2 to the power of the fixed point\n",
" self.divisor = Decimal(2 ** fixed_point)\n",
"\n",
" def _decode(self, obj, context, path) -> Decimal:\n",
" return Decimal(obj) / self.divisor\n",
2021-04-14 08:51:39 -07:00
"\n",
" def _encode(self, obj, context, path) -> bytes:\n",
" return bytes(obj)\n"
]
},
{
"cell_type": "markdown",
"id": "chinese-soundtrack",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## PublicKeyAdapter\n",
"\n",
"A simple construct `Adapter` that lets us use `PublicKey`s directly in our structs."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "controlling-mother",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"class PublicKeyAdapter(construct.Adapter):\n",
" def __init__(self):\n",
" construct.Adapter.__init__(self, construct.Bytes(32))\n",
"\n",
" def _decode(self, obj, context, path) -> typing.Optional[PublicKey]:\n",
" if (obj is None) or (obj == bytes([0] * 32)):\n",
" return None\n",
2021-04-14 08:51:39 -07:00
" return PublicKey(obj)\n",
"\n",
" def _encode(self, obj, context, path) -> bytes:\n",
" return bytes(obj)\n"
]
},
{
"cell_type": "markdown",
"id": "endangered-scanning",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## DatetimeAdapter\n",
"\n",
"A simple construct `Adapter` that lets us load `datetime`s directly in our structs."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "deluxe-range",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"class DatetimeAdapter(construct.Adapter):\n",
" def __init__(self):\n",
" construct.Adapter.__init__(self, construct.BytesInteger(8, swapped=True))\n",
"\n",
" def _decode(self, obj, context, path) -> datetime.datetime:\n",
" return datetime.datetime.fromtimestamp(obj)\n",
"\n",
" def _encode(self, obj, context, path) -> bytes:\n",
" return bytes(obj)\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "enormous-fundamentals",
"metadata": {},
"source": [
"# Layout Structs"
]
},
2021-04-14 08:51:39 -07:00
{
"cell_type": "markdown",
"id": "hollow-peace",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## SERUM_ACCOUNT_FLAGS\n",
2021-04-14 08:51:39 -07:00
"\n",
"The SERUM_ prefix is because there's also `MANGO_ACCOUNT_FLAGS`.\n",
"\n",
"Here's the [Serum Rust structure](https://github.com/project-serum/serum-dex/blob/master/dex/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone, BitFlags, Debug, Eq, PartialEq)]\n",
"#[repr(u64)]\n",
"pub enum AccountFlag {\n",
" Initialized = 1u64 << 0,\n",
" Market = 1u64 << 1,\n",
" OpenOrders = 1u64 << 2,\n",
" RequestQueue = 1u64 << 3,\n",
" EventQueue = 1u64 << 4,\n",
" Bids = 1u64 << 5,\n",
" Asks = 1u64 << 6,\n",
" Disabled = 1u64 << 7,\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "minor-pierre",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"SERUM_ACCOUNT_FLAGS = construct.BitsSwapped(\n",
" construct.BitStruct(\n",
" \"initialized\" / construct.Flag,\n",
" \"market\" / construct.Flag,\n",
" \"open_orders\" / construct.Flag,\n",
" \"request_queue\" / construct.Flag,\n",
" \"event_queue\" / construct.Flag,\n",
" \"bids\" / construct.Flag,\n",
" \"asks\" / construct.Flag,\n",
" \"disabled\" / construct.Flag,\n",
" construct.Padding(7 * 8)\n",
" )\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "premier-raise",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## MANGO_ACCOUNT_FLAGS\n",
2021-04-14 08:51:39 -07:00
"\n",
"The MANGO_ prefix is because there's also `SERUM_ACCOUNT_FLAGS`.\n",
"\n",
"The MANGO_ACCOUNT_FLAGS should be exactly 8 bytes.\n",
"\n",
"Here's the [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone, BitFlags, Debug, Eq, PartialEq)]\n",
"#[repr(u64)]\n",
"pub enum AccountFlag {\n",
" Initialized = 1u64 << 0,\n",
" MangoGroup = 1u64 << 1,\n",
" MarginAccount = 1u64 << 2,\n",
" MangoSrmAccount = 1u64 << 3\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "special-medicare",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"MANGO_ACCOUNT_FLAGS = construct.BitsSwapped(\n",
" construct.BitStruct(\n",
" \"initialized\" / construct.Flag,\n",
" \"group\" / construct.Flag,\n",
" \"margin_account\" / construct.Flag,\n",
" \"srm_account\" / construct.Flag,\n",
" construct.Padding(4 + (7 * 8))\n",
" )\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "narrow-collection",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## INDEX\n",
2021-04-14 08:51:39 -07:00
"\n",
"Here's the [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone)]\n",
"#[repr(C)]\n",
"pub struct MangoIndex {\n",
" pub last_update: u64,\n",
" pub borrow: U64F64,\n",
" pub deposit: U64F64\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "operational-implementation",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"INDEX = construct.Struct(\n",
" \"last_update\" / DatetimeAdapter(),\n",
" \"borrow\" / FloatAdapter(),\n",
" \"deposit\" / FloatAdapter()\n",
")\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "looking-clarity",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## AGGREGATOR_CONFIG\n",
2021-04-14 08:51:39 -07:00
"\n",
"Here's the [Flux Rust structure](https://github.com/blockworks-foundation/solana-flux-aggregator/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]\n",
"pub struct AggregatorConfig {\n",
" /// description\n",
" pub description: [u8; 32],\n",
"\n",
" /// decimals for this feed\n",
" pub decimals: u8,\n",
"\n",
" /// oracle cannot start a new round until after `restart_relay` rounds\n",
" pub restart_delay: u8,\n",
"\n",
" /// max number of submissions in a round\n",
" pub max_submissions: u8,\n",
"\n",
" /// min number of submissions in a round to resolve an answer\n",
" pub min_submissions: u8,\n",
"\n",
" /// amount of tokens oracles are reward per submission\n",
" pub reward_amount: u64,\n",
"\n",
" /// SPL token account from which to withdraw rewards\n",
" pub reward_token_account: PublicKey,\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "intermediate-insured",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"AGGREGATOR_CONFIG = construct.Struct(\n",
" \"description\" / construct.PaddedString(32, \"utf8\"),\n",
" \"decimals\" / DecimalAdapter(1),\n",
" \"restart_delay\" / DecimalAdapter(1),\n",
" \"max_submissions\" / DecimalAdapter(1),\n",
" \"min_submissions\" / DecimalAdapter(1),\n",
" \"reward_amount\" / DecimalAdapter(),\n",
" \"reward_token_account\" / PublicKeyAdapter()\n",
")\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "impressed-jackson",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## ROUND\n",
2021-04-14 08:51:39 -07:00
"\n",
"Here's the [Flux Rust structure](https://github.com/blockworks-foundation/solana-flux-aggregator/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]\n",
"pub struct Round {\n",
" pub id: u64,\n",
" pub created_at: u64,\n",
" pub updated_at: u64,\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "excellent-sunset",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"ROUND = construct.Struct(\n",
" \"id\" / DecimalAdapter(),\n",
" \"created_at\" / DecimalAdapter(),\n",
" \"updated_at\" / DecimalAdapter()\n",
")\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "dress-champion",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## ANSWER\n",
2021-04-14 08:51:39 -07:00
"\n",
"Here's the [Flux Rust structure](https://github.com/blockworks-foundation/solana-flux-aggregator/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]\n",
"pub struct Answer {\n",
" pub round_id: u64,\n",
" pub median: u64,\n",
" pub created_at: u64,\n",
" pub updated_at: u64,\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "short-sewing",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"ANSWER = construct.Struct(\n",
" \"round_id\" / DecimalAdapter(),\n",
" \"median\" / DecimalAdapter(),\n",
" \"created_at\" / DatetimeAdapter(),\n",
" \"updated_at\" / DatetimeAdapter()\n",
")\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "blind-weight",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## AGGREGATOR\n",
2021-04-14 08:51:39 -07:00
"\n",
"Here's the [Flux Rust structure](https://github.com/blockworks-foundation/solana-flux-aggregator/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]\n",
"pub struct Aggregator {\n",
" pub config: AggregatorConfig,\n",
" /// is initialized\n",
" pub is_initialized: bool,\n",
" /// authority\n",
" pub owner: PublicKey,\n",
" /// current round accepting oracle submissions\n",
" pub round: Round,\n",
" pub round_submissions: PublicKey, // has_one: Submissions\n",
" /// the latest answer resolved\n",
" pub answer: Answer,\n",
" pub answer_submissions: PublicKey, // has_one: Submissions\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "verbal-subscriber",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"AGGREGATOR = construct.Struct(\n",
" \"config\" / AGGREGATOR_CONFIG,\n",
" \"initialized\" / DecimalAdapter(1),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"round\" / ROUND,\n",
" \"round_submissions\" / PublicKeyAdapter(),\n",
" \"answer\" / ANSWER,\n",
" \"answer_submissions\" / PublicKeyAdapter()\n",
")\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "black-bacteria",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## GROUP_V1\n",
"\n",
"Groups have a common quote currency, and it's always the last token in the tokens.\n",
"\n",
"That means the number of markets is number_of_tokens - 1.\n",
2021-04-14 08:51:39 -07:00
"\n",
"Here's the [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone)]\n",
"#[repr(C)]\n",
"pub struct MangoGroup {\n",
" pub account_flags: u64,\n",
" pub tokens: [Pubkey; NUM_TOKENS], // Last token is shared quote currency\n",
" pub vaults: [Pubkey; NUM_TOKENS], // where funds are stored\n",
" pub indexes: [MangoIndex; NUM_TOKENS], // to keep track of interest\n",
" pub spot_markets: [Pubkey; NUM_MARKETS], // pubkeys to MarketState of serum dex\n",
" pub oracles: [Pubkey; NUM_MARKETS], // oracles that give price of each base currency in quote currency\n",
" pub signer_nonce: u64,\n",
" pub signer_key: Pubkey,\n",
" pub dex_program_id: Pubkey, // serum dex program id\n",
"\n",
" // denominated in Mango index adjusted terms\n",
" pub total_deposits: [U64F64; NUM_TOKENS],\n",
" pub total_borrows: [U64F64; NUM_TOKENS],\n",
"\n",
" pub maint_coll_ratio: U64F64, // 1.10\n",
" pub init_coll_ratio: U64F64, // 1.20\n",
"\n",
" pub srm_vault: Pubkey, // holds users SRM for fee reduction\n",
"\n",
" /// This admin key is only for alpha release and the only power it has is to amend borrow limits\n",
" /// If users borrow too much too quickly before liquidators are able to handle the volume,\n",
" /// lender funds will be at risk. Hence these borrow limits will be raised slowly\n",
" pub admin: Pubkey,\n",
" pub borrow_limits: [u64; NUM_TOKENS],\n",
"\n",
" pub mint_decimals: [u8; NUM_TOKENS],\n",
" pub oracle_decimals: [u8; NUM_MARKETS],\n",
" pub padding: [u8; MANGO_GROUP_PADDING]\n",
"}\n",
"impl_loadable!(MangoGroup);\n",
2021-04-14 08:51:39 -07:00
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "changing-story",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"GROUP_V1_NUM_TOKENS = 3\n",
"GROUP_V1_NUM_MARKETS = GROUP_V1_NUM_TOKENS - 1\n",
"GROUP_V1_PADDING = 8 - (GROUP_V1_NUM_TOKENS + GROUP_V1_NUM_MARKETS) % 8\n",
"GROUP_V1 = construct.Struct(\n",
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"tokens\" / construct.Array(GROUP_V1_NUM_TOKENS, PublicKeyAdapter()),\n",
" \"vaults\" / construct.Array(GROUP_V1_NUM_TOKENS, PublicKeyAdapter()),\n",
" \"indexes\" / construct.Array(GROUP_V1_NUM_TOKENS, INDEX),\n",
" \"spot_markets\" / construct.Array(GROUP_V1_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"oracles\" / construct.Array(GROUP_V1_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"signer_nonce\" / DecimalAdapter(),\n",
" \"signer_key\" / PublicKeyAdapter(),\n",
" \"dex_program_id\" / PublicKeyAdapter(),\n",
" \"total_deposits\" / construct.Array(GROUP_V1_NUM_TOKENS, FloatAdapter()),\n",
" \"total_borrows\" / construct.Array(GROUP_V1_NUM_TOKENS, FloatAdapter()),\n",
" \"maint_coll_ratio\" / FloatAdapter(),\n",
" \"init_coll_ratio\" / FloatAdapter(),\n",
" \"srm_vault\" / PublicKeyAdapter(),\n",
" \"admin\" / PublicKeyAdapter(),\n",
" \"borrow_limits\" / construct.Array(GROUP_V1_NUM_TOKENS, DecimalAdapter()),\n",
" \"mint_decimals\" / construct.Array(GROUP_V1_NUM_TOKENS, DecimalAdapter(1)),\n",
" \"oracle_decimals\" / construct.Array(GROUP_V1_NUM_MARKETS, DecimalAdapter(1)),\n",
" \"padding\" / construct.Array(GROUP_V1_PADDING, construct.Padding(1))\n",
")"
]
},
{
"cell_type": "markdown",
"id": "wicked-scotland",
"metadata": {},
"source": [
"## GROUP_V2\n",
"\n",
"Groups have a common quote currency, and it's always the last token in the tokens.\n",
2021-04-14 08:51:39 -07:00
"\n",
"That means the number of markets is number_of_tokens - 1.\n",
"\n",
"There is no difference between the V1 and V2 structures except for the number of tokens. We handle things this way to be consistent with how we handle V1 and V2 `MarginAccount`s.\n",
"\n",
"Here's the [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone)]\n",
"#[repr(C)]\n",
"pub struct MangoGroup {\n",
" pub account_flags: u64,\n",
" pub tokens: [Pubkey; NUM_TOKENS], // Last token is shared quote currency\n",
" pub vaults: [Pubkey; NUM_TOKENS], // where funds are stored\n",
" pub indexes: [MangoIndex; NUM_TOKENS], // to keep track of interest\n",
" pub spot_markets: [Pubkey; NUM_MARKETS], // pubkeys to MarketState of serum dex\n",
" pub oracles: [Pubkey; NUM_MARKETS], // oracles that give price of each base currency in quote currency\n",
" pub signer_nonce: u64,\n",
" pub signer_key: Pubkey,\n",
" pub dex_program_id: Pubkey, // serum dex program id\n",
"\n",
" // denominated in Mango index adjusted terms\n",
" pub total_deposits: [U64F64; NUM_TOKENS],\n",
" pub total_borrows: [U64F64; NUM_TOKENS],\n",
"\n",
" pub maint_coll_ratio: U64F64, // 1.10\n",
" pub init_coll_ratio: U64F64, // 1.20\n",
"\n",
" pub srm_vault: Pubkey, // holds users SRM for fee reduction\n",
"\n",
" /// This admin key is only for alpha release and the only power it has is to amend borrow limits\n",
" /// If users borrow too much too quickly before liquidators are able to handle the volume,\n",
" /// lender funds will be at risk. Hence these borrow limits will be raised slowly\n",
" /// UPDATE: 4/15/2021 - this admin key is now useless, borrow limits are removed\n",
" pub admin: Pubkey,\n",
" pub borrow_limits: [u64; NUM_TOKENS],\n",
"\n",
" pub mint_decimals: [u8; NUM_TOKENS],\n",
" pub oracle_decimals: [u8; NUM_MARKETS],\n",
" pub padding: [u8; MANGO_GROUP_PADDING]\n",
"}\n",
"\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "headed-windsor",
"metadata": {},
"outputs": [],
"source": [
"GROUP_V2_NUM_TOKENS = 5\n",
"GROUP_V2_NUM_MARKETS = GROUP_V2_NUM_TOKENS - 1\n",
"GROUP_V2_PADDING = 8 - (GROUP_V2_NUM_TOKENS + GROUP_V2_NUM_MARKETS) % 8\n",
"GROUP_V2 = construct.Struct(\n",
2021-04-14 08:51:39 -07:00
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"tokens\" / construct.Array(GROUP_V2_NUM_TOKENS, PublicKeyAdapter()),\n",
" \"vaults\" / construct.Array(GROUP_V2_NUM_TOKENS, PublicKeyAdapter()),\n",
" \"indexes\" / construct.Array(GROUP_V2_NUM_TOKENS, INDEX),\n",
" \"spot_markets\" / construct.Array(GROUP_V2_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"oracles\" / construct.Array(GROUP_V2_NUM_MARKETS, PublicKeyAdapter()),\n",
2021-04-14 08:51:39 -07:00
" \"signer_nonce\" / DecimalAdapter(),\n",
" \"signer_key\" / PublicKeyAdapter(),\n",
" \"dex_program_id\" / PublicKeyAdapter(),\n",
" \"total_deposits\" / construct.Array(GROUP_V2_NUM_TOKENS, FloatAdapter()),\n",
" \"total_borrows\" / construct.Array(GROUP_V2_NUM_TOKENS, FloatAdapter()),\n",
" \"maint_coll_ratio\" / FloatAdapter(),\n",
" \"init_coll_ratio\" / FloatAdapter(),\n",
2021-04-14 08:51:39 -07:00
" \"srm_vault\" / PublicKeyAdapter(),\n",
" \"admin\" / PublicKeyAdapter(),\n",
" \"borrow_limits\" / construct.Array(GROUP_V2_NUM_TOKENS, DecimalAdapter()),\n",
" \"mint_decimals\" / construct.Array(GROUP_V2_NUM_TOKENS, DecimalAdapter(1)),\n",
" \"oracle_decimals\" / construct.Array(GROUP_V2_NUM_MARKETS, DecimalAdapter(1)),\n",
" \"padding\" / construct.Array(GROUP_V2_PADDING, construct.Padding(1))\n",
")"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "surgical-proceeding",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## TOKEN_ACCOUNT"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "least-replication",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"TOKEN_ACCOUNT = construct.Struct(\n",
" \"mint\" / PublicKeyAdapter(),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"amount\" / DecimalAdapter(),\n",
" \"padding\" / construct.Padding(93)\n",
")\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "upper-persian",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## OPEN_ORDERS\n",
2021-04-14 08:51:39 -07:00
"\n",
"Trying to use the `OPEN_ORDERS_LAYOUT` and `OpenOrdersAccount` from `pyserum` just proved too probelmatic. (`OpenOrdersAccount` doesn't expose `referrer_rebate_accrued`, for instance.)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "supposed-purpose",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"OPEN_ORDERS = construct.Struct(\n",
" construct.Padding(5),\n",
" \"account_flags\" / SERUM_ACCOUNT_FLAGS,\n",
" \"market\" / PublicKeyAdapter(),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"base_token_free\" / DecimalAdapter(),\n",
" \"base_token_total\" / DecimalAdapter(),\n",
" \"quote_token_free\" / DecimalAdapter(),\n",
" \"quote_token_total\" / DecimalAdapter(),\n",
" \"free_slot_bits\" / DecimalAdapter(16),\n",
" \"is_bid_bits\" / DecimalAdapter(16),\n",
" \"orders\" / construct.Array(128, DecimalAdapter(16)),\n",
" \"client_ids\" / construct.Array(128, DecimalAdapter()),\n",
" \"referrer_rebate_accrued\" / DecimalAdapter(),\n",
" \"padding\" / construct.Padding(7)\n",
")\n"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "fatty-dublin",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"## MARGIN_ACCOUNT_V1\n",
2021-04-14 08:51:39 -07:00
"\n",
"Here's the V1 [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
2021-04-14 08:51:39 -07:00
"```\n",
"#[derive(Copy, Clone)]\n",
"#[repr(C)]\n",
"pub struct MarginAccount {\n",
" pub account_flags: u64,\n",
" pub mango_group: Pubkey,\n",
" pub owner: Pubkey, // solana pubkey of owner\n",
"\n",
" // assets and borrows are denominated in Mango adjusted terms\n",
" pub deposits: [U64F64; NUM_TOKENS], // assets being lent out and gaining interest, including collateral\n",
"\n",
" // this will be incremented every time an order is opened and decremented when order is closed\n",
" pub borrows: [U64F64; NUM_TOKENS], // multiply by current index to get actual value\n",
"\n",
" pub open_orders: [Pubkey; NUM_MARKETS], // owned by Mango\n",
"\n",
" pub being_liquidated: bool,\n",
" pub padding: [u8; 7] // padding to make compatible with previous MarginAccount size\n",
" // TODO add has_borrows field for easy memcmp fetching\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "centered-metadata",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"MARGIN_ACCOUNT_V1_NUM_TOKENS = 3\n",
"MARGIN_ACCOUNT_V1_NUM_MARKETS = MARGIN_ACCOUNT_V1_NUM_TOKENS - 1\n",
"MARGIN_ACCOUNT_V1 = construct.Struct(\n",
2021-04-14 08:51:39 -07:00
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"mango_group\" / PublicKeyAdapter(),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"deposits\" / construct.Array(MARGIN_ACCOUNT_V1_NUM_TOKENS, FloatAdapter()),\n",
" \"borrows\" / construct.Array(MARGIN_ACCOUNT_V1_NUM_TOKENS, FloatAdapter()),\n",
" \"open_orders\" / construct.Array(MARGIN_ACCOUNT_V1_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"being_liquidated\" / DecimalAdapter(1),\n",
" \"padding\" / construct.Padding(7)\n",
")"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "markdown",
"id": "enabling-london",
"metadata": {},
"source": [
"## MARGIN_ACCOUNT_V2\n",
"\n",
"Here's the V2 [Mango Rust structure](https://github.com/blockworks-foundation/mango/blob/master/program/src/state.rs):\n",
"```\n",
"#[derive(Copy, Clone)]\n",
"#[repr(C)]\n",
"pub struct MarginAccount {\n",
" pub account_flags: u64,\n",
" pub mango_group: Pubkey,\n",
" pub owner: Pubkey, // solana pubkey of owner\n",
"\n",
" // assets and borrows are denominated in Mango adjusted terms\n",
" pub deposits: [U64F64; NUM_TOKENS], // assets being lent out and gaining interest, including collateral\n",
"\n",
" // this will be incremented every time an order is opened and decremented when order is closed\n",
" pub borrows: [U64F64; NUM_TOKENS], // multiply by current index to get actual value\n",
"\n",
" pub open_orders: [Pubkey; NUM_MARKETS], // owned by Mango\n",
"\n",
" pub being_liquidated: bool,\n",
" pub has_borrows: bool, // does the account have any open borrows? set by checked_add_borrow and checked_sub_borrow\n",
" pub padding: [u8; 64] // padding\n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "palestinian-startup",
"metadata": {},
"outputs": [],
"source": [
"MARGIN_ACCOUNT_V2_NUM_TOKENS = 5\n",
"MARGIN_ACCOUNT_V2_NUM_MARKETS = MARGIN_ACCOUNT_V2_NUM_TOKENS - 1\n",
"MARGIN_ACCOUNT_V2 = construct.Struct(\n",
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"mango_group\" / PublicKeyAdapter(),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"deposits\" / construct.Array(MARGIN_ACCOUNT_V2_NUM_TOKENS, FloatAdapter()),\n",
" \"borrows\" / construct.Array(MARGIN_ACCOUNT_V2_NUM_TOKENS, FloatAdapter()),\n",
" \"open_orders\" / construct.Array(MARGIN_ACCOUNT_V2_NUM_MARKETS, PublicKeyAdapter()),\n",
" \"being_liquidated\" / DecimalAdapter(1),\n",
" \"has_borrows\" / DecimalAdapter(1),\n",
" \"padding\" / construct.Padding(70)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "scenic-honey",
"metadata": {},
"source": [
"## build_margin_account_parser_for_num_tokens() function\n",
"\n",
"This function builds a `construct.Struct` that can load a `MarginAccount` with a specific number of tokens. The number of markets and size of padding are derived from the number of tokens."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dominican-wales",
"metadata": {},
"outputs": [],
"source": [
"def build_margin_account_parser_for_num_tokens(num_tokens: int) -> construct.Struct:\n",
" num_markets = num_tokens - 1\n",
"\n",
" return construct.Struct(\n",
" \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n",
" \"mango_group\" / PublicKeyAdapter(),\n",
" \"owner\" / PublicKeyAdapter(),\n",
" \"deposits\" / construct.Array(num_tokens, FloatAdapter()),\n",
" \"borrows\" / construct.Array(num_tokens, FloatAdapter()),\n",
" \"open_orders\" / construct.Array(num_markets, PublicKeyAdapter()),\n",
" \"padding\" / construct.Padding(8)\n",
" )\n"
]
},
{
"cell_type": "markdown",
"id": "certified-sympathy",
"metadata": {},
"source": [
"## build_margin_account_parser_for_length() function\n",
"\n",
"This function takes a data length (presumably the size of the structure returned from the `AccountInfo`) and returns a `MarginAccount` structure that can parse it.\n",
"\n",
"If the size doesn't _exactly_ match the size of the `Struct`, and Exception is raised."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "received-humanity",
"metadata": {},
"outputs": [],
"source": [
"def build_margin_account_parser_for_length(length: int) -> construct.Struct:\n",
" tried_sizes: typing.List[int] = []\n",
" for num_tokens in itertools.count(start=2):\n",
" parser = build_margin_account_parser_for_num_tokens(num_tokens)\n",
" if parser.sizeof() == length:\n",
" return parser\n",
"\n",
" tried_sizes += [parser.sizeof()]\n",
" if parser.sizeof() > length:\n",
" raise Exception(f\"Could not create MarginAccount parser for length ({length}) - tried sizes ({tried_sizes})\")\n"
]
},
{
"cell_type": "markdown",
"id": "appointed-tennis",
"metadata": {},
"source": [
"# Instruction Structs"
]
},
{
"cell_type": "markdown",
"id": "hollywood-import",
"metadata": {},
"source": [
"## MANGO_INSTRUCTION_VARIANT_FINDER\n",
"\n",
"The 'variant' of the instruction is held in the first 4 bytes. The remainder of the data is per-instruction.\n",
"\n",
"This `struct` loads only the first 4 bytes, as an `int`, so we know which specific parser has to be used to load the rest of the data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "apparent-offer",
"metadata": {},
"outputs": [],
"source": [
"MANGO_INSTRUCTION_VARIANT_FINDER = construct.Struct(\n",
" \"variant\" / construct.BytesInteger(4, swapped=True)\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "superior-version",
"metadata": {},
"source": [
"## Variant 0: INIT_MANGO_GROUP\n",
"\n",
"Instruction variant 1. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Initialize a group of lending pools that can be cross margined\n",
"```\n",
"InitMangoGroup {\n",
" signer_nonce: u64,\n",
" maint_coll_ratio: U64F64,\n",
" init_coll_ratio: U64F64,\n",
" borrow_limits: [u64; NUM_TOKENS]\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "russian-study",
"metadata": {},
"outputs": [],
"source": [
"INIT_MANGO_GROUP = construct.Struct(\n",
" \"variant\" / construct.Const(0x0, construct.BytesInteger(4, swapped=True)),\n",
" \"signer_nonce\" / DecimalAdapter(),\n",
" \"maint_coll_ratio\" / FloatAdapter(),\n",
" \"init_coll_ratio\" / FloatAdapter(),\n",
" # \"borrow_limits\" / construct.Array(NUM_TOKENS, DecimalAdapter()) # This is inconsistently available\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "weighted-neighbor",
"metadata": {},
"source": [
"## Variant 1: INIT_MARGIN_ACCOUNT\n",
"\n",
"Instruction variant 1. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Initialize a margin account for a user\n",
"```\n",
"InitMarginAccount,\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "certain-baker",
"metadata": {},
"outputs": [],
"source": [
"INIT_MARGIN_ACCOUNT = construct.Struct(\n",
" \"variant\" / construct.Const(0x1, construct.BytesInteger(4, swapped=True)),\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "second-bottom",
"metadata": {},
"source": [
"## Variant 2: DEPOSIT\n",
"\n",
"Instruction variant 2. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Deposit funds into margin account to be used as collateral and earn interest.\n",
"```\n",
"Deposit {\n",
" quantity: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "royal-repository",
"metadata": {},
"outputs": [],
"source": [
"DEPOSIT = construct.Struct(\n",
" \"variant\" / construct.Const(0x2, construct.BytesInteger(4, swapped=True)),\n",
" \"quantity\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "communist-private",
"metadata": {},
"source": [
"## Variant 3: WITHDRAW\n",
"\n",
"Instruction variant 3. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Withdraw funds that were deposited earlier.\n",
"```\n",
"Withdraw {\n",
" quantity: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "spread-spencer",
"metadata": {},
"outputs": [],
"source": [
"WITHDRAW = construct.Struct(\n",
" \"variant\" / construct.Const(0x3, construct.BytesInteger(4, swapped=True)),\n",
" \"quantity\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "valid-representation",
"metadata": {},
"source": [
"## Variant 4: BORROW\n",
"\n",
"Instruction variant 4. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Borrow by incrementing MarginAccount.borrows given collateral ratio is below init_coll_rat\n",
"```\n",
"Borrow {\n",
" token_index: usize,\n",
" quantity: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "impossible-logging",
"metadata": {},
"outputs": [],
"source": [
"BORROW = construct.Struct(\n",
" \"variant\" / construct.Const(0x4, construct.BytesInteger(4, swapped=True)),\n",
" \"token_index\" / DecimalAdapter(),\n",
" \"quantity\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "former-transparency",
"metadata": {},
"source": [
"## Variant 5: SETTLE_BORROW\n",
"\n",
"Instruction variant 5. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Use this token's position and deposit to reduce borrows\n",
"```\n",
"SettleBorrow {\n",
" token_index: usize,\n",
" quantity: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "sudden-bikini",
"metadata": {},
"outputs": [],
"source": [
"SETTLE_BORROW = construct.Struct(\n",
" \"variant\" / construct.Const(0x5, construct.BytesInteger(4, swapped=True)),\n",
" \"token_index\" / DecimalAdapter(),\n",
" \"quantity\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "measured-diving",
"metadata": {},
"source": [
"## Variant 6: LIQUIDATE\n",
"\n",
"Instruction variant 6. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Take over a MarginAccount that is below init_coll_ratio by depositing funds\n",
"```\n",
"Liquidate {\n",
" /// Quantity of each token liquidator is depositing in order to bring account above maint\n",
" deposit_quantities: [u64; NUM_TOKENS]\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "public-relay",
"metadata": {},
"outputs": [],
"source": [
"_LIQUIDATE_NUM_TOKENS = 3 # Liquidate is deprecated and was only used with 3 tokens.\n",
"LIQUIDATE = construct.Struct(\n",
" \"variant\" / construct.Const(0x6, construct.BytesInteger(4, swapped=True)),\n",
" \"deposit_quantities\" / construct.Array(_LIQUIDATE_NUM_TOKENS, DecimalAdapter())\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "limited-hygiene",
"metadata": {},
"source": [
"## Variant 7: DEPOSIT_SRM\n",
"\n",
"Instruction variant 7. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Deposit SRM into the SRM vault for MangoGroup\n",
">\n",
"> These SRM are not at risk and are not counted towards collateral or any margin calculations\n",
">\n",
"> Depositing SRM is a strictly altruistic act with no upside and no downside\n",
"```\n",
"DepositSrm {\n",
" quantity: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "apart-religion",
"metadata": {},
"outputs": [],
"source": [
"DEPOSIT_SRM = construct.Struct(\n",
" \"variant\" / construct.Const(0x7, construct.BytesInteger(4, swapped=True)),\n",
" \"quantity\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "departmental-context",
"metadata": {},
"source": [
"## Variant 8: WITHDRAW_SRM\n",
"\n",
"Instruction variant 8. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Withdraw SRM owed to this MarginAccount\n",
"```\n",
"WithdrawSrm {\n",
" quantity: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "mexican-budapest",
"metadata": {},
"outputs": [],
"source": [
"WITHDRAW_SRM = construct.Struct(\n",
" \"variant\" / construct.Const(0x8, construct.BytesInteger(4, swapped=True)),\n",
" \"quantity\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "incomplete-wound",
"metadata": {},
"source": [
"## Variant 9: PLACE_ORDER\n",
"\n",
"Instruction variant 9. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Place an order on the Serum Dex using Mango margin facilities\n",
"```\n",
"PlaceOrder {\n",
" order: serum_dex::instruction::NewOrderInstructionV3\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "expanded-resolution",
"metadata": {},
"outputs": [],
"source": [
"PLACE_ORDER = construct.Struct(\n",
" \"variant\" / construct.Const(0x9, construct.BytesInteger(4, swapped=True)),\n",
" \"order\" / construct.Padding(1) # Actual type is: serum_dex::instruction::NewOrderInstructionV3\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "nearby-defensive",
"metadata": {},
"source": [
"## Variant 10: SETTLE_FUNDS\n",
"\n",
"Instruction variant 10. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Settle all funds from serum dex open orders into MarginAccount positions\n",
"```\n",
"SettleFunds,\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "packed-crowd",
"metadata": {},
"outputs": [],
"source": [
"SETTLE_FUNDS = construct.Struct(\n",
" \"variant\" / construct.Const(0xa, construct.BytesInteger(4, swapped=True)),\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "periodic-arkansas",
"metadata": {},
"source": [
"## Variant 11: CANCEL_ORDER\n",
"\n",
"Instruction variant 11. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Cancel an order using dex instruction\n",
"```\n",
"CancelOrder {\n",
" order: serum_dex::instruction::CancelOrderInstructionV2\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "accepted-reduction",
"metadata": {},
"outputs": [],
"source": [
"CANCEL_ORDER = construct.Struct(\n",
" \"variant\" / construct.Const(0xb, construct.BytesInteger(4, swapped=True)),\n",
" \"order\" / construct.Padding(1) # Actual type is: serum_dex::instruction::CancelOrderInstructionV2\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "empty-matter",
"metadata": {},
"source": [
"## Variant 12: CANCEL_ORDER_BY_CLIENT_ID\n",
"\n",
"Instruction variant 12. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Cancel an order using client_id\n",
"```\n",
"CancelOrderByClientId {\n",
" client_id: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "recent-gardening",
"metadata": {},
"outputs": [],
"source": [
"CANCEL_ORDER_BY_CLIENT_ID = construct.Struct(\n",
" \"variant\" / construct.Const(0xc, construct.BytesInteger(4, swapped=True)),\n",
" \"client_id\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "dirty-decimal",
"metadata": {},
"source": [
"## Variant 13: CHANGE_BORROW_LIMIT\n",
"\n",
"Instruction variant 13. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs).\n",
"\n",
"> Change the borrow limit using admin key. This will not affect any open positions on any MarginAccount\n",
">\n",
"> This is intended to be an instruction only in alpha stage while liquidity is slowly improved\"_\n",
"```\n",
"ChangeBorrowLimit {\n",
" token_index: usize,\n",
" borrow_limit: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ethical-mason",
"metadata": {},
"outputs": [],
"source": [
"CHANGE_BORROW_LIMIT = construct.Struct(\n",
" \"variant\" / construct.Const(0xd, construct.BytesInteger(4, swapped=True)),\n",
" \"token_index\" / DecimalAdapter(),\n",
" \"borrow_limit\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "offensive-tract",
"metadata": {},
"source": [
"## Variant 14: PLACE_AND_SETTLE\n",
"\n",
"Instruction variant 14. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs).\n",
"\n",
"> Place an order on the Serum Dex and settle funds from the open orders account\n",
"```\n",
"PlaceAndSettle {\n",
" order: serum_dex::instruction::NewOrderInstructionV3\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "steady-plaza",
"metadata": {},
"outputs": [],
"source": [
"PLACE_AND_SETTLE = construct.Struct(\n",
" \"variant\" / construct.Const(0xe, construct.BytesInteger(4, swapped=True)),\n",
" \"order\" / construct.Padding(1) # Actual type is: serum_dex::instruction::NewOrderInstructionV3\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "dense-dinner",
"metadata": {},
"source": [
"## Variant 15: FORCE_CANCEL_ORDERS\n",
"\n",
"Instruction variant 15. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Allow a liquidator to cancel open orders and settle to recoup funds for partial liquidation\n",
"\n",
"```\n",
"ForceCancelOrders {\n",
" /// Max orders to cancel -- could be useful to lower this if running into compute limits\n",
" /// Recommended: 5\n",
" limit: u8\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "stopped-relay",
"metadata": {},
"outputs": [],
"source": [
"FORCE_CANCEL_ORDERS = construct.Struct(\n",
" \"variant\" / construct.Const(0xf, construct.BytesInteger(4, swapped=True)),\n",
" \"limit\" / DecimalAdapter(1)\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "provincial-literacy",
"metadata": {},
"source": [
"## Variant 16: PARTIAL_LIQUIDATE\n",
"\n",
"Instruction variant 16. From the [Rust source](https://github.com/blockworks-foundation/mango/blob/master/program/src/instruction.rs):\n",
"\n",
"> Take over a MarginAccount that is below init_coll_ratio by depositing funds\n",
"\n",
"```\n",
"PartialLiquidate {\n",
" /// Quantity of the token being deposited to repay borrows\n",
" max_deposit: u64\n",
"},\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "artistic-hardwood",
"metadata": {},
"outputs": [],
"source": [
"PARTIAL_LIQUIDATE = construct.Struct(\n",
" \"variant\" / construct.Const(0x10, construct.BytesInteger(4, swapped=True)),\n",
" \"max_deposit\" / DecimalAdapter()\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "announced-interval",
"metadata": {},
"source": [
"## InstructionParsersByVariant dictionary\n",
"\n",
"This dictionary provides an easy way for us to access the specific parser for a given variant."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "macro-proportion",
"metadata": {},
"outputs": [],
"source": [
"InstructionParsersByVariant = {\n",
" 0: INIT_MANGO_GROUP,\n",
" 1: INIT_MARGIN_ACCOUNT,\n",
" 2: DEPOSIT,\n",
" 3: WITHDRAW,\n",
" 4: BORROW,\n",
" 5: SETTLE_BORROW,\n",
" 6: LIQUIDATE,\n",
" 7: DEPOSIT_SRM,\n",
" 8: WITHDRAW_SRM,\n",
" 9: PLACE_ORDER,\n",
" 10: SETTLE_FUNDS,\n",
" 11: CANCEL_ORDER,\n",
" 12: CANCEL_ORDER_BY_CLIENT_ID,\n",
" 13: CHANGE_BORROW_LIMIT,\n",
" 14: PLACE_AND_SETTLE,\n",
" 15: FORCE_CANCEL_ORDERS,\n",
" 16: PARTIAL_LIQUIDATE\n",
"}\n"
]
},
{
"cell_type": "markdown",
"id": "foreign-provincial",
2021-04-14 08:51:39 -07:00
"metadata": {},
"source": [
"# 🏃 Running"
2021-04-14 08:51:39 -07:00
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "european-adventure",
2021-04-14 08:51:39 -07:00
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" import base64\n",
" import logging\n",
"\n",
" logging.getLogger().setLevel(logging.INFO)\n",
2021-04-14 08:51:39 -07:00
"\n",
" encoded_3_token_group = \"AwAAAAAAAACCaOmpoURMK6XHelGTaFawcuQ/78/15LAemWI8jrt3SRKLy2R9i60eclDjuDS8+p/ZhvTUd9G7uQVOYCsR6+BhmqGCiO6EPYP2PQkf/VRTvw7JjXvIjPFJy06QR1Cq1WfTonHl0OjCkyEf60SD07+MFJu5pVWNFGGEO/8AiAYfduaKdnFTaZEHPcK5Eq72WWHeHg2yIbBF09kyeOhlCJwOoG8O5SgpPV8QOA64ZNV4aKroFfADg6kEy/wWCdp3fv0O4GJgAAAAAPH6Ud6jtjwAAQAAAAAAAADiDkkCi9UOAAEAAAAAAAAADuBiYAAAAACNS5bSy7soAAEAAAAAAAAACMvgO+2jCwABAAAAAAAAAA7gYmAAAAAAZFeDUBNVhwABAAAAAAAAABtRNytozC8AAQAAAAAAAABIBGiCcyaEZdNhrTyeqUY692vOzzPdHaxAxguht3JQGlkzjtd05dX9LENHkl2z1XvUbTNKZlweypNRetmH0lmQ9VYQAHqylxZVK65gEg85g27YuSyvOBZAjJyRmYU9KdCO1D+4ehdPu9dQB1yI1uh75wShdAaFn2o4qrMYwq3SQQEAAAAAAAAAAiH1PPJKAuh6oGiE35aGhUQhFi/bxgKOudpFv8HEHNCFDy1uAqR6+CTQmradxC1wyyjL+iSft+5XudJWwSdi7wvphsxb96x7Obj/AgAAAAAKlV4LL5ow6r9LMhIAAAAADvsOtqcVFmChDPzPnwAAAE33lx1h8hPFD04AAAAAAAA8YRV3Oa309B2wGwAAAAAA+yPBZRlZz7b605n+AQAAAACgmZmZmZkZAQAAAAAAAAAAMDMzMzMzMwEAAAAAAAAA25D1XcAtRzSuuyx3U+X7aE9vM1EJySU9KprgL0LMJ/vat9+SEEUZuga7O5tTUrcMDYWDg+LYaAWhSQiN2fYk7aCGAQAAAAAAgIQeAAAAAAAA8gUqAQAAAAYGBgICAAAA\"\n",
" decoded_3_token_group = base64.b64decode(encoded_3_token_group)\n",
"\n",
" group_with_3_tokens = GROUP_V1.parse(decoded_3_token_group)\n",
" print(\"\\n\\nThis is hard-coded, not live information!\")\n",
" print(group_with_3_tokens)\n",
"\n",
" encoded_5_token_group = \"AwAAAAAAAACk6bHzfLvX/YZNskK+2brLGvXTPR3P4qF2Hkc2HZANL3QVQJS5HzYh3sTbcf99JgISg7g07yK6MxP5nzzTyEy8BpuIV/6rgYT7aH9jRhjANdrEOdwa6ztVmKDwAAAAAAF6mkC+0kyNlxFPeKUyBpYL11A4a/pUcOYunlw92EFaYV3OcRQkdPidUvY2YtaR52uqW752f+Ufcci7ei8SWZYWgwYD6XWYDToBxFf2L2JoXCKqjFDfQ8+dfYUGnLBupwJdSa0UH20tWtHJh6VlhzFcMCQSU8LYiS6z6cR/GWXcWKd36DfFLhkULOxma3DDEBmYIqDsEZC02N3Vm5lJSc+okeVxcJzdQ48XVJuyTEli9TP2HAbrNqFJSyFbQo0f+dKsTopavA5ndrNOk9So4ANgHdwBGUVGY0SLS7+TVXkcCgwgqGAAAAAALYxEAuIBAAABAAAAAAAAAErLeL8QAAAAAQAAAAAAAAAMIKhgAAAAACwC4BEHAAAAAQAAAAAAAACByD0AAAAAAAEAAAAAAAAADCCoYAAAAABJpmxt87MCAAEAAAAAAAAArwaaHqGSAgABAAAAAAAAAAwgqGAAAAAANHqKx8PiAwABAAAAAAAAACyFpt5myQMAAQAAAAAAAAAMIKhgAAAAAIrCIhLj2gUAAQAAAAAAAACd0UEiFPsAAAEAAAAAAAAATVH2gvK/JiB7fCRmoBvLuTcba2/qHCRnzOjDFFYNStgzmK/Q8Wk0MLLXAIvgBrVJW1RiSvwiqrTezEkb3zUF4m47JYDk9DZOvmCGR63JwGv8BXI/DLCdMIagQ6MUC4krqaznZiQhDOVuXXpiS+7Q4PgE8V9wkGVQHFpDgVQVx6VSNqBWdfB5O0wXcsXSAxviIZnpzZb8yVPAJhqxFNViEC+Y1L1+ULx6KHTSRIwn5wXTktPuZEsRtSpUJpqZD9V5y+8i6KVSbQNBUDzf0xeAFH85lOlowgaoBW/MjycjsrXlD0s7jbPTK2SvlK0fgerysq1O8y7bk5yp6001Zr/sLAEAAAAAAAAAkE0bF+zrix2vIYYIDbjjQiF/B5h1fwDNpfltVuG2QD61vZ7LZ502Gt8wb/WL0dyrOS4eySbPnOiFlVUBjsVtHweAuy088r4ZtPPGxCoAAABG2qkYb53x/zKdhfmqjgMA+NOnThFN5Rte4iNxcwIAACjnpx/Yisq5DiDQQgAAAAAGSKNb2bYXz1S4odlZDwAAxfiZZdXRLGMAAAAAAAAAALNmZBB7GyHIDgAAAAAAAABPGsO1PmFjqp3jcQAAAAAAil3Lhe3fP1HPAgAAAAAAANnFw5TROkYQ1B82OwkAAAAAoJmZmZmZGQEAAAAAAAAAADAzMzMzMzMBAAAAAAAAAIOo484GpuWM2iV176h0nKGu2lHIio/GeSYKkkptjxvYBpU3RtcibIXhdSaNSvboTLAKz91Es3OeaoUjHgSxfAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQkJBgYCAgICAAAAAAAAAA==\"\n",
" decoded_5_token_group = base64.b64decode(encoded_5_token_group)\n",
2021-04-14 08:51:39 -07:00
"\n",
" group_with_5_tokens = GROUP_V2.parse(decoded_5_token_group)\n",
2021-04-14 08:51:39 -07:00
" print(\"\\n\\nThis is hard-coded, not live information!\")\n",
" print(group_with_5_tokens)\n",
"\n"
2021-04-14 08:51:39 -07:00
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
},
"toc-showcode": false,
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
2021-04-14 08:51:39 -07:00
},
"nbformat": 4,
"nbformat_minor": 5
}