{ "cells": [ { "cell_type": "markdown", "id": "sized-burst", "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", "[![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)" ] }, { "cell_type": "markdown", "id": "relative-register", "metadata": {}, "source": [ "# 🥭 Layouts\n", "\n", "This notebook contains structure layouts to load the sometimes-opaque data blobs from Solana accounts.\n", "\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", "\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": "internal-andrews", "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "import construct\n", "import datetime\n", "\n", "from decimal import Decimal\n", "from solana.publickey import PublicKey\n", "\n", "from Constants import NUM_MARKETS, NUM_TOKENS\n" ] }, { "cell_type": "markdown", "id": "linear-depth", "metadata": {}, "source": [ "# Adapters\n", "\n", "These are adapters for the construct package to simplify our struct declarations." ] }, { "cell_type": "markdown", "id": "looking-yukon", "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": "acting-technical", "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" ] }, { "cell_type": "markdown", "id": "terminal-intensity", "metadata": {}, "source": [ "## Float64Adapter class\n", "\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", "\n", "This is a simple construct `Adapter` that lets us use these float values directly in our structs." ] }, { "cell_type": "code", "execution_count": null, "id": "military-township", "metadata": {}, "outputs": [], "source": [ "class Float64Adapter(construct.Adapter):\n", " _divisor = Decimal(2 ** 64)\n", "\n", " def __init__(self, size: int = 16):\n", " construct.Adapter.__init__(self, construct.BytesInteger(size, swapped=True))\n", "\n", " def _decode(self, obj, context, path) -> Decimal:\n", " return Decimal(obj) / Float64Adapter._divisor\n", "\n", " def _encode(self, obj, context, path) -> bytes:\n", " return bytes(obj)\n" ] }, { "cell_type": "markdown", "id": "crude-joseph", "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": "derived-antenna", "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) -> PublicKey:\n", " return PublicKey(obj)\n", "\n", " def _encode(self, obj, context, path) -> bytes:\n", " return bytes(obj)\n" ] }, { "cell_type": "markdown", "id": "described-mechanism", "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": "extreme-amount", "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", "\n", " " ] }, { "cell_type": "markdown", "id": "communist-survivor", "metadata": {}, "source": [ "# Layout Structs" ] }, { "cell_type": "markdown", "id": "robust-level", "metadata": {}, "source": [ "## SERUM_ACCOUNT_FLAGS\n", "\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": "mediterranean-bloom", "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": "successful-musical", "metadata": {}, "source": [ "## MANGO_ACCOUNT_FLAGS\n", "\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": "complete-payment", "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": "virtual-running", "metadata": {}, "source": [ "## INDEX\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 MangoIndex {\n", " pub last_update: u64,\n", " pub borrow: U64F64,\n", " pub deposit: U64F64\n", "}\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "naval-wheat", "metadata": {}, "outputs": [], "source": [ "INDEX = construct.Struct(\n", " \"last_update\" / DatetimeAdapter(),\n", " \"borrow\" / Float64Adapter(),\n", " \"deposit\" / Float64Adapter()\n", ");\n" ] }, { "cell_type": "markdown", "id": "empirical-dodge", "metadata": {}, "source": [ "## AGGREGATOR_CONFIG\n", "\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": "frequent-bearing", "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" ] }, { "cell_type": "markdown", "id": "chronic-august", "metadata": {}, "source": [ "## ROUND\n", "\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": "classical-sunrise", "metadata": {}, "outputs": [], "source": [ "ROUND = construct.Struct(\n", " \"id\" / DecimalAdapter(),\n", " \"created_at\" / DecimalAdapter(),\n", " \"updated_at\" / DecimalAdapter()\n", ");\n" ] }, { "cell_type": "markdown", "id": "german-ridge", "metadata": {}, "source": [ "## ANSWER\n", "\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": "first-hazard", "metadata": {}, "outputs": [], "source": [ "ANSWER = construct.Struct(\n", " \"round_id\" / DecimalAdapter(),\n", " \"median\" / DecimalAdapter(),\n", " \"created_at\" / DatetimeAdapter(),\n", " \"updated_at\" / DatetimeAdapter()\n", ");\n" ] }, { "cell_type": "markdown", "id": "universal-ceiling", "metadata": {}, "source": [ "## AGGREGATOR\n", "\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": "other-german", "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" ] }, { "cell_type": "markdown", "id": "skilled-sense", "metadata": {}, "source": [ "## GROUP\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", " 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!(MangoGr\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "collective-cisco", "metadata": {}, "outputs": [], "source": [ "GROUP_PADDING = 8 - (NUM_TOKENS + NUM_MARKETS) % 8\n", "\n", "GROUP = construct.Struct(\n", " \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n", " \"tokens\" / construct.Array(NUM_TOKENS, PublicKeyAdapter()),\n", " \"vaults\" / construct.Array(NUM_TOKENS, PublicKeyAdapter()),\n", " \"indexes\" / construct.Array(NUM_TOKENS, INDEX),\n", " \"spot_markets\" / construct.Array(NUM_MARKETS, PublicKeyAdapter()),\n", " \"oracles\" / construct.Array(NUM_MARKETS, PublicKeyAdapter()),\n", " \"signer_nonce\" / DecimalAdapter(),\n", " \"signer_key\" / PublicKeyAdapter(),\n", " \"dex_program_id\" / PublicKeyAdapter(),\n", " \"total_deposits\" / construct.Array(NUM_TOKENS, Float64Adapter()),\n", " \"total_borrows\" / construct.Array(NUM_TOKENS, Float64Adapter()),\n", " \"maint_coll_ratio\" / Float64Adapter(),\n", " \"init_coll_ratio\" / Float64Adapter(),\n", " \"srm_vault\" / PublicKeyAdapter(),\n", " \"admin\" / PublicKeyAdapter(),\n", " \"borrow_limits\" / construct.Array(NUM_TOKENS, DecimalAdapter()),\n", " \"mint_decimals\" / construct.Array(NUM_TOKENS, DecimalAdapter(1)),\n", " \"oracle_decimals\" / construct.Array(NUM_MARKETS, DecimalAdapter(1)),\n", " \"padding\" / construct.Array(GROUP_PADDING, construct.Padding(1))\n", ");\n" ] }, { "cell_type": "markdown", "id": "foster-answer", "metadata": {}, "source": [ "## TOKEN_ACCOUNT" ] }, { "cell_type": "code", "execution_count": null, "id": "indirect-seminar", "metadata": {}, "outputs": [], "source": [ "TOKEN_ACCOUNT = construct.Struct(\n", " \"mint\" / PublicKeyAdapter(),\n", " \"owner\" / PublicKeyAdapter(),\n", " \"amount\" / DecimalAdapter(),\n", " \"padding\" / construct.Padding(93)\n", ");\n" ] }, { "cell_type": "markdown", "id": "civil-athens", "metadata": {}, "source": [ "## OPEN_ORDERS\n", "\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": "registered-interpretation", "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" ] }, { "cell_type": "markdown", "id": "explicit-sigma", "metadata": {}, "source": [ "## MARGIN_ACCOUNT\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 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": "sharing-clinton", "metadata": {}, "outputs": [], "source": [ "MARGIN_ACCOUNT = construct.Struct(\n", " \"account_flags\" / MANGO_ACCOUNT_FLAGS,\n", " \"mango_group\" / PublicKeyAdapter(),\n", " \"owner\" / PublicKeyAdapter(),\n", " \"deposits\" / construct.Array(NUM_TOKENS, Float64Adapter()),\n", " \"borrows\" / construct.Array(NUM_TOKENS, Float64Adapter()),\n", " \"open_orders\" / construct.Array(NUM_MARKETS, PublicKeyAdapter()),\n", " \"padding\" / construct.Padding(8)\n", ");\n" ] }, { "cell_type": "markdown", "id": "latter-optimization", "metadata": {}, "source": [ "## FORCE_CANCEL" ] }, { "cell_type": "code", "execution_count": null, "id": "cathedral-berry", "metadata": {}, "outputs": [], "source": [ "FORCE_CANCEL = construct.Struct(\n", " \"variant\" / construct.Const(0xf, construct.BytesInteger(4, swapped=True)),\n", " \"limit\" / construct.BytesInteger(1, swapped=False)\n", ");\n" ] }, { "cell_type": "markdown", "id": "composed-colony", "metadata": {}, "source": [ "## PARTIAL_LIQUIDATE" ] }, { "cell_type": "code", "execution_count": null, "id": "vietnamese-theorem", "metadata": {}, "outputs": [], "source": [ "PARTIAL_LIQUIDATE = construct.Struct(\n", " \"variant\" / construct.Const(0x10, construct.BytesInteger(4, swapped=True)),\n", " \"maxDeposit\" / DecimalAdapter()\n", ");\n" ] }, { "cell_type": "markdown", "id": "funded-neutral", "metadata": {}, "source": [ "# 🏃 Running" ] }, { "cell_type": "code", "execution_count": null, "id": "anticipated-shakespeare", "metadata": {}, "outputs": [], "source": [ "if __name__ == \"__main__\":\n", " import base64\n", " import logging\n", "\n", " logging.getLogger().setLevel(logging.INFO)\n", "\n", " encoded = \"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 = base64.b64decode(encoded)\n", "\n", " group = GROUP.parse(decoded)\n", " print(\"\\n\\nThis is hard-coded, not live information!\")\n", " print(group)\n" ] } ], "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 } }, "nbformat": 4, "nbformat_minor": 5 }