Mc/realloc 4 (#119)

Realloc + dynamic mango account

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
Co-authored-by: Christian Kamm <mail@ckamm.de>
This commit is contained in:
microwavedcola1 2022-07-25 16:07:53 +02:00 committed by GitHub
parent 46b6bce14b
commit 0b2e1e6e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 3356 additions and 1754 deletions

227
Cargo.lock generated
View File

@ -107,7 +107,20 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70f6ee9518f50ff4d434471ccf569186022bdd5ef65a21d14da3ea5231af944f"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
"regex",
"syn 1.0.95",
]
[[package]]
name = "anchor-attribute-access-control"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
@ -121,7 +134,21 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32c92bcf5388b52676d990f85bbfd838a8f5672393135063a50dc79b2b837c79"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow",
"bs58 0.4.0",
"proc-macro2 1.0.39",
"quote 1.0.18",
"rustversion",
"syn 1.0.95",
]
[[package]]
name = "anchor-attribute-account"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"bs58 0.4.0",
"proc-macro2 1.0.39",
@ -136,7 +163,17 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0844974ac35e8ced62056b0d63777ebcdc5807438b8b189c881e2b647450b70a"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.39",
"syn 1.0.95",
]
[[package]]
name = "anchor-attribute-constant"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"proc-macro2 1.0.39",
"syn 1.0.95",
]
@ -147,7 +184,18 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f7467345e67a6f1d4b862b9763a4160ad89d18c247b8c902807768f7b6e23df"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.39",
"quote 1.0.18",
"syn 1.0.95",
]
[[package]]
name = "anchor-attribute-error"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"proc-macro2 1.0.39",
"quote 1.0.18",
"syn 1.0.95",
@ -159,7 +207,19 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8774e4c1ac71f71a5aea7e4932fb69c30e3b8155c4fa59fd69401195434528a9"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
"syn 1.0.95",
]
[[package]]
name = "anchor-attribute-event"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
@ -172,7 +232,20 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeb6e1c80f9f94fcef93a52813f6472186200e275e83cb3fac92b801de92f7"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow",
"heck 0.3.3",
"proc-macro2 1.0.39",
"quote 1.0.18",
"syn 1.0.95",
]
[[package]]
name = "anchor-attribute-interface"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"heck 0.3.3",
"proc-macro2 1.0.39",
@ -186,7 +259,19 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac515a7a5a4fea7fc768b1cec40ddb948e148ea657637c75f94f283212326cb9"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
"syn 1.0.95",
]
[[package]]
name = "anchor-attribute-program"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
@ -199,7 +284,19 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43dc667b62ff71450f19dcfcc37b0c408fd4ddd89e8650368c2b0984b110603f"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
"syn 1.0.95",
]
[[package]]
name = "anchor-attribute-state"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
@ -209,10 +306,9 @@ dependencies = [
[[package]]
name = "anchor-client"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee0e630f9310a0134c92df4458890a0f9c5b662d69c305690af1c17f5cd0b3ba"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-lang",
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"regex",
"serde",
@ -229,7 +325,19 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7354d583a06701d24800a8ec4c2b0491f62581a331af349205e23421e0b56643"
dependencies = [
"anchor-syn",
"anchor-syn 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
"syn 1.0.95",
]
[[package]]
name = "anchor-derive-accounts"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-syn 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"proc-macro2 1.0.39",
"quote 1.0.18",
@ -242,15 +350,38 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff5f57ec5e12fa6874b27f3d5c1f6f44302d3ad86c1266197ff7611bf6f5d251"
dependencies = [
"anchor-attribute-access-control",
"anchor-attribute-account",
"anchor-attribute-constant",
"anchor-attribute-error",
"anchor-attribute-event",
"anchor-attribute-interface",
"anchor-attribute-program",
"anchor-attribute-state",
"anchor-derive-accounts",
"anchor-attribute-access-control 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-attribute-account 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-attribute-constant 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-attribute-error 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-attribute-event 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-attribute-interface 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-attribute-program 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-attribute-state 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-derive-accounts 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayref",
"base64 0.13.0",
"bincode",
"borsh",
"bytemuck",
"solana-program",
"thiserror",
]
[[package]]
name = "anchor-lang"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-attribute-access-control 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-attribute-account 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-attribute-constant 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-attribute-error 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-attribute-event 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-attribute-interface 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-attribute-program 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-attribute-state 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-derive-accounts 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"arrayref",
"base64 0.13.0",
"bincode",
@ -266,7 +397,18 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65904c3106851f6d1bb87d504044764819d69c51d2b4346d59d399d8afa7d18"
dependencies = [
"anchor-lang",
"anchor-lang 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-program",
"spl-associated-token-account",
"spl-token",
]
[[package]]
name = "anchor-spl"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"solana-program",
"spl-associated-token-account",
"spl-token",
@ -291,6 +433,24 @@ dependencies = [
"thiserror",
]
[[package]]
name = "anchor-syn"
version = "0.25.0"
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
dependencies = [
"anyhow",
"bs58 0.3.1",
"heck 0.3.3",
"proc-macro2 1.0.39",
"proc-macro2-diagnostics",
"quote 1.0.18",
"serde",
"serde_json",
"sha2 0.9.9",
"syn 1.0.95",
"thiserror",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@ -1039,8 +1199,8 @@ name = "client"
version = "0.1.0"
dependencies = [
"anchor-client",
"anchor-lang",
"anchor-spl",
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-spl 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"fixed",
"fixed-macro",
@ -1048,6 +1208,7 @@ dependencies = [
"mango-v4",
"pyth-sdk-solana",
"serum_dex",
"solana-account-decoder",
"solana-client",
"solana-sdk",
"thiserror",
@ -2662,8 +2823,8 @@ name = "keeper"
version = "0.1.0"
dependencies = [
"anchor-client",
"anchor-lang",
"anchor-spl",
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-spl 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"clap 3.1.18",
"client",
@ -2816,7 +2977,7 @@ name = "liquidator"
version = "0.0.1"
dependencies = [
"anchor-client",
"anchor-lang",
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anyhow",
"arrayref",
"async-channel",
@ -2935,8 +3096,8 @@ dependencies = [
name = "mango-v4"
version = "0.1.0"
dependencies = [
"anchor-lang",
"anchor-spl",
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-spl 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"arrayref",
"async-trait",
"base64 0.13.0",
@ -2972,8 +3133,8 @@ dependencies = [
name = "margin-trade"
version = "0.1.0"
dependencies = [
"anchor-lang",
"anchor-spl",
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"anchor-spl 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
"solana-program",
]
@ -6352,8 +6513,8 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e01007eb005d7b5d4b6daaec7db6fe0b5ce9a0c6cb28b00d5c94dc7bfacc075"
dependencies = [
"anchor-lang",
"anchor-spl",
"anchor-lang 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anchor-spl 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck",
"rust_decimal",
"solana-program",

View File

@ -9,4 +9,4 @@ members = [
[patch.crates-io]
# for gzip encoded responses
jsonrpc-core-client = { git = "https://github.com/ckamm/jsonrpc.git", branch = "ckamm/http-with-gzip" }
jsonrpc-core-client = { git = "https://github.com/ckamm/jsonrpc.git", branch = "ckamm/http-with-gzip" }

View File

@ -7,9 +7,9 @@ edition = "2021"
doctest = false
[dependencies]
anchor-client = "0.25.0"
anchor-lang = "0.25.0"
anchor-spl = "0.25.0"
anchor-client = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anchor-spl = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anyhow = "1.0"
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
fixed-macro = "^1.1.1"
@ -17,6 +17,7 @@ itertools = "0.10.3"
mango-v4 = { path = "../programs/mango-v4" }
pyth-sdk-solana = "0.1.0"
serum_dex = { version = "0.4.0", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false, features = ["no-entrypoint", "program"] }
solana-account-decoder = "~1.10.29"
solana-client = "~1.10.29"
solana-sdk = "~1.10.29"
thiserror = "1.0.31"

View File

@ -1,16 +1,17 @@
use std::collections::HashMap;
use std::sync::Mutex;
use anchor_client::ClientError;
use anyhow::Context;
use anchor_client::ClientError;
use anchor_lang::AccountDeserialize;
use solana_client::rpc_client::RpcClient;
use anyhow::Context;
use solana_sdk::account::Account;
use solana_sdk::pubkey::Pubkey;
use mango_v4::state::MangoAccountValue;
pub trait AccountFetcher: Sync + Send {
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<Account>;
}
@ -22,7 +23,19 @@ pub fn account_fetcher_fetch_anchor_account<T: AccountDeserialize>(
) -> anyhow::Result<T> {
let account = fetcher.fetch_raw_account(address)?;
let mut data: &[u8] = &account.data;
T::try_deserialize(&mut data).with_context(|| format!("deserializing account {}", address))
T::try_deserialize(&mut data)
.with_context(|| format!("deserializing anchor account {}", address))
}
// Can't be in the trait, since then it would no longer be object-safe...
pub fn account_fetcher_fetch_mango_account(
fetcher: &dyn AccountFetcher,
address: Pubkey,
) -> anyhow::Result<MangoAccountValue> {
let account = fetcher.fetch_raw_account(address)?;
let data: &[u8] = &account.data;
MangoAccountValue::from_bytes(&data[8..])
.with_context(|| format!("deserializing mango account {}", address))
}
pub struct RpcAccountFetcher {

View File

@ -11,7 +11,7 @@ use anchor_spl::token::Token;
use fixed::types::I80F48;
use itertools::Itertools;
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
use mango_v4::state::{Bank, Group, MangoAccount, Serum3MarketIndex, TokenIndex};
use mango_v4::state::{AccountSize, Bank, Group, MangoAccountValue, Serum3MarketIndex, TokenIndex};
use solana_client::rpc_client::RpcClient;
@ -100,12 +100,16 @@ impl MangoClient {
let mut mango_account_tuples = fetch_mango_accounts(&program, group, payer.pubkey())?;
let mango_account_opt = mango_account_tuples
.iter()
.find(|tuple| tuple.1.name() == mango_account_name);
.find(|(_, account)| account.fixed.name() == mango_account_name);
if mango_account_opt.is_none() {
mango_account_tuples
.sort_by(|a, b| a.1.account_num.partial_cmp(&b.1.account_num).unwrap());
mango_account_tuples.sort_by(|a, b| {
a.1.fixed
.account_num
.partial_cmp(&b.1.fixed.account_num)
.unwrap()
});
let account_num = match mango_account_tuples.last() {
Some(tuple) => tuple.1.account_num + 1,
Some(tuple) => tuple.1.fixed.account_num + 1,
None => 0u8,
};
program
@ -137,6 +141,7 @@ impl MangoClient {
&mango_v4::instruction::AccountCreate {
account_num,
name: mango_account_name.to_owned(),
account_size: AccountSize::Small,
},
),
})
@ -146,9 +151,9 @@ impl MangoClient {
let mango_account_tuples = fetch_mango_accounts(&program, group, payer.pubkey())?;
let index = mango_account_tuples
.iter()
.position(|tuple| tuple.1.name() == mango_account_name)
.position(|tuple| tuple.1.fixed.name() == mango_account_name)
.unwrap();
let mango_account_cache = mango_account_tuples[index];
let mango_account_cache = &mango_account_tuples[index];
Ok(Self {
rpc,
@ -181,8 +186,8 @@ impl MangoClient {
self.context.group
}
pub fn mango_account(&self) -> anyhow::Result<MangoAccount> {
account_fetcher_fetch_anchor_account(&*self.account_fetcher, self.mango_account_address)
pub fn mango_account(&self) -> anyhow::Result<MangoAccountValue> {
account_fetcher_fetch_mango_account(&*self.account_fetcher, self.mango_account_address)
}
pub fn first_bank(&self, token_index: TokenIndex) -> anyhow::Result<Bank> {
@ -205,7 +210,7 @@ impl MangoClient {
pub fn derive_liquidation_health_check_remaining_account_metas(
&self,
liqee: &MangoAccount,
liqee: &MangoAccountValue,
asset_token_index: TokenIndex,
liab_token_index: TokenIndex,
) -> anyhow::Result<Vec<AccountMeta>> {
@ -215,9 +220,8 @@ impl MangoClient {
let account = self.mango_account()?;
let token_indexes = liqee
.tokens
.iter_active()
.chain(account.tokens.iter_active())
.token_iter_active()
.chain(account.token_iter_active())
.map(|ta| ta.token_index)
.unique();
@ -229,14 +233,12 @@ impl MangoClient {
}
let serum_oos = liqee
.serum3
.iter_active()
.chain(account.serum3.iter_active())
.serum3_iter_active()
.chain(account.serum3_iter_active())
.map(|&s| s.open_orders);
let perp_markets = liqee
.perps
.iter_active_accounts()
.chain(account.perps.iter_active_accounts())
.perp_iter_active_accounts()
.chain(account.perp_iter_active_accounts())
.map(|&pa| self.context.perp_market_address(pa.market_index));
let to_account_meta = |pubkey| AccountMeta {
@ -392,7 +394,7 @@ impl MangoClient {
let s3 = self.serum3_data(name)?;
let account = self.mango_account()?;
let open_orders = account.serum3.find(s3.market_index).unwrap().open_orders;
let open_orders = account.serum3_find(s3.market_index).unwrap().open_orders;
let health_check_metas = self.derive_health_check_remaining_account_metas(None, false)?;
@ -506,7 +508,7 @@ impl MangoClient {
let s3 = self.serum3_data(name)?;
let account = self.mango_account()?;
let open_orders = account.serum3.find(s3.market_index).unwrap().open_orders;
let open_orders = account.serum3_find(s3.market_index).unwrap().open_orders;
self.program()
.request()
@ -547,7 +549,7 @@ impl MangoClient {
.get(market_name)
.unwrap();
let account = self.mango_account()?;
let open_orders = account.serum3.find(market_index).unwrap().open_orders;
let open_orders = account.serum3_find(market_index).unwrap().open_orders;
let open_orders_bytes = self.account_fetcher.fetch_raw_account(open_orders)?.data;
let open_orders_data: &serum_dex::state::OpenOrders = bytemuck::from_bytes(
@ -579,7 +581,7 @@ impl MangoClient {
let s3 = self.serum3_data(market_name)?;
let account = self.mango_account()?;
let open_orders = account.serum3.find(s3.market_index).unwrap().open_orders;
let open_orders = account.serum3_find(s3.market_index).unwrap().open_orders;
self.program()
.request()
@ -622,7 +624,7 @@ impl MangoClient {
pub fn liq_token_with_token(
&self,
liqee: (&Pubkey, &MangoAccount),
liqee: (&Pubkey, &MangoAccountValue),
asset_token_index: TokenIndex,
liab_token_index: TokenIndex,
max_liab_transfer: I80F48,
@ -666,7 +668,7 @@ impl MangoClient {
pub fn liq_token_bankruptcy(
&self,
liqee: (&Pubkey, &MangoAccount),
liqee: (&Pubkey, &MangoAccountValue),
liab_token_index: TokenIndex,
max_liab_transfer: I80F48,
) -> anyhow::Result<Signature> {

View File

@ -5,7 +5,7 @@ use anchor_client::{Client, ClientError, Cluster, Program};
use anchor_lang::__private::bytemuck;
use mango_v4::state::{
MangoAccount, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market, Serum3MarketIndex,
MangoAccountValue, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market, Serum3MarketIndex,
TokenIndex,
};
@ -186,43 +186,36 @@ impl MangoGroupContext {
pub fn derive_health_check_remaining_account_metas(
&self,
account: &MangoAccount,
account: &MangoAccountValue,
affected_token: Option<TokenIndex>,
writable_banks: bool,
) -> anyhow::Result<Vec<AccountMeta>> {
// figure out all the banks/oracles that need to be passed for the health check
let mut banks = vec![];
let mut oracles = vec![];
for position in account.tokens.iter_active() {
for position in account.token_iter_active() {
let mint_info = self.mint_info(position.token_index);
banks.push(mint_info.first_bank());
oracles.push(mint_info.oracle);
}
if let Some(affected_token_index) = affected_token {
if account
.tokens
.iter_active()
.token_iter_active()
.find(|p| p.token_index == affected_token_index)
.is_none()
{
// If there is not yet an active position for the token, we need to pass
// the bank/oracle for health check anyway.
let new_position = account
.tokens
.values
.iter()
.position(|p| !p.is_active())
.unwrap();
let new_position = account.token_iter().position(|p| !p.is_active()).unwrap();
let mint_info = self.mint_info(affected_token_index);
banks.insert(new_position, mint_info.first_bank());
oracles.insert(new_position, mint_info.oracle);
}
}
let serum_oos = account.serum3.iter_active().map(|&s| s.open_orders);
let serum_oos = account.serum3_iter_active().map(|&s| s.open_orders);
let perp_markets = account
.perps
.iter_active_accounts()
.perp_iter_active_accounts()
.map(|&pa| self.perp_market_address(pa.market_index));
Ok(banks

View File

@ -1,7 +1,10 @@
use anchor_client::{ClientError, Program};
use anchor_lang::Discriminator;
use mango_v4::state::{Bank, MangoAccount, MintInfo, PerpMarket, Serum3Market};
use mango_v4::state::{Bank, MangoAccount, MangoAccountValue, MintInfo, PerpMarket, Serum3Market};
use solana_account_decoder::UiAccountEncoding;
use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig};
use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType};
use solana_sdk::pubkey::Pubkey;
@ -9,19 +12,37 @@ pub fn fetch_mango_accounts(
program: &Program,
group: Pubkey,
owner: Pubkey,
) -> Result<Vec<(Pubkey, MangoAccount)>, ClientError> {
program.accounts::<MangoAccount>(vec![
RpcFilterType::Memcmp(Memcmp {
offset: 8,
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
encoding: None,
}),
RpcFilterType::Memcmp(Memcmp {
offset: 40,
bytes: MemcmpEncodedBytes::Base58(owner.to_string()),
encoding: None,
}),
])
) -> Result<Vec<(Pubkey, MangoAccountValue)>, ClientError> {
let config = RpcProgramAccountsConfig {
filters: Some(vec![
RpcFilterType::Memcmp(Memcmp {
offset: 0,
bytes: MemcmpEncodedBytes::Bytes(MangoAccount::discriminator().to_vec()),
encoding: None,
}),
RpcFilterType::Memcmp(Memcmp {
offset: 8,
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
encoding: None,
}),
RpcFilterType::Memcmp(Memcmp {
offset: 40,
bytes: MemcmpEncodedBytes::Base58(owner.to_string()),
encoding: None,
}),
]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
..RpcAccountInfoConfig::default()
},
..RpcProgramAccountsConfig::default()
};
program
.rpc()
.get_program_accounts_with_config(&program.id(), config)?
.into_iter()
.map(|(key, account)| Ok((key, MangoAccountValue::from_bytes(&account.data[8..])?)))
.collect::<Result<Vec<_>, _>>()
}
pub fn fetch_banks(program: &Program, group: Pubkey) -> Result<Vec<(Pubkey, Bank)>, ClientError> {

View File

@ -6,9 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anchor-client = "0.25.0"
anchor-lang = "0.25.0"
anchor-spl = "0.25.0"
anchor-client = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anchor-spl = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anyhow = "1.0"
clap = { version = "3.1.8", features = ["derive", "env"] }
client = { path = "../client" }

View File

@ -77,7 +77,7 @@ fn ensure_oo(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error> {
let account = mango_client.mango_account()?;
for (market_index, serum3_market) in mango_client.context.serum3_markets.iter() {
if account.serum3.find(*market_index).is_none() {
if account.serum3_find(*market_index).is_none() {
mango_client.serum3_create_open_orders(serum3_market.market.name())?;
}
}
@ -92,7 +92,7 @@ fn ensure_deposit(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error>
let bank = mango_client.first_bank(token_index)?;
let desired_balance = I80F48::from_num(10_000 * 10u64.pow(bank.mint_decimals as u32));
let token_account_opt = mango_account.tokens.find(token_index);
let token_account_opt = mango_account.token_find(token_index);
let deposit_native = match token_account_opt {
Some(token_account) => {

View File

@ -4,8 +4,8 @@ version = "0.0.1"
edition = "2021"
[dependencies]
anchor-client = "0.25.0"
anchor-lang = "0.25.0"
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anchor-client = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anyhow = "1.0"
arrayref = "0.3.6"
async-channel = "1.6"

View File

@ -4,11 +4,12 @@ use crate::chain_data::*;
use client::AccountFetcher;
use mango_v4::accounts_zerocopy::LoadZeroCopy;
use mango_v4::state::MangoAccountValue;
use anyhow::Context;
use solana_client::rpc_client::RpcClient;
use solana_sdk::account::AccountSharedData;
use solana_sdk::account::{AccountSharedData, ReadableAccount};
use solana_sdk::pubkey::Pubkey;
pub struct ChainDataAccountFetcher {
@ -29,6 +30,12 @@ impl ChainDataAccountFetcher {
.clone())
}
pub fn fetch_mango_account(&self, address: &Pubkey) -> anyhow::Result<MangoAccountValue> {
let acc = self.fetch_raw(address)?;
Ok(MangoAccountValue::from_bytes(acc.data())
.with_context(|| format!("loading mango account {}", address))?)
}
// fetches via RPC, stores in ChainData, returns new version
pub fn fetch_fresh<T: anchor_lang::ZeroCopy + anchor_lang::Owner>(
&self,
@ -38,6 +45,11 @@ impl ChainDataAccountFetcher {
self.fetch(address)
}
pub fn fetch_fresh_mango_account(&self, address: &Pubkey) -> anyhow::Result<MangoAccountValue> {
self.refresh_account_via_rpc(address)?;
self.fetch_mango_account(address)
}
pub fn fetch_raw(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
let chain_data = self.chain_data.read().unwrap();
Ok(chain_data

View File

@ -4,7 +4,7 @@ use crate::ChainDataAccountFetcher;
use client::{AccountFetcher, MangoClient, MangoClientError, MangoGroupContext};
use mango_v4::state::{
new_health_cache, oracle_price, Bank, FixedOrderAccountRetriever, HealthCache, HealthType,
MangoAccount, TokenIndex,
MangoAccountValue, TokenIndex,
};
use {anyhow::Context, fixed::types::I80F48, solana_sdk::pubkey::Pubkey};
@ -12,10 +12,10 @@ use {anyhow::Context, fixed::types::I80F48, solana_sdk::pubkey::Pubkey};
pub fn new_health_cache_(
context: &MangoGroupContext,
account_fetcher: &ChainDataAccountFetcher,
account: &MangoAccount,
account: &MangoAccountValue,
) -> anyhow::Result<HealthCache> {
let active_token_len = account.tokens.iter_active().count();
let active_perp_len = account.perps.iter_active_accounts().count();
let active_token_len = account.token_iter_active().count();
let active_perp_len = account.perp_iter_active_accounts().count();
let metas = context.derive_health_check_remaining_account_metas(account, None, false)?;
let accounts = metas
@ -34,7 +34,7 @@ pub fn new_health_cache_(
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len,
};
new_health_cache(account, &retriever).context("make health cache")
new_health_cache(&account.borrow(), &retriever).context("make health cache")
}
#[allow(clippy::too_many_arguments)]
@ -47,7 +47,7 @@ pub fn process_account(
let min_health_ratio = I80F48::from_num(50.0f64);
let quote_token_index = 0;
let account = account_fetcher.fetch::<MangoAccount>(pubkey)?;
let account = account_fetcher.fetch_mango_account(pubkey)?;
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
.expect("always ok")
.health(HealthType::Maint);
@ -59,7 +59,7 @@ pub fn process_account(
log::trace!(
"possible candidate: {}, with owner: {}, maint health: {}, bankrupt: {}",
pubkey,
account.owner,
account.fixed.owner,
maint_health,
account.is_bankrupt(),
);
@ -67,15 +67,14 @@ pub fn process_account(
// Fetch a fresh account and re-compute
// This is -- unfortunately -- needed because the websocket streams seem to not
// be great at providing timely updates to the account data.
let account = account_fetcher.fetch_fresh::<MangoAccount>(pubkey)?;
let account = account_fetcher.fetch_fresh_mango_account(pubkey)?;
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
.expect("always ok")
.health(HealthType::Maint);
// find asset and liab tokens
let mut tokens = account
.tokens
.iter_active()
.token_iter_active()
.map(|token_position| {
let token = mango_client.context.token(token_position.token_index);
let bank = account_fetcher.fetch::<Bank>(&token.mint_info.first_bank())?;
@ -96,14 +95,14 @@ pub fn process_account(
let get_max_liab_transfer = |source, target| -> anyhow::Result<I80F48> {
let mut liqor = account_fetcher
.fetch_fresh::<MangoAccount>(&mango_client.mango_account_address)
.fetch_fresh_mango_account(&mango_client.mango_account_address)
.context("getting liquidator account")?
.clone();
// Ensure the tokens are activated, so they appear in the health cache and
// max_swap_source() will work.
liqor.tokens.get_mut_or_create(source)?;
liqor.tokens.get_mut_or_create(target)?;
liqor.token_get_mut_or_create(source)?;
liqor.token_get_mut_or_create(target)?;
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &liqor).expect("always ok");

View File

@ -161,9 +161,9 @@ async fn feed_snapshots(
})
.flat_map(|mango_account| {
mango_account
.serum3
.iter_active()
.serum3_iter_active()
.map(|serum3account| serum3account.open_orders)
.collect::<Vec<_>>()
})
.collect::<Vec<Pubkey>>();

View File

@ -1,7 +1,7 @@
use anchor_lang::Discriminator;
use arrayref::array_ref;
use mango_v4::state::{Bank, MangoAccount, MintInfo, PerpMarket};
use mango_v4::state::{Bank, MangoAccount, MangoAccountRefWithHeader, MintInfo, PerpMarket};
use solana_sdk::account::{AccountSharedData, ReadableAccount};
use solana_sdk::pubkey::Pubkey;
@ -10,7 +10,7 @@ pub fn is_mango_account<'a>(
account: &'a AccountSharedData,
program_id: &Pubkey,
group_id: &Pubkey,
) -> Option<&'a MangoAccount> {
) -> Option<MangoAccountRefWithHeader<'a>> {
let data = account.data();
if account.owner() != program_id || data.is_empty() {
return None;
@ -20,11 +20,9 @@ pub fn is_mango_account<'a>(
if disc_bytes != &MangoAccount::discriminator() {
return None;
}
if data.len() != 8 + std::mem::size_of::<MangoAccount>() {
return None;
}
let mango_account: &MangoAccount = bytemuck::try_from_bytes(&data[8..]).expect("always Ok");
if mango_account.group != *group_id {
let mango_account = MangoAccountRefWithHeader::from_bytes(&data[8..]).expect("always ok");
if mango_account.fixed.group != *group_id {
return None;
}
Some(mango_account)

View File

@ -2,6 +2,13 @@
set -e pipefail
ANCHOR_BRANCH=v0.25.0-mangov4
ANCHOR_FORK=$(cd ../anchor && git rev-parse --abbrev-ref HEAD)
if [ "$ANCHOR_FORK" != "$ANCHOR_BRANCH" ]; then
echo "Check out anchor fork at git@github.com:blockworks-foundation/anchor.git, and switch to branch $ANCHOR_BRANCH!"
exit 1;
fi
# rg m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD -l | xargs -I % sed -i '' 's/m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD/5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM/g' %;
WALLET_WITH_FUNDS=~/.config/solana/mango-devnet.json
@ -9,7 +16,7 @@ PROGRAM_ID=5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM
# TODO fix need for --skip-lint
# build program,
anchor build --skip-lint
cargo run --manifest-path ../anchor/cli/Cargo.toml build --skip-lint
# patch types, which we want in rust, but anchor client doesn't support
./idl-fixup.sh
@ -22,7 +29,7 @@ if [[ -z "${NO_DEPLOY}" ]]; then
solana --url https://mango.devnet.rpcpool.com program deploy --program-id 5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM -k ~/.config/solana/mango-devnet.json target/deploy/mango_v4.so
# publish idl
anchor idl upgrade --provider.cluster https://mango.devnet.rpcpool.com --provider.wallet $WALLET_WITH_FUNDS --filepath target/idl/mango_v4.json $PROGRAM_ID
cargo run --manifest-path ../anchor/cli/Cargo.toml idl upgrade --provider.cluster https://mango.devnet.rpcpool.com --provider.wallet $WALLET_WITH_FUNDS --filepath target/idl/mango_v4.json $PROGRAM_ID
else
echo "Skipping deployment..."
fi

View File

@ -21,8 +21,8 @@ client = ["solana-sdk"]
[dependencies]
# todo: when to fix, when to use caret? need a regular chore to bump dependencies
# note: possibly need init-if-needed feature
anchor-lang = { version = "0.25.0", features = [] }
anchor-spl = { version = "0.25.0", features = [] }
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anchor-spl = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
arrayref = "0.3.6"
bincode = "1.3.3"
bytemuck = "^1.7.2"

View File

@ -8,14 +8,8 @@ use crate::state::*;
pub struct AccountClose<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
// note: should never be the delegate
has_one = owner,
has_one = group,
close = sol_destination
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group, has_one = owner, close = sol_destination)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(mut)]
@ -28,23 +22,24 @@ pub struct AccountClose<'info> {
pub fn account_close(ctx: Context<AccountClose>) -> Result<()> {
let group = ctx.accounts.group.load()?;
// don't perform checks if group is just testing
if group.testing == 1 {
return Ok(());
}
{
let account = ctx.accounts.account.load_mut()?;
let account = ctx.accounts.account.load()?;
require!(!account.being_liquidated(), MangoError::SomeError);
require!(!account.is_bankrupt(), MangoError::SomeError);
require_eq!(account.delegate, Pubkey::default());
for ele in account.tokens.values {
require_eq!(ele.is_active(), false);
}
for ele in account.serum3.values {
require_eq!(ele.is_active(), false);
}
for ele in account.perps.accounts {
require_eq!(ele.is_active(), false);
// don't perform checks if group is just testing
if group.testing == 0 {
require!(!account.fixed.being_liquidated(), MangoError::SomeError);
require!(!account.fixed.is_bankrupt(), MangoError::SomeError);
require_eq!(account.fixed.delegate, Pubkey::default());
for ele in account.token_iter() {
require_eq!(ele.is_active(), false);
}
for ele in account.serum3_iter() {
require_eq!(ele.is_active(), false);
}
for ele in account.perp_iter() {
require_eq!(ele.is_active(), false);
}
}
}
Ok(())

View File

@ -5,7 +5,7 @@ use crate::state::*;
use crate::util::fill32_from_str;
#[derive(Accounts)]
#[instruction(account_num: u8)]
#[instruction(account_num: u8, account_size: AccountSize)]
pub struct AccountCreate<'info> {
pub group: AccountLoader<'info, Group>,
@ -14,9 +14,10 @@ pub struct AccountCreate<'info> {
seeds = [group.key().as_ref(), b"MangoAccount".as_ref(), owner.key().as_ref(), &account_num.to_le_bytes()],
bump,
payer = payer,
space = 8 + std::mem::size_of::<MangoAccount>(),
space = MangoAccount::space(account_size),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(mut)]
@ -25,20 +26,24 @@ pub struct AccountCreate<'info> {
pub system_program: Program<'info, System>,
}
pub fn account_create(ctx: Context<AccountCreate>, account_num: u8, name: String) -> Result<()> {
pub fn account_create(
ctx: Context<AccountCreate>,
account_num: u8,
account_size: AccountSize,
name: String,
) -> Result<()> {
let mut account = ctx.accounts.account.load_init()?;
account.name = fill32_from_str(name)?;
account.group = ctx.accounts.group.key();
account.owner = ctx.accounts.owner.key();
account.account_num = account_num;
account.bump = *ctx.bumps.get("account").ok_or(MangoError::SomeError)?;
account.delegate = Pubkey::default();
account.tokens = MangoAccountTokenPositions::default();
account.serum3 = MangoAccountSerum3Orders::default();
account.perps = MangoAccountPerpPositions::default();
account.set_being_liquidated(false);
account.set_bankrupt(false);
account.fixed.name = fill32_from_str(name)?;
account.fixed.group = ctx.accounts.group.key();
account.fixed.owner = ctx.accounts.owner.key();
account.fixed.account_num = account_num;
account.fixed.bump = *ctx.bumps.get("account").ok_or(MangoError::SomeError)?;
account.fixed.delegate = Pubkey::default();
account.fixed.set_being_liquidated(false);
account.fixed.set_bankrupt(false);
account.expand_dynamic_content(account_size)?;
Ok(())
}

View File

@ -8,13 +8,8 @@ use crate::util::fill32_from_str;
pub struct AccountEdit<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
// Note: should never be the delegate
has_one = owner,
has_one = group,
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group, has_one = owner)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
}
@ -31,13 +26,11 @@ pub fn account_edit(
let mut account = ctx.accounts.account.load_mut()?;
// msg!("old account {:#?}", account);
// note: unchanged fields are inline, and match exact definition in create_account
// please maintain, and don't remove, makes it easy to reason about which support modification by owner
if let Some(name) = name_opt {
account.name = fill32_from_str(name)?;
account.fixed.name = fill32_from_str(name)?;
}
// unchanged -
@ -46,7 +39,7 @@ pub fn account_edit(
// bump
if let Some(delegate) = delegate_opt {
account.delegate = delegate;
account.fixed.delegate = delegate;
}
// unchanged -
@ -56,7 +49,5 @@ pub fn account_edit(
// being_liquidated
// is_bankrupt
// msg!("new account {:#?}", account);
Ok(())
}

View File

@ -0,0 +1,58 @@
use anchor_lang::prelude::*;
use crate::state::*;
#[derive(Accounts)]
pub struct AccountExpand<'info> {
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = owner)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn account_expand(ctx: Context<AccountExpand>) -> Result<()> {
let account_size = {
let account = ctx.accounts.account.load()?;
account.size()
};
require_eq!(account_size, AccountSize::Small);
let new_space = MangoAccount::space(AccountSize::Large.try_into().unwrap());
let new_rent_minimum = Rent::get()?.minimum_balance(new_space);
let realloc_account = ctx.accounts.account.as_ref();
let old_space = realloc_account.data_len();
require_gt!(new_space, old_space);
// transfer required additional rent
anchor_lang::system_program::transfer(
anchor_lang::context::CpiContext::new(
ctx.accounts.system_program.to_account_info(),
anchor_lang::system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: realloc_account.clone(),
},
),
new_rent_minimum
.checked_sub(realloc_account.lamports())
.unwrap(),
)?;
// realloc
realloc_account.realloc(new_space, true)?;
// expand dynamic content, e.g. to grow token positions, we need to slide serum3orders further later, and so on....
let mut account = ctx.accounts.account.load_mut()?;
account.expand_dynamic_content(AccountSize::Large.try_into().unwrap())?;
Ok(())
}

View File

@ -5,23 +5,21 @@ use anchor_lang::prelude::*;
pub struct ComputeAccountData<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
has_one = group,
)]
pub account: AccountLoader<'info, MangoAccount>,
pub account: AccountLoaderDynamic<'info, MangoAccount>,
}
pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
let group_pk = ctx.accounts.group.key();
let account = ctx.accounts.account.load()?;
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group_pk)?;
let health_cache = new_health_cache(&account, &account_retriever)?;
let health_cache = new_health_cache(&account.borrow(), &account_retriever)?;
let init_health = health_cache.health(HealthType::Init);
let maint_health = health_cache.health(HealthType::Maint);
let equity = compute_equity(&account, &account_retriever)?;
let equity = compute_equity(&account.borrow(), &account_retriever)?;
emit!(MangoAccountData {
health_cache,

View File

@ -2,8 +2,8 @@ use crate::accounts_zerocopy::*;
use crate::error::MangoError;
use crate::logs::{MarginTradeLog, TokenBalanceLog};
use crate::state::{
compute_health, new_fixed_order_account_retriever, AccountRetriever, Bank, Group, HealthType,
MangoAccount,
compute_health, new_fixed_order_account_retriever, AccountLoaderDynamic, AccountRetriever,
Bank, Group, HealthType, MangoAccount, MangoAccountRefMut,
};
use crate::{group_seeds, Mango};
use anchor_lang::prelude::*;
@ -29,13 +29,8 @@ use std::collections::HashMap;
pub struct FlashLoan<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group, has_one = owner)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
pub token_program: Program<'info, Token>,
}
@ -85,7 +80,7 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
let group = ctx.accounts.group.load()?;
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Go over the banks passed as health accounts and:
// - Ensure that all banks that are passed in have activated positions.
@ -101,8 +96,8 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
for (i, ai) in health_ais.iter().enumerate() {
match ai.load::<Bank>() {
Ok(bank) => {
require!(bank.group == account.group, MangoError::SomeError);
let (_, raw_token_index, _) = account.tokens.get_mut_or_create(bank.token_index)?;
require!(bank.group == account.fixed.group, MangoError::SomeError);
let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?;
allowed_vaults.insert(bank.vault, (i, raw_token_index));
allowed_banks.insert(ai.key, bank);
}
@ -120,8 +115,8 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
// NOTE: This health check isn't strictly necessary. It will be, later, when
// we want to have reduce_only or be able to move an account out of bankruptcy.
{
let retriever = new_fixed_order_account_retriever(health_ais, &account)?;
let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?;
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
msg!("pre_cpi_health {:?}", pre_cpi_health);
}
@ -182,7 +177,7 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
// Store the indexed value before the margin trade for logging purposes
let mut pre_indexed_positions = Vec::new();
for (_, info) in used_vaults.iter() {
let position = account.tokens.get_raw(info.raw_token_index);
let position = account.token_get_raw(info.raw_token_index);
pre_indexed_positions.push(position.indexed_position.to_bits());
}
@ -212,7 +207,7 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
// if there are withdraws: figure out loan amount, mark as signer
if withdraw_amount > 0 {
let token_account = account.tokens.get_mut_raw(vault_info.raw_token_index);
let token_account = account.token_get_mut_raw(vault_info.raw_token_index);
let native_position = token_account.native(&bank);
vault_info.loan_amount = if native_position > 0 {
(I80F48::from(withdraw_amount) - native_position).max(I80F48::ZERO)
@ -323,12 +318,16 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
// Track vault changes and apply them to the user's token positions
let mut account = ctx.accounts.account.load_mut()?;
let inactive_tokens =
adjust_for_post_cpi_vault_amounts(health_ais, all_cpi_ais, &used_vaults, &mut account)?;
let inactive_tokens = adjust_for_post_cpi_vault_amounts(
health_ais,
all_cpi_ais,
&used_vaults,
&mut account.borrow_mut(),
)?;
// Check post-cpi health
let retriever = new_fixed_order_account_retriever(health_ais, &account)?;
let post_cpi_health = compute_health(&account, HealthType::Init, &retriever)?;
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let post_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
require!(post_cpi_health >= 0, MangoError::HealthMustBePositive);
msg!("post_cpi_health {:?}", post_cpi_health);
@ -336,7 +335,7 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
let mut token_indexes = Vec::with_capacity(used_vaults.len());
let mut post_indexed_positions = Vec::with_capacity(used_vaults.len());
for (_, info) in used_vaults.iter() {
let position = account.tokens.get_raw(info.raw_token_index);
let position = account.token_get_raw(info.raw_token_index);
post_indexed_positions.push(position.indexed_position.to_bits());
token_indexes.push(position.token_index as u16);
@ -365,7 +364,7 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
// Deactivate inactive token accounts at the end
for raw_token_index in inactive_tokens {
account.tokens.deactivate(raw_token_index);
account.token_deactivate(raw_token_index);
}
Ok(())
@ -375,13 +374,13 @@ fn adjust_for_post_cpi_vault_amounts(
health_ais: &[AccountInfo],
cpi_ais: &[AccountInfo],
used_vaults: &HashMap<&Pubkey, AllowedVault>,
account: &mut MangoAccount,
account: &mut MangoAccountRefMut,
) -> Result<Vec<usize>> {
let mut inactive_token_raw_indexes = Vec::with_capacity(used_vaults.len());
for (_, info) in used_vaults.iter() {
let vault = Account::<TokenAccount>::try_from(&cpi_ais[info.vault_cpi_ai_index]).unwrap();
let mut bank = health_ais[info.bank_health_ai_index].load_mut::<Bank>()?;
let position = account.tokens.get_mut_raw(info.raw_token_index);
let position = account.token_get_mut_raw(info.raw_token_index);
let loan_origination_fee = info.loan_amount * bank.loan_origination_fee_rate;
bank.collected_fees_native += loan_origination_fee;

View File

@ -4,7 +4,7 @@ use crate::group_seeds;
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
use crate::state::{
compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever,
AccountRetriever, Bank, Group, HealthType, MangoAccount, TokenIndex,
AccountLoaderDynamic, AccountRetriever, Bank, Group, HealthType, MangoAccount, TokenIndex,
};
use crate::util::checked_math as cm;
use anchor_lang::prelude::*;
@ -35,12 +35,8 @@ pub struct FlashLoan2Begin<'info> {
#[derive(Accounts)]
pub struct FlashLoan2End<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group, has_one = owner)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
pub token_program: Program<'info, Token>,
@ -162,8 +158,8 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
let group_seeds = group_seeds!(group);
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Find index at which vaults start
let vaults_index = ctx
.remaining_accounts
@ -174,7 +170,7 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
return false;
}
maybe_token_account.unwrap().owner == account.group
maybe_token_account.unwrap().owner == account.fixed.group
})
.ok_or_else(|| error!(MangoError::SomeError))?;
@ -213,7 +209,7 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
require_neq!(bank.flash_loan_vault_initial, u64::MAX);
// Create the token position now, so we can compute the pre-health with fixed order health accounts
let (_, raw_token_index, _) = account.tokens.get_mut_or_create(bank.token_index)?;
let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?;
// Revoke delegation
let ix = token::spl_token::instruction::revoke(
@ -246,8 +242,8 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
// Check pre-cpi health
// NOTE: This health check isn't strictly necessary. It will be, later, when
// we want to have reduce_only or be able to move an account out of bankruptcy.
let retriever = new_fixed_order_account_retriever(health_ais, &account)?;
let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?;
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
msg!("pre_cpi_health {:?}", pre_cpi_health);
@ -255,7 +251,7 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
let mut prices = vec![];
for change in &changes {
let (_, oracle_price) = retriever.bank_and_oracle(
&account.group,
&account.fixed.group,
change.bank_index,
change.raw_token_index as TokenIndex,
)?;
@ -270,7 +266,7 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
let mut token_loan_details = Vec::with_capacity(changes.len());
for (change, price) in changes.iter().zip(prices.iter()) {
let mut bank = health_ais[change.bank_index].load_mut::<Bank>()?;
let position = account.tokens.get_mut_raw(change.raw_token_index);
let position = account.token_get_mut_raw(change.raw_token_index);
let native = position.native(&bank);
let approved_amount = I80F48::from(bank.flash_loan_approved_amount);
@ -314,18 +310,18 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
emit!(FlashLoanLog {
mango_account: ctx.accounts.account.key(),
token_loan_details: token_loan_details
token_loan_details
});
// Check post-cpi health
let post_cpi_health =
compute_health_from_fixed_accounts(&account, HealthType::Init, health_ais)?;
compute_health_from_fixed_accounts(&account.borrow(), HealthType::Init, health_ais)?;
require!(post_cpi_health >= 0, MangoError::HealthMustBePositive);
msg!("post_cpi_health {:?}", post_cpi_health);
// Deactivate inactive token accounts after health check
for raw_token_index in deactivated_token_positions {
account.tokens.deactivate(raw_token_index);
account.token_deactivate(raw_token_index);
}
Ok(())

View File

@ -2,9 +2,10 @@ use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::group_seeds;
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
use crate::state::MangoAccount;
use crate::state::{
compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever,
AccountRetriever, Bank, Group, HealthType, MangoAccount, TokenIndex,
AccountLoaderDynamic, AccountRetriever, Bank, Group, HealthType, TokenIndex,
};
use crate::util::checked_math as cm;
use anchor_lang::prelude::*;
@ -38,11 +39,8 @@ pub struct FlashLoan3Begin<'info> {
/// the `owner` must have authority to transfer tokens out of them
#[derive(Accounts)]
pub struct FlashLoan3End<'info> {
#[account(
mut,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = owner)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
pub token_program: Program<'info, Token>,
@ -179,7 +177,8 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan3End<'info>>,
) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Find index at which vaults start
let vaults_index = ctx
@ -191,7 +190,7 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
return false;
}
maybe_token_account.unwrap().owner == account.group
maybe_token_account.unwrap().owner == account.fixed.group
})
.ok_or_else(|| error_msg!("expected at least one vault token account to be passed"))?;
let vaults_len = (ctx.remaining_accounts.len() - vaults_index) / 2;
@ -234,7 +233,7 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
require_neq!(bank.flash_loan_vault_initial, u64::MAX);
// Create the token position now, so we can compute the pre-health with fixed order health accounts
let (_, raw_token_index, _) = account.tokens.get_mut_or_create(bank.token_index)?;
let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?;
// Transfer any excess over the inital balance of the token account back
// into the vault. Compute the total change in the vault balance.
@ -276,16 +275,19 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
// Check pre-cpi health
// NOTE: This health check isn't strictly necessary. It will be, later, when
// we want to have reduce_only or be able to move an account out of bankruptcy.
let retriever = new_fixed_order_account_retriever(health_ais, &account)?;
let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?;
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
msg!("pre_cpi_health {:?}", pre_cpi_health);
// Prices for logging
let mut prices = vec![];
for change in &changes {
let (_, oracle_price) =
retriever.bank_and_oracle(&account.group, change.bank_index, change.token_index)?;
let (_, oracle_price) = retriever.bank_and_oracle(
&account.fixed.group,
change.bank_index,
change.token_index,
)?;
prices.push(oracle_price);
}
@ -297,7 +299,7 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
let mut token_loan_details = Vec::with_capacity(changes.len());
for (change, price) in changes.iter().zip(prices.iter()) {
let mut bank = health_ais[change.bank_index].load_mut::<Bank>()?;
let position = account.tokens.get_mut_raw(change.raw_token_index);
let position = account.token_get_mut_raw(change.raw_token_index);
let native = position.native(&bank);
let approved_amount = I80F48::from(bank.flash_loan_approved_amount);
@ -341,18 +343,18 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
emit!(FlashLoanLog {
mango_account: ctx.accounts.account.key(),
token_loan_details: token_loan_details
token_loan_details
});
// Check post-cpi health
let post_cpi_health =
compute_health_from_fixed_accounts(&account, HealthType::Init, health_ais)?;
compute_health_from_fixed_accounts(&account.borrow(), HealthType::Init, health_ais)?;
require!(post_cpi_health >= 0, MangoError::HealthMustBePositive);
msg!("post_cpi_health {:?}", post_cpi_health);
// Deactivate inactive token accounts after health check
for raw_token_index in deactivated_token_positions {
account.tokens.deactivate(raw_token_index);
account.token_deactivate(raw_token_index);
}
Ok(())

View File

@ -21,19 +21,12 @@ pub struct LiqTokenBankruptcy<'info> {
)]
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = liqor.load()?.is_owner_or_delegate(liqor_owner.key()),
)]
pub liqor: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
pub liqor_owner: Signer<'info>,
#[account(
mut,
has_one = group,
)]
pub liqee: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
#[account(
has_one = group,
@ -80,14 +73,20 @@ pub fn liq_token_bankruptcy(
);
let mut liqor = ctx.accounts.liqor.load_mut()?;
require!(!liqor.is_bankrupt(), MangoError::IsBankrupt);
require!(
liqor
.fixed
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
MangoError::SomeError
);
require!(!liqor.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut liqee = ctx.accounts.liqee.load_mut()?;
require!(liqee.is_bankrupt(), MangoError::IsNotBankrupt);
require!(liqee.fixed.is_bankrupt(), MangoError::IsBankrupt);
let liab_bank = bank_ais[0].load::<Bank>()?;
let liab_deposit_index = liab_bank.deposit_index;
let (liqee_liab, liqee_raw_token_index) = liqee.tokens.get_mut(liab_token_index)?;
let (liqee_liab, liqee_raw_token_index) = liqee.token_get_mut(liab_token_index)?;
let mut remaining_liab_loss = -liqee_liab.native(&liab_bank);
require_gt!(remaining_liab_loss, I80F48::ZERO);
drop(liab_bank);
@ -140,23 +139,24 @@ pub fn liq_token_bankruptcy(
// credit the liqor
let (liqor_quote, liqor_quote_raw_token_index, _) =
liqor.tokens.get_mut_or_create(QUOTE_TOKEN_INDEX)?;
liqor.token_get_mut_or_create(QUOTE_TOKEN_INDEX)?;
let liqor_quote_active = quote_bank.deposit(liqor_quote, insurance_transfer_i80f48)?;
// transfer liab from liqee to liqor
let (liqor_liab, liqor_liab_raw_token_index, _) =
liqor.tokens.get_mut_or_create(liab_token_index)?;
liqor.token_get_mut_or_create(liab_token_index)?;
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab, liab_transfer)?;
// Check liqor's health
let liqor_health = compute_health(&liqor, HealthType::Init, &account_retriever)?;
let liqor_health =
compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
if !liqor_quote_active {
liqor.tokens.deactivate(liqor_quote_raw_token_index);
liqor.token_deactivate(liqor_quote_raw_token_index);
}
if !liqor_liab_active {
liqor.tokens.deactivate(liqor_liab_raw_token_index);
liqor.token_deactivate(liqor_liab_raw_token_index);
}
} else {
// For liab_token_index == QUOTE_TOKEN_INDEX: the insurance fund deposits directly into liqee,
@ -207,14 +207,14 @@ pub fn liq_token_bankruptcy(
// If the account has no more borrows then it's no longer bankrupt
// and should (always?) no longer be liquidated.
let account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
let liqee_health_cache = new_health_cache(&liqee, &account_retriever)?;
liqee.set_bankrupt(liqee_health_cache.has_borrows());
let liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)?;
liqee.fixed.set_bankrupt(liqee_health_cache.has_borrows());
if !liqee.is_bankrupt() && liqee_health_cache.health(HealthType::Init) >= 0 {
liqee.set_being_liquidated(false);
liqee.fixed.set_being_liquidated(false);
}
if !liqee_liab_active {
liqee.tokens.deactivate(liqee_raw_token_index);
liqee.token_deactivate(liqee_raw_token_index);
}
Ok(())

View File

@ -12,19 +12,12 @@ use crate::util::checked_math as cm;
pub struct LiqTokenWithToken<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = liqor.load()?.is_owner_or_delegate(liqor_owner.key()),
)]
pub liqor: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
pub liqor_owner: Signer<'info>,
#[account(
mut,
has_one = group,
)]
pub liqee: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
}
pub fn liq_token_with_token(
@ -40,30 +33,34 @@ pub fn liq_token_with_token(
.context("create account retriever")?;
let mut liqor = ctx.accounts.liqor.load_mut()?;
require!(!liqor.is_bankrupt(), MangoError::IsBankrupt);
require!(
liqor
.fixed
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
MangoError::SomeError
);
require!(!liqor.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut liqee = ctx.accounts.liqee.load_mut()?;
require!(!liqee.is_bankrupt(), MangoError::IsBankrupt);
require!(!liqee.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Initial liqee health check
let mut liqee_health_cache =
new_health_cache(&liqee, &account_retriever).context("create liqee health cache")?;
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
.context("create liqee health cache")?;
let init_health = liqee_health_cache.health(HealthType::Init);
msg!("init health: {}", init_health);
if liqee.being_liquidated() {
if init_health > I80F48::ZERO {
liqee.set_being_liquidated(false);
liqee.fixed.set_being_liquidated(false);
msg!("Liqee init_health above zero");
return Ok(());
}
} else {
let maint_health = liqee_health_cache.health(HealthType::Maint);
msg!("maint health: {}", maint_health);
require!(
maint_health < I80F48::ZERO,
MangoError::HealthMustBeNegative
);
liqee.set_being_liquidated(true);
liqee.fixed.set_being_liquidated(true);
}
//
@ -81,11 +78,11 @@ pub fn liq_token_with_token(
// The main complication here is that we can't keep the liqee_asset_position and liqee_liab_position
// borrows alive at the same time. Possibly adding get_mut_pair() would be helpful.
let (liqee_asset_position, liqee_asset_raw_index) = liqee.tokens.get(asset_token_index)?;
let (liqee_asset_position, liqee_asset_raw_index) = liqee.token_get(asset_token_index)?;
let liqee_assets_native = liqee_asset_position.native(&asset_bank);
require!(liqee_assets_native.is_positive(), MangoError::SomeError);
let (liqee_liab_position, liqee_liab_raw_index) = liqee.tokens.get(liab_token_index)?;
let (liqee_liab_position, liqee_liab_raw_index) = liqee.token_get(liab_token_index)?;
let liqee_liab_native = liqee_liab_position.native(&liab_bank);
require!(liqee_liab_native.is_negative(), MangoError::SomeError);
@ -124,21 +121,21 @@ pub fn liq_token_with_token(
let asset_transfer = cm!(liab_transfer * liab_price_adjusted / asset_price);
// Apply the balance changes to the liqor and liqee accounts
let liqee_liab_position = liqee.tokens.get_mut_raw(liqee_liab_raw_index);
let liqee_liab_position = liqee.token_get_mut_raw(liqee_liab_raw_index);
let liqee_liab_active = liab_bank.deposit(liqee_liab_position, liab_transfer)?;
let liqee_liab_position_indexed = liqee_liab_position.indexed_position;
let (liqor_liab_position, liqor_liab_raw_index, _) =
liqor.tokens.get_mut_or_create(liab_token_index)?;
liqor.token_get_mut_or_create(liab_token_index)?;
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
let liqor_liab_position_indexed = liqor_liab_position.indexed_position;
let (liqor_asset_position, liqor_asset_raw_index, _) =
liqor.tokens.get_mut_or_create(asset_token_index)?;
liqor.token_get_mut_or_create(asset_token_index)?;
let liqor_asset_active = asset_bank.deposit(liqor_asset_position, asset_transfer)?;
let liqor_asset_position_indexed = liqor_asset_position.indexed_position;
let liqee_asset_position = liqee.tokens.get_mut_raw(liqee_asset_raw_index);
let liqee_asset_position = liqee.token_get_mut_raw(liqee_asset_raw_index);
let liqee_asset_active =
asset_bank.withdraw_without_fee(liqee_asset_position, asset_transfer)?;
let liqee_asset_position_indexed = liqee_asset_position.indexed_position;
@ -156,8 +153,8 @@ pub fn liq_token_with_token(
emit!(LiquidateTokenAndTokenLog {
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
asset_token_index: asset_token_index,
liab_token_index: liab_token_index,
asset_token_index,
liab_token_index,
asset_transfer: asset_transfer.to_bits(),
liab_transfer: liab_transfer.to_bits(),
asset_price: asset_price.to_bits(),
@ -204,33 +201,35 @@ pub fn liq_token_with_token(
// Since we use a scanning account retriever, it's safe to deactivate inactive token positions
if !liqee_asset_active {
liqee.tokens.deactivate(liqee_asset_raw_index);
liqee.token_deactivate(liqee_asset_raw_index);
}
if !liqee_liab_active {
liqee.tokens.deactivate(liqee_liab_raw_index);
liqee.token_deactivate(liqee_liab_raw_index);
}
if !liqor_asset_active {
liqor.tokens.deactivate(liqor_asset_raw_index);
liqor.token_deactivate(liqor_asset_raw_index);
}
if !liqor_liab_active {
liqor.tokens.deactivate(liqor_liab_raw_index)
liqor.token_deactivate(liqor_liab_raw_index)
}
}
// Check liqee health again
let maint_health = liqee_health_cache.health(HealthType::Maint);
if maint_health < I80F48::ZERO {
liqee.set_bankrupt(!liqee_health_cache.has_liquidatable_assets());
liqee
.fixed
.set_bankrupt(!liqee_health_cache.has_liquidatable_assets());
} else {
let init_health = liqee_health_cache.health(HealthType::Init);
// this is equivalent to one native USDC or 1e-6 USDC
// This is used as threshold to flip flag instead of 0 because of dust issues
liqee.set_being_liquidated(init_health < -I80F48::ONE);
liqee.fixed.set_being_liquidated(init_health < -I80F48::ONE);
}
// Check liqor's health
let liqor_health = compute_health(&liqor, HealthType::Init, &account_retriever)
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)
.context("compute liqor health")?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive);

View File

@ -1,6 +1,7 @@
pub use account_close::*;
pub use account_create::*;
pub use account_edit::*;
pub use account_expand::*;
pub use benchmark::*;
pub use compute_account_data::*;
pub use flash_loan::*;
@ -43,6 +44,7 @@ pub use token_withdraw::*;
mod account_close;
mod account_create;
mod account_edit;
mod account_expand;
mod benchmark;
mod compute_account_data;
mod flash_loan;

View File

@ -1,18 +1,14 @@
use anchor_lang::prelude::*;
use crate::error::MangoError;
use crate::state::{Book, BookSide, Group, MangoAccount, PerpMarket};
use crate::state::{AccountLoaderDynamic, Book, BookSide, Group, MangoAccount, PerpMarket};
#[derive(Accounts)]
pub struct PerpCancelAllOrders<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
@ -29,15 +25,20 @@ pub struct PerpCancelAllOrders<'info> {
}
pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> {
let mut mango_account = ctx.accounts.account.load_mut()?;
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
let mut account = ctx.accounts.account.load_mut()?;
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;
let mut book = Book::new(bids, asks);
book.cancel_all_order(&mut mango_account, &mut perp_market, limit, None)?;
book.cancel_all_order(&mut account.borrow_mut(), &mut perp_market, limit, None)?;
Ok(())
}

View File

@ -1,18 +1,14 @@
use anchor_lang::prelude::*;
use crate::error::MangoError;
use crate::state::{Book, BookSide, Group, MangoAccount, PerpMarket, Side};
use crate::state::{AccountLoaderDynamic, Book, BookSide, Group, MangoAccount, PerpMarket, Side};
#[derive(Accounts)]
pub struct PerpCancelAllOrdersBySide<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
@ -33,15 +29,26 @@ pub fn perp_cancel_all_orders_by_side(
side_option: Option<Side>,
limit: u8,
) -> Result<()> {
let mut mango_account = ctx.accounts.account.load_mut()?;
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
let mut account = ctx.accounts.account.load_mut()?;
require_keys_eq!(account.fixed.group, ctx.accounts.group.key());
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;
let mut book = Book::new(bids, asks);
book.cancel_all_order(&mut mango_account, &mut perp_market, limit, side_option)?;
book.cancel_all_order(
&mut account.borrow_mut(),
&mut perp_market,
limit,
side_option,
)?;
Ok(())
}

View File

@ -1,18 +1,14 @@
use anchor_lang::prelude::*;
use crate::error::MangoError;
use crate::state::{Book, BookSide, Group, MangoAccount, PerpMarket};
use crate::state::{AccountLoaderDynamic, Book, BookSide, Group, MangoAccount, PerpMarket};
#[derive(Accounts)]
pub struct PerpCancelOrder<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
@ -29,17 +25,21 @@ pub struct PerpCancelOrder<'info> {
}
pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: i128) -> Result<()> {
let mut mango_account = ctx.accounts.account.load_mut()?;
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
let mut account = ctx.accounts.account.load_mut()?;
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;
let mut book = Book::new(bids, asks);
let side = mango_account
.perps
.find_order_side(perp_market.perp_market_index, order_id)
let side = account
.perp_find_order_side(perp_market.perp_market_index, order_id)
.ok_or_else(|| error!(MangoError::SomeError))?; // InvalidOrderId
let order = book.cancel_order(order_id, side)?;
@ -48,7 +48,5 @@ pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: i128) -> Resul
MangoError::SomeError // InvalidOwner
);
mango_account
.perps
.remove_order(order.owner_slot as usize, order.quantity)
account.perp_remove_order(order.owner_slot as usize, order.quantity)
}

View File

@ -1,18 +1,14 @@
use anchor_lang::prelude::*;
use crate::error::MangoError;
use crate::state::{Book, BookSide, Group, MangoAccount, PerpMarket};
use crate::state::{AccountLoaderDynamic, Book, BookSide, Group, MangoAccount, PerpMarket};
#[derive(Accounts)]
pub struct PerpCancelOrderByClientOrderId<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
@ -32,17 +28,21 @@ pub fn perp_cancel_order_by_client_order_id(
ctx: Context<PerpCancelOrderByClientOrderId>,
client_order_id: u64,
) -> Result<()> {
let mut mango_account = ctx.accounts.account.load_mut()?;
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
let mut account = ctx.accounts.account.load_mut()?;
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;
let mut book = Book::new(bids, asks);
let (order_id, side) = mango_account
.perps
.find_order_with_client_order_id(perp_market.perp_market_index, client_order_id)
let (order_id, side) = account
.perp_find_order_with_client_order_id(perp_market.perp_market_index, client_order_id)
.ok_or_else(|| error!(MangoError::SomeError))?;
let order = book.cancel_order(order_id, side)?;
@ -51,7 +51,5 @@ pub fn perp_cancel_order_by_client_order_id(
MangoError::SomeError // InvalidOwner
);
mango_account
.perps
.remove_order(order.owner_slot as usize, order.quantity)
account.perp_remove_order(order.owner_slot as usize, order.quantity)
}

View File

@ -1,10 +1,9 @@
use anchor_lang::prelude::*;
use bytemuck::cast_ref;
use crate::accounts_zerocopy::*;
use crate::error::MangoError;
use crate::state::EventQueue;
use crate::state::{EventType, FillEvent, Group, MangoAccount, OutEvent, PerpMarket};
use crate::state::{AccountLoaderDynamic, EventQueue, MangoAccount};
use crate::state::{EventType, FillEvent, Group, OutEvent, PerpMarket};
use crate::logs::{emit_perp_balances, FillLog};
@ -41,73 +40,88 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
// handle self trade separately because of rust borrow checker
if fill.maker == fill.taker {
let mut ma = match mango_account_ais.iter().find(|ai| ai.key == &fill.maker) {
match mango_account_ais.iter().find(|ai| ai.key == &fill.maker) {
None => {
msg!("Unable to find account {}", fill.maker.to_string());
return Ok(());
}
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
};
ma.perps.execute_maker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
ma.perps.execute_taker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
emit_perp_balances(
fill.maker,
perp_market.perp_market_index as u64,
fill.price,
&ma.perps.accounts[perp_market.perp_market_index as usize],
&perp_market,
);
Some(ai) => {
let mal: AccountLoaderDynamic<MangoAccount> =
AccountLoaderDynamic::try_from(&ai)?;
let mut ma = mal.load_mut()?;
ma.perp_execute_maker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
ma.perp_execute_taker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
emit_perp_balances(
fill.maker,
perp_market.perp_market_index as u64,
fill.price,
&ma.perp_find_account(perp_market.perp_market_index).unwrap(),
&perp_market,
);
}
};
} else {
let mut maker = match mango_account_ais.iter().find(|ai| ai.key == &fill.maker)
{
match mango_account_ais.iter().find(|ai| ai.key == &fill.maker) {
None => {
msg!("Unable to find maker account {}", fill.maker.to_string());
return Ok(());
}
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
};
let mut taker = match mango_account_ais.iter().find(|ai| ai.key == &fill.taker)
{
None => {
msg!("Unable to find taker account {}", fill.taker.to_string());
return Ok(());
}
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
};
Some(ai) => {
let mal: AccountLoaderDynamic<MangoAccount> =
AccountLoaderDynamic::try_from(&ai)?;
let mut maker = mal.load_mut()?;
maker.perps.execute_maker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
taker.perps.execute_taker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
emit_perp_balances(
fill.maker,
perp_market.perp_market_index as u64,
fill.price,
&maker.perps.accounts[perp_market.perp_market_index as usize],
&perp_market,
);
emit_perp_balances(
fill.taker,
perp_market.perp_market_index as u64,
fill.price,
&taker.perps.accounts[perp_market.perp_market_index as usize],
&perp_market,
);
match mango_account_ais.iter().find(|ai| ai.key == &fill.taker) {
None => {
msg!("Unable to find taker account {}", fill.taker.to_string());
return Ok(());
}
Some(ai) => {
let mal: AccountLoaderDynamic<MangoAccount> =
AccountLoaderDynamic::try_from(&ai)?;
let mut taker = mal.load_mut()?;
maker.perp_execute_maker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
taker.perp_execute_taker(
perp_market.perp_market_index,
&mut perp_market,
fill,
)?;
emit_perp_balances(
fill.maker,
perp_market.perp_market_index as u64,
fill.price,
&maker
.perp_find_account(perp_market.perp_market_index)
.unwrap(),
&perp_market,
);
emit_perp_balances(
fill.taker,
perp_market.perp_market_index as u64,
fill.price,
&taker
.perp_find_account(perp_market.perp_market_index)
.unwrap(),
&perp_market,
);
}
};
}
};
}
emit!(FillLog {
mango_group: ctx.accounts.group.key(),
@ -134,16 +148,19 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
EventType::Out => {
let out: &OutEvent = cast_ref(event);
let mut ma = match mango_account_ais.iter().find(|ai| ai.key == &out.owner) {
match mango_account_ais.iter().find(|ai| ai.key == &out.owner) {
None => {
msg!("Unable to find account {}", out.owner.to_string());
return Ok(());
}
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
};
Some(ai) => {
let mal: AccountLoaderDynamic<MangoAccount> =
AccountLoaderDynamic::try_from(&ai)?;
let mut ma = mal.load_mut()?;
ma.perps
.remove_order(out.owner_slot as usize, out.quantity)?;
ma.perp_remove_order(out.owner_slot as usize, out.quantity)?;
}
};
}
EventType::Liquidate => {
// This is purely for record keeping. Can be removed if program logs are superior

View File

@ -2,21 +2,18 @@ use anchor_lang::prelude::*;
use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::state::MangoAccount;
use crate::state::{
compute_health, new_fixed_order_account_retriever, oracle_price, Book, BookSide, EventQueue,
Group, HealthType, MangoAccount, OrderType, PerpMarket, Side,
compute_health, new_fixed_order_account_retriever, oracle_price, AccountLoaderDynamic, Book,
BookSide, EventQueue, Group, HealthType, OrderType, PerpMarket, Side,
};
#[derive(Accounts)]
pub struct PerpPlaceOrder<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
@ -78,9 +75,14 @@ pub fn perp_place_order(
// When the limit is reached, processing stops and the instruction succeeds.
limit: u8,
) -> Result<()> {
let mut mango_account = ctx.accounts.account.load_mut()?;
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
let mango_account_pk = ctx.accounts.account.key();
let mut account = ctx.accounts.account.load_mut()?;
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let account_pk = ctx.accounts.account.key();
{
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
@ -118,8 +120,8 @@ pub fn perp_place_order(
&mut perp_market,
&mut event_queue,
oracle_price,
&mut mango_account.perps,
&mango_account_pk,
&mut account.borrow_mut(),
&account_pk,
price_lots,
max_base_lots,
max_quote_lots,
@ -131,8 +133,8 @@ pub fn perp_place_order(
)?;
}
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &mango_account)?;
let health = compute_health(&mango_account, HealthType::Init, &retriever)?;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
msg!("health: {}", health);
require!(health >= 0, MangoError::HealthMustBePositive);

View File

@ -7,12 +7,8 @@ use crate::state::*;
pub struct Serum3CancelAllOrders<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(mut)]
@ -50,15 +46,19 @@ pub fn serum3_cancel_all_orders(ctx: Context<Serum3CancelAllOrders>, limit: u8)
//
{
let account = ctx.accounts.account.load()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders
require!(
account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.ok_or_else(|| error!(MangoError::SomeError))?
.open_orders
== ctx.accounts.open_orders.key(),

View File

@ -14,12 +14,8 @@ use checked_math as cm;
pub struct Serum3CancelOrder<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(mut)]
@ -63,13 +59,17 @@ pub fn serum3_cancel_order(
//
{
let account = ctx.accounts.account.load()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(
account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.ok_or_else(|| error!(MangoError::SomeError))?
.open_orders
== ctx.accounts.open_orders.key(),
@ -96,7 +96,7 @@ pub fn serum3_cancel_order(
let mut account = ctx.accounts.account.load_mut()?;
decrease_maybe_loan(
serum_market.market_index,
&mut account,
&mut account.borrow_mut(),
&before_oo,
&after_oo,
);
@ -109,11 +109,11 @@ pub fn serum3_cancel_order(
// the cached
pub fn decrease_maybe_loan(
market_index: Serum3MarketIndex,
account: &mut MangoAccount,
account: &mut MangoAccountRefMut,
before_oo: &OpenOrdersSlim,
after_oo: &OpenOrdersSlim,
) {
let serum3_account = account.serum3.find_mut(market_index).unwrap();
let serum3_account = account.serum3_find_mut(market_index).unwrap();
if after_oo.native_coin_free > before_oo.native_coin_free {
let native_coin_free_increase = after_oo.native_coin_free - before_oo.native_coin_free;

View File

@ -7,12 +7,8 @@ use crate::state::*;
pub struct Serum3CloseOpenOrders<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
@ -40,13 +36,19 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
// Validation
//
let mut account = ctx.accounts.account.load_mut()?;
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(
account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.ok_or_else(|| error!(MangoError::SomeError))?
.open_orders
== ctx.accounts.open_orders.key(),
@ -59,7 +61,7 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
cpi_close_open_orders(ctx.accounts)?;
// TODO: decrement in_use_count on the base token and quote token
account.serum3.deactivate(serum_market.market_index)?;
account.serum3_deactivate(serum_market.market_index)?;
Ok(())
}

View File

@ -7,12 +7,8 @@ use crate::state::*;
pub struct Serum3CreateOpenOrders<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
@ -50,9 +46,16 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
cpi_init_open_orders(ctx.accounts)?;
let serum_market = ctx.accounts.serum_market.load()?;
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
let serum_account = account.serum3.create(serum_market.market_index)?;
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_account = account.serum3_create(serum_market.market_index)?;
serum_account.open_orders = ctx.accounts.open_orders.key();
serum_account.base_token_index = serum_market.base_token_index;
serum_account.quote_token_index = serum_market.quote_token_index;
@ -60,13 +63,9 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
// Make it so that the token_account_map for the base and quote currency
// stay permanently blocked. Otherwise users may end up in situations where
// they can't settle a market because they don't have free token_account_map!
let (quote_position, _, _) = account
.tokens
.get_mut_or_create(serum_market.quote_token_index)?;
let (quote_position, _, _) = account.token_get_mut_or_create(serum_market.quote_token_index)?;
quote_position.in_use_count += 1;
let (base_position, _, _) = account
.tokens
.get_mut_or_create(serum_market.base_token_index)?;
let (base_position, _, _) = account.token_get_mut_or_create(serum_market.base_token_index)?;
base_position.in_use_count += 1;
Ok(())

View File

@ -9,11 +9,8 @@ use crate::state::*;
pub struct Serum3LiqForceCancelOrders<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
#[account(mut)]
/// CHECK: Validated inline by checking against the pubkey stored in the account
@ -73,14 +70,13 @@ pub fn serum3_liq_force_cancel_orders(
//
{
let account = ctx.accounts.account.load()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders
require!(
account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.ok_or_else(|| error!(MangoError::SomeError))?
.open_orders
== ctx.accounts.open_orders.key(),
@ -112,8 +108,9 @@ pub fn serum3_liq_force_cancel_orders(
{
let account = ctx.accounts.account.load()?;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
let health = compute_health(&account, HealthType::Maint, &retriever)?;
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health = compute_health(&account.borrow(), HealthType::Maint, &retriever)?;
msg!("health: {}", health);
require!(health < 0, MangoError::SomeError);
}
@ -143,7 +140,7 @@ pub fn serum3_liq_force_cancel_orders(
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference(
&mut account,
&mut account.borrow_mut(),
&mut base_bank,
after_base_vault,
before_base_vault,
@ -151,7 +148,7 @@ pub fn serum3_liq_force_cancel_orders(
after_quote_vault,
before_quote_vault,
)?
.deactivate_inactive_token_accounts(&mut account);
.deactivate_inactive_token_accounts(&mut account.borrow_mut());
Ok(())
}

View File

@ -83,12 +83,8 @@ pub enum Serum3Side {
pub struct Serum3PlaceOrder<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(mut)]
@ -168,13 +164,16 @@ pub fn serum3_place_order(
//
{
let account = ctx.accounts.account.load()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(
account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.ok_or_else(|| error!(MangoError::SomeError))?
.open_orders
== ctx.accounts.open_orders.key(),
@ -257,7 +256,7 @@ pub fn serum3_place_order(
let mut account = ctx.accounts.account.load_mut()?;
inc_maybe_loan(
serum_market.market_index,
&mut account,
&mut account.borrow_mut(),
&before_oo,
&after_oo,
);
@ -279,7 +278,7 @@ pub fn serum3_place_order(
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference(
&mut account,
&mut account.borrow_mut(),
&mut base_bank,
after_base_vault,
before_base_vault,
@ -292,12 +291,12 @@ pub fn serum3_place_order(
//
// Health check
//
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
let health = compute_health(&account, HealthType::Init, &retriever)?;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
msg!("health: {}", health);
require!(health >= 0, MangoError::HealthMustBePositive);
vault_difference_result.deactivate_inactive_token_accounts(&mut account);
vault_difference_result.deactivate_inactive_token_accounts(&mut account.borrow_mut());
Ok(())
}
@ -305,11 +304,11 @@ pub fn serum3_place_order(
// if reserved has increased, then increase cached value by the increase in reserved
pub fn inc_maybe_loan(
market_index: Serum3MarketIndex,
account: &mut MangoAccount,
account: &mut MangoAccountRefMut,
before_oo: &OpenOrdersSlim,
after_oo: &OpenOrdersSlim,
) {
let serum3_account = account.serum3.find_mut(market_index).unwrap();
let serum3_account = account.serum3_find_mut(market_index).unwrap();
if after_oo.native_coin_reserved() > before_oo.native_coin_reserved() {
let native_coin_reserved_increase =
@ -333,18 +332,18 @@ pub struct VaultDifferenceResult {
}
impl VaultDifferenceResult {
pub fn deactivate_inactive_token_accounts(&self, account: &mut MangoAccount) {
pub fn deactivate_inactive_token_accounts(&self, account: &mut MangoAccountRefMut) {
if !self.base_active {
account.tokens.deactivate(self.base_raw_index);
account.token_deactivate(self.base_raw_index);
}
if !self.quote_active {
account.tokens.deactivate(self.quote_raw_index);
account.token_deactivate(self.quote_raw_index);
}
}
}
pub fn apply_vault_difference(
account: &mut MangoAccount,
account: &mut MangoAccountRefMut,
base_bank: &mut Bank,
after_base_vault: u64,
before_base_vault: u64,
@ -356,11 +355,11 @@ pub fn apply_vault_difference(
// charged if an order executes and the loan materializes? Otherwise MMs that place
// an order without having the funds will be charged for each place_order!
let (base_position, base_raw_index) = account.tokens.get_mut(base_bank.token_index)?;
let (base_position, base_raw_index) = account.token_get_mut(base_bank.token_index)?;
let base_change = I80F48::from(after_base_vault) - I80F48::from(before_base_vault);
let base_active = base_bank.change_with_fee(base_position, base_change)?;
let (quote_position, quote_raw_index) = account.tokens.get_mut(quote_bank.token_index)?;
let (quote_position, quote_raw_index) = account.token_get_mut(quote_bank.token_index)?;
let quote_change = I80F48::from(after_quote_vault) - I80F48::from(before_quote_vault);
let quote_active = quote_bank.change_with_fee(quote_position, quote_change)?;

View File

@ -15,12 +15,8 @@ use super::{apply_vault_difference, OpenOrdersReserved, OpenOrdersSlim};
pub struct Serum3SettleFunds<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(mut)]
@ -77,13 +73,17 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
//
{
let account = ctx.accounts.account.load()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(
account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.ok_or_else(|| error!(MangoError::SomeError))?
.open_orders
== ctx.accounts.open_orders.key(),
@ -126,15 +126,14 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
cpi_settle_funds(ctx.accounts)?;
let after_oo = OpenOrdersSlim::from_oo(&open_orders);
let account = &mut ctx.accounts.account.load_mut()?;
let mut account = ctx.accounts.account.load_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
charge_maybe_fees(
serum_market.market_index,
&mut base_bank,
&mut quote_bank,
account,
&mut account.borrow_mut(),
&after_oo,
)?;
}
@ -153,7 +152,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference(
&mut account,
&mut account.borrow_mut(),
&mut base_bank,
after_base_vault,
before_base_vault,
@ -161,7 +160,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
after_quote_vault,
before_quote_vault,
)?
.deactivate_inactive_token_accounts(&mut account);
.deactivate_inactive_token_accounts(&mut account.borrow_mut());
}
Ok(())
@ -172,10 +171,10 @@ pub fn charge_maybe_fees(
market_index: Serum3MarketIndex,
coin_bank: &mut Bank,
pc_bank: &mut Bank,
account: &mut MangoAccount,
account: &mut MangoAccountRefMut,
after_oo: &OpenOrdersSlim,
) -> Result<()> {
let serum3_account = account.serum3.find_mut(market_index).unwrap();
let serum3_account = account.serum3_find_mut(market_index).unwrap();
let maybe_actualized_coin_loan = I80F48::from_num::<u64>(
serum3_account
@ -185,9 +184,10 @@ pub fn charge_maybe_fees(
if maybe_actualized_coin_loan > 0 {
serum3_account.previous_native_coin_reserved = after_oo.native_coin_reserved();
drop(serum3_account);
// loan origination fees
let coin_token_account = account.tokens.get_mut(coin_bank.token_index)?.0;
let coin_token_account = account.token_get_mut(coin_bank.token_index)?.0;
let coin_token_native = coin_token_account.native(&coin_bank);
if coin_token_native.is_negative() {
@ -201,6 +201,7 @@ pub fn charge_maybe_fees(
}
}
let serum3_account = account.serum3_find_mut(market_index).unwrap();
let maybe_actualized_pc_loan = I80F48::from_num::<u64>(
serum3_account
.previous_native_pc_reserved
@ -211,7 +212,7 @@ pub fn charge_maybe_fees(
serum3_account.previous_native_pc_reserved = after_oo.native_pc_reserved();
// loan origination fees
let pc_token_account = account.tokens.get_mut(pc_bank.token_index)?.0;
let pc_token_account = account.token_get_mut(pc_bank.token_index)?.0;
let pc_token_native = pc_token_account.native(&pc_bank);
if pc_token_native.is_negative() {

View File

@ -14,11 +14,8 @@ use crate::logs::{DepositLog, TokenBalanceLog};
pub struct TokenDeposit<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
#[account(
mut,
@ -61,10 +58,10 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
// Get the account's position for that token index
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let (position, raw_token_index, active_token_index) =
account.tokens.get_mut_or_create(token_index)?;
account.token_get_mut_or_create(token_index)?;
let amount_i80f48 = I80F48::from(amount);
let position_is_active = {
@ -77,16 +74,17 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
let indexed_position = position.indexed_position;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let (bank, oracle_price) =
retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?;
// Update the net deposits - adjust by price so different tokens are on the same basis (in USD terms)
account.net_deposits += cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
account.fixed.net_deposits +=
cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
emit!(TokenBalanceLog {
mango_account: ctx.accounts.account.key(),
token_index: token_index,
token_index,
indexed_position: indexed_position.to_bits(),
deposit_index: bank.deposit_index.to_bits(),
borrow_index: bank.borrow_index.to_bits(),
@ -98,7 +96,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
// TODO: This will be used to disable is_bankrupt or being_liquidated
// when health recovers sufficiently
//
let health = compute_health(&account, HealthType::Init, &retriever)
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)
.context("post-deposit init health")?;
msg!("health: {}", health);
@ -109,7 +107,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
// Deposits can deactivate a position if they cancel out a previous borrow.
//
if !position_is_active {
account.tokens.deactivate(raw_token_index);
account.token_deactivate(raw_token_index);
}
emit!(DepositLog {

View File

@ -14,13 +14,8 @@ use crate::util::checked_math as cm;
pub struct TokenWithdraw<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
// note: should never be the delegate
has_one = owner,
)]
pub account: AccountLoader<'info, MangoAccount>,
#[account(mut, has_one = group, has_one = owner)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
@ -65,10 +60,10 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// Get the account's position for that token index
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let (position, raw_token_index, active_token_index) =
account.tokens.get_mut_or_create(token_index)?;
account.token_get_mut_or_create(token_index)?;
// The bank will also be passed in remainingAccounts. Use an explicit scope
// to drop the &mut before we borrow it immutably again later.
@ -121,16 +116,17 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
let indexed_position = position.indexed_position;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let (bank, oracle_price) =
retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?;
// Update the net deposits - adjust by price so different tokens are on the same basis (in USD terms)
account.net_deposits -= cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
account.fixed.net_deposits -=
cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
emit!(TokenBalanceLog {
mango_account: ctx.accounts.account.key(),
token_index: token_index,
token_index,
indexed_position: indexed_position.to_bits(),
deposit_index: bank.deposit_index.to_bits(),
borrow_index: bank.borrow_index.to_bits(),
@ -140,7 +136,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
//
// Health check
//
let health = compute_health(&account, HealthType::Init, &retriever)
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)
.context("post-withdraw init health")?;
msg!("health: {}", health);
require!(health >= 0, MangoError::HealthMustBePositive);
@ -151,13 +147,13 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// deactivated.
//
if !position_is_active {
account.tokens.deactivate(raw_token_index);
account.token_deactivate(raw_token_index);
}
emit!(WithdrawLog {
mango_account: ctx.accounts.account.key(),
signer: ctx.accounts.owner.key(),
token_index: token_index,
token_index,
quantity: amount,
price: oracle_price.to_bits(),
});

View File

@ -15,18 +15,20 @@ pub mod error;
pub mod events;
pub mod instructions;
pub mod logs;
mod serum3_cpi;
pub mod serum3_cpi;
pub mod state;
pub mod types;
use state::{OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex};
use state::{
AccountSize, OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex,
};
declare_id!("m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD");
#[program]
pub mod mango_v4 {
use crate::state::OracleConfig;
use crate::state::{AccountSize, OracleConfig};
use super::*;
@ -125,9 +127,14 @@ pub mod mango_v4 {
pub fn account_create(
ctx: Context<AccountCreate>,
account_num: u8,
account_size: AccountSize,
name: String,
) -> Result<()> {
instructions::account_create(ctx, account_num, name)
instructions::account_create(ctx, account_num, account_size, name)
}
pub fn account_expand(ctx: Context<AccountExpand>) -> Result<()> {
instructions::account_expand(ctx)
}
pub fn account_edit(

View File

@ -11,8 +11,8 @@ pub fn emit_perp_balances(
pm: &PerpMarket,
) {
emit!(PerpBalanceLog {
mango_account: mango_account,
market_index: market_index,
mango_account,
market_index,
base_position: pp.base_position_lots,
quote_position: pp.quote_position_native.to_bits(),
long_settled_funding: pp.long_settled_funding.to_bits(),

View File

@ -0,0 +1,313 @@
use std::cell::{Ref, RefMut};
use std::marker::PhantomData;
use std::mem::size_of;
use anchor_lang::prelude::*;
use anchor_lang::Discriminator;
use arrayref::array_ref;
// Header is created by scanning and parsing dynamic portion of the account
// Header stores useful information e.g. offsets to easily seek into dynamic content
pub trait DynamicHeader: Sized {
// build header by scanning and parsing dynamic portion of the account
fn from_bytes(data: &[u8]) -> Result<Self>;
// initialize a header on a new account, if necessary
fn initialize(data: &mut [u8]) -> Result<()>;
}
pub trait DynamicAccountType: Owner + Discriminator {
type Header: DynamicHeader;
type Fixed: bytemuck::Pod;
}
#[derive(Clone)]
pub struct DynamicAccount<Header, Fixed, Dynamic> {
pub header: Header,
pub fixed: Fixed,
pub dynamic: Dynamic,
}
pub type DynamicAccountValue<D> =
DynamicAccount<<D as DynamicAccountType>::Header, <D as DynamicAccountType>::Fixed, Vec<u8>>;
pub type DynamicAccountRef<'a, D> = DynamicAccount<
&'a <D as DynamicAccountType>::Header,
&'a <D as DynamicAccountType>::Fixed,
&'a [u8],
>;
pub type DynamicAccountRefMut<'a, D> = DynamicAccount<
&'a mut <D as DynamicAccountType>::Header,
&'a mut <D as DynamicAccountType>::Fixed,
&'a mut [u8],
>;
// Want to generalize over:
// - T (which is Borrow<T>)
// - &T (which is Borrow<T> and Deref<Target=T>)
// - Ref<T> (which is Deref<T>)
pub trait DerefOrBorrow<T: ?Sized> {
fn deref_or_borrow(&self) -> &T;
}
impl<T: ?Sized> DerefOrBorrow<T> for T {
fn deref_or_borrow(&self) -> &T {
self
}
}
impl<T: ?Sized> DerefOrBorrow<T> for &T {
fn deref_or_borrow(&self) -> &T {
self
}
}
impl<T: Sized> DerefOrBorrow<[T]> for Vec<T> {
fn deref_or_borrow(&self) -> &[T] {
&self
}
}
impl<T: ?Sized> DerefOrBorrow<T> for &mut T {
fn deref_or_borrow(&self) -> &T {
self
}
}
impl<'a, T: ?Sized> DerefOrBorrow<T> for Ref<'a, T> {
fn deref_or_borrow(&self) -> &T {
&self
}
}
impl<'a, T: ?Sized> DerefOrBorrow<T> for RefMut<'a, T> {
fn deref_or_borrow(&self) -> &T {
&self
}
}
pub trait DerefOrBorrowMut<T: ?Sized> {
fn deref_or_borrow_mut(&mut self) -> &mut T;
}
impl<T: ?Sized> DerefOrBorrowMut<T> for T {
fn deref_or_borrow_mut(&mut self) -> &mut T {
self
}
}
impl<T: ?Sized> DerefOrBorrowMut<T> for &mut T {
fn deref_or_borrow_mut(&mut self) -> &mut T {
self
}
}
impl<'a, T: ?Sized> DerefOrBorrowMut<T> for RefMut<'a, T> {
fn deref_or_borrow_mut(&mut self) -> &mut T {
self
}
}
impl<T: Sized> DerefOrBorrowMut<[T]> for Vec<T> {
fn deref_or_borrow_mut(&mut self) -> &mut [T] {
self
}
}
pub struct AccountLoaderDynamic<'info, D: DynamicAccountType> {
acc_info: AccountInfo<'info>,
phantom1: PhantomData<&'info D>,
}
impl<'info, D: DynamicAccountType> AccountLoaderDynamic<'info, D> {
pub fn try_from(acc_info: &AccountInfo<'info>) -> Result<Self> {
if acc_info.owner != &D::owner() {
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
.with_pubkeys((*acc_info.owner, D::owner())));
}
let data = acc_info.try_borrow_data()?;
if data.len() < D::discriminator().len() {
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
}
let disc_bytes = array_ref![data, 0, 8];
if disc_bytes != &D::discriminator() {
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(Self {
acc_info: acc_info.clone(),
phantom1: PhantomData,
})
}
pub fn try_from_unchecked(acc_info: &AccountInfo<'info>) -> Result<Self> {
if acc_info.owner != &D::owner() {
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
.with_pubkeys((*acc_info.owner, D::owner())));
}
Ok(Self {
acc_info: acc_info.clone(),
phantom1: PhantomData,
})
}
/// Returns a Ref to the account data structure for reading.
pub fn load_fixed<'a>(&'a self) -> Result<Ref<'a, D::Fixed>> {
let data = self.acc_info.try_borrow_data()?;
let fixed = Ref::map(data, |d| {
bytemuck::from_bytes(&d[8..8 + size_of::<D::Fixed>()])
});
Ok(fixed)
}
/// Returns a Ref to the account data structure for reading.
pub fn load<'a>(
&'a self,
) -> Result<DynamicAccount<D::Header, Ref<'a, D::Fixed>, Ref<'a, [u8]>>> {
let data = self.acc_info.try_borrow_data()?;
let header = D::Header::from_bytes(&data[8 + size_of::<D::Fixed>()..])?;
let (_, data) = Ref::map_split(data, |d| d.split_at(8));
let (fixed_bytes, dynamic) = Ref::map_split(data, |d| d.split_at(size_of::<D::Fixed>()));
Ok(DynamicAccount {
header,
fixed: Ref::map(fixed_bytes, |b| bytemuck::from_bytes(b)),
dynamic,
})
}
pub fn load_init<'a>(
&'a self,
) -> Result<DynamicAccount<D::Header, RefMut<'a, D::Fixed>, RefMut<'a, [u8]>>> {
if !self.acc_info.is_writable {
return Err(ErrorCode::AccountNotMutable.into());
}
let mut data = self.acc_info.try_borrow_mut_data()?;
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
let disc_bytes: &mut [u8] = &mut data[0..8];
disc_bytes.copy_from_slice(bytemuck::bytes_of(&(D::discriminator())));
D::Header::initialize(&mut data[8 + size_of::<D::Fixed>()..])?;
drop(data);
self.load_mut()
}
/// Returns a Ref to the account data structure for reading.
pub fn load_mut<'a>(
&'a self,
) -> Result<DynamicAccount<D::Header, RefMut<'a, D::Fixed>, RefMut<'a, [u8]>>> {
if !self.acc_info.is_writable {
return Err(ErrorCode::AccountNotMutable.into());
}
let data = self.acc_info.try_borrow_mut_data()?;
let header = D::Header::from_bytes(&data[8 + size_of::<D::Fixed>()..])?;
let (_, data) = RefMut::map_split(data, |d| d.split_at_mut(8));
let (fixed_bytes, dynamic) =
RefMut::map_split(data, |d| d.split_at_mut(size_of::<D::Fixed>()));
Ok(DynamicAccount {
header,
fixed: RefMut::map(fixed_bytes, |b| bytemuck::from_bytes_mut(b)),
dynamic,
})
}
}
impl<'info, D: DynamicAccountType> anchor_lang::Accounts<'info> for AccountLoaderDynamic<'info, D> {
#[inline(never)]
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut std::collections::BTreeMap<String, u8>,
_reallocs: &mut std::collections::BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
let l = AccountLoaderDynamic::try_from(account)?;
Ok(l)
}
}
impl<'info, D: DynamicAccountType> anchor_lang::AccountsExit<'info>
for AccountLoaderDynamic<'info, D>
{
fn exit(&self, _program_id: &Pubkey) -> Result<()> {
// Normally anchor writes the discriminator again here, but I don't see why
let data = self.acc_info.try_borrow_data()?;
if data.len() < D::discriminator().len() {
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
}
let disc_bytes = array_ref![data, 0, 8];
if disc_bytes != &D::discriminator() {
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(())
}
}
impl<'info, D: DynamicAccountType> anchor_lang::AccountsClose<'info>
for AccountLoaderDynamic<'info, D>
{
fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
close(self.to_account_info(), sol_destination)
}
}
impl<'info, D: DynamicAccountType> anchor_lang::ToAccountMetas for AccountLoaderDynamic<'info, D> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.acc_info.is_signer);
let meta = match self.acc_info.is_writable {
false => AccountMeta::new_readonly(*self.acc_info.key, is_signer),
true => AccountMeta::new(*self.acc_info.key, is_signer),
};
vec![meta]
}
}
impl<'info, D: DynamicAccountType> AsRef<AccountInfo<'info>> for AccountLoaderDynamic<'info, D> {
fn as_ref(&self) -> &AccountInfo<'info> {
&self.acc_info
}
}
impl<'info, D: DynamicAccountType> anchor_lang::ToAccountInfos<'info>
for AccountLoaderDynamic<'info, D>
{
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.acc_info.clone()]
}
}
impl<'info, D: DynamicAccountType> anchor_lang::Key for AccountLoaderDynamic<'info, D> {
fn key(&self) -> Pubkey {
*self.acc_info.key
}
}
// https://github.com/coral-xyz/anchor/blob/master/lang/src/common.rs#L8
fn close<'info>(info: AccountInfo<'info>, sol_destination: AccountInfo<'info>) -> Result<()> {
// Transfer tokens from the account to the sol_destination.
let dest_starting_lamports = sol_destination.lamports();
**sol_destination.lamports.borrow_mut() =
dest_starting_lamports.checked_add(info.lamports()).unwrap();
**info.lamports.borrow_mut() = 0;
// Mark the account discriminator as closed.
let mut data = info.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
dst[0..8].copy_from_slice(&[255, 255, 255, 255, 255, 255, 255, 255]);
Ok(())
}

View File

@ -6,16 +6,16 @@ use fixed::types::I80F48;
use crate::events::{Equity, TokenEquity};
use super::{MangoAccount, ScanningAccountRetriever};
use super::{MangoAccountRef, ScanningAccountRetriever};
pub fn compute_equity(
account: &MangoAccount,
account: &MangoAccountRef,
retriever: &ScanningAccountRetriever,
) -> Result<Equity> {
let mut token_equity_map = HashMap::new();
// token contributions
for (_i, position) in account.tokens.iter_active().enumerate() {
for (_i, position) in account.token_iter_active().enumerate() {
let (bank, oracle_price) = retriever.scanned_bank_and_oracle(position.token_index)?;
// converts the token value to the basis token value for health computations
// TODO: health basis token == USDC?
@ -24,7 +24,7 @@ pub fn compute_equity(
}
// token contributions from Serum3
for (_i, serum_account) in account.serum3.iter_active().enumerate() {
for (_i, serum_account) in account.serum3_iter_active().enumerate() {
let oo = retriever.scanned_serum_oo(&serum_account.open_orders)?;
// note base token value

View File

@ -11,9 +11,11 @@ use std::collections::HashMap;
use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::serum3_cpi;
use crate::state::{oracle_price, Bank, MangoAccount, PerpMarket, PerpMarketIndex, TokenIndex};
use crate::state::{oracle_price, Bank, PerpMarket, PerpMarketIndex, TokenIndex};
use crate::util::checked_math as cm;
use super::MangoAccountRef;
const BANKRUPTCY_DUST_THRESHOLD: I80F48 = I80F48!(0.000001);
/// This trait abstracts how to find accounts needed for the health computation.
@ -59,11 +61,11 @@ pub struct FixedOrderAccountRetriever<T: KeyedAccountReader> {
pub fn new_fixed_order_account_retriever<'a, 'info>(
ais: &'a [AccountInfo<'info>],
account: &MangoAccount,
account: &MangoAccountRef,
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
let active_token_len = account.tokens.iter_active().count();
let active_serum3_len = account.serum3.iter_active().count();
let active_perp_len = account.perps.iter_active_accounts().count();
let active_token_len = account.token_iter_active().count();
let active_serum3_len = account.serum3_iter_active().count();
let active_perp_len = account.perp_iter_active_accounts().count();
let expected_ais = cm!(active_token_len * 2 // banks + oracles
+ active_perp_len // PerpMarkets
+ active_serum3_len); // open_orders
@ -377,13 +379,13 @@ pub enum HealthType {
///
/// These account infos must fit the fixed layout defined by FixedOrderAccountRetriever.
pub fn compute_health_from_fixed_accounts(
account: &MangoAccount,
account: &MangoAccountRef,
health_type: HealthType,
ais: &[AccountInfo],
) -> Result<I80F48> {
let active_token_len = account.tokens.iter_active().count();
let active_serum3_len = account.serum3.iter_active().count();
let active_perp_len = account.perps.iter_active_accounts().count();
let active_token_len = account.token_iter_active().count();
let active_serum3_len = account.serum3_iter_active().count();
let active_perp_len = account.perp_iter_active_accounts().count();
let expected_ais = cm!(active_token_len * 2 // banks + oracles
+ active_perp_len // PerpMarkets
+ active_serum3_len); // open_orders
@ -403,7 +405,7 @@ pub fn compute_health_from_fixed_accounts(
/// Compute health with an arbitrary AccountRetriever
pub fn compute_health(
account: &MangoAccount,
account: &MangoAccountRef,
health_type: HealthType,
retriever: &impl AccountRetriever,
) -> Result<I80F48> {
@ -785,15 +787,15 @@ fn find_token_info_index(infos: &[TokenInfo], token_index: TokenIndex) -> Result
/// Generate a HealthCache for an account and its health accounts.
pub fn new_health_cache(
account: &MangoAccount,
account: &MangoAccountRef,
retriever: &impl AccountRetriever,
) -> Result<HealthCache> {
// token contribution from token accounts
let mut token_infos = vec![];
for (i, position) in account.tokens.iter_active().enumerate() {
for (i, position) in account.token_iter_active().enumerate() {
let (bank, oracle_price) =
retriever.bank_and_oracle(&account.group, i, position.token_index)?;
retriever.bank_and_oracle(&account.fixed.group, i, position.token_index)?;
// converts the token value to the basis token value for health computations
// TODO: health basis token == USDC?
@ -814,7 +816,7 @@ pub fn new_health_cache(
// Fill the TokenInfo balance with free funds in serum3 oo accounts, and fill
// the serum3_max_reserved with their reserved funds. Also build Serum3Infos.
let mut serum3_infos = vec![];
for (i, serum_account) in account.serum3.iter_active().enumerate() {
for (i, serum_account) in account.serum3_iter_active().enumerate() {
let oo = retriever.serum_oo(i, &serum_account.open_orders)?;
// find the TokenInfos for the market's base and quote tokens
@ -851,9 +853,10 @@ pub fn new_health_cache(
// TODO: also account for perp funding in health
// health contribution from perp accounts
let mut perp_infos = Vec::with_capacity(account.perps.iter_active_accounts().count());
for (i, perp_account) in account.perps.iter_active_accounts().enumerate() {
let perp_market = retriever.perp_market(&account.group, i, perp_account.market_index)?;
let mut perp_infos = Vec::with_capacity(account.perp_iter_active_accounts().count());
for (i, perp_account) in account.perp_iter_active_accounts().enumerate() {
let perp_market =
retriever.perp_market(&account.fixed.group, i, perp_account.market_index)?;
// find the TokenInfos for the market's base and quote tokens
let base_index = find_token_info_index(&token_infos, perp_market.base_token_index)?;
@ -947,6 +950,7 @@ pub fn new_health_cache(
mod tests {
use super::*;
use crate::state::oracle::StubOracle;
use crate::state::{MangoAccount, MangoAccountValue};
use std::cell::RefCell;
use std::convert::identity;
use std::mem::size_of;
@ -1065,7 +1069,9 @@ mod tests {
// Run a health test that includes all the side values (like referrer_rebates_accrued)
#[test]
fn test_health0() {
let mut account = MangoAccount::default();
let buffer = MangoAccount::default().try_to_vec().unwrap();
let mut account = MangoAccountValue::from_bytes(&buffer).unwrap();
let group = Pubkey::new_unique();
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
@ -1073,20 +1079,20 @@ mod tests {
bank1
.data()
.deposit(
account.tokens.get_mut_or_create(1).unwrap().0,
account.token_get_mut_or_create(1).unwrap().0,
I80F48::from(100),
)
.unwrap();
bank2
.data()
.withdraw_without_fee(
account.tokens.get_mut_or_create(4).unwrap().0,
account.token_get_mut_or_create(4).unwrap().0,
I80F48::from(10),
)
.unwrap();
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
let serum3account = account.serum3.create(2).unwrap();
let serum3account = account.serum3_create(2).unwrap();
serum3account.open_orders = oo1.pubkey;
serum3account.base_token_index = 4;
serum3account.quote_token_index = 1;
@ -1106,7 +1112,7 @@ mod tests {
perp1.data().maint_liab_weight = I80F48::from_num(1.0 + 0.1f64);
perp1.data().quote_lot_size = 100;
perp1.data().base_lot_size = 10;
let perpaccount = account.perps.get_account_mut_or_create(9).unwrap().0;
let perpaccount = account.perp_get_account_mut_or_create(9).unwrap().0;
perpaccount.base_position_lots = 3;
perpaccount.quote_position_native = -I80F48::from(310u16);
perpaccount.bids_base_lots = 7;
@ -1142,7 +1148,7 @@ mod tests {
let health3 =
(3.0 + 7.0 + 1.0) * 10.0 * 5.0 * 0.8 + (-310.0 + 2.0 * 100.0 - 7.0 * 10.0 * 5.0);
assert!(health_eq(
compute_health(&account, HealthType::Init, &retriever).unwrap(),
compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(),
health1 + health2 + health3
));
}
@ -1226,7 +1232,9 @@ mod tests {
expected_health: f64,
}
fn test_health1_runner(testcase: &TestHealth1Case) {
let mut account = MangoAccount::default();
let buffer = MangoAccount::default().try_to_vec().unwrap();
let mut account = MangoAccountValue::from_bytes(&buffer).unwrap();
let group = Pubkey::new_unique();
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
@ -1235,27 +1243,27 @@ mod tests {
bank1
.data()
.change_without_fee(
account.tokens.get_mut_or_create(1).unwrap().0,
account.token_get_mut_or_create(1).unwrap().0,
I80F48::from(testcase.token1),
)
.unwrap();
bank2
.data()
.change_without_fee(
account.tokens.get_mut_or_create(4).unwrap().0,
account.token_get_mut_or_create(4).unwrap().0,
I80F48::from(testcase.token2),
)
.unwrap();
bank3
.data()
.change_without_fee(
account.tokens.get_mut_or_create(5).unwrap().0,
account.token_get_mut_or_create(5).unwrap().0,
I80F48::from(testcase.token3),
)
.unwrap();
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
let serum3account1 = account.serum3.create(2).unwrap();
let serum3account1 = account.serum3_create(2).unwrap();
serum3account1.open_orders = oo1.pubkey;
serum3account1.base_token_index = 4;
serum3account1.quote_token_index = 1;
@ -1263,7 +1271,7 @@ mod tests {
oo1.data().native_coin_total = testcase.oo_1_2.1;
let mut oo2 = TestAccount::<OpenOrders>::new_zeroed();
let serum3account2 = account.serum3.create(3).unwrap();
let serum3account2 = account.serum3_create(3).unwrap();
serum3account2.open_orders = oo2.pubkey;
serum3account2.base_token_index = 5;
serum3account2.quote_token_index = 1;
@ -1280,7 +1288,7 @@ mod tests {
perp1.data().maint_liab_weight = I80F48::from_num(1.0 + 0.1f64);
perp1.data().quote_lot_size = 100;
perp1.data().base_lot_size = 10;
let perpaccount = account.perps.get_account_mut_or_create(9).unwrap().0;
let perpaccount = account.perp_get_account_mut_or_create(9).unwrap().0;
perpaccount.base_position_lots = testcase.perp1.0;
perpaccount.quote_position_native = I80F48::from(testcase.perp1.1);
perpaccount.bids_base_lots = testcase.perp1.2;
@ -1310,7 +1318,7 @@ mod tests {
};
assert!(health_eq(
compute_health(&account, HealthType::Init, &retriever).unwrap(),
compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(),
testcase.expected_health
));
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,296 @@
use anchor_lang::prelude::*;
use checked_math as cm;
use fixed::types::I80F48;
use static_assertions::const_assert_eq;
use std::cmp::Ordering;
use std::mem::size_of;
use crate::state::*;
pub const FREE_ORDER_SLOT: PerpMarketIndex = PerpMarketIndex::MAX;
#[zero_copy]
#[derive(AnchorDeserialize, AnchorSerialize, Debug)]
pub struct TokenPosition {
// TODO: Why did we have deposits and borrows as two different values
// if only one of them was allowed to be != 0 at a time?
// todo: maybe we want to split collateral and lending?
// todo: see https://github.com/blockworks-foundation/mango-v4/issues/1
// todo: how does ftx do this?
/// The deposit_index (if positive) or borrow_index (if negative) scaled position
pub indexed_position: I80F48,
/// index into Group.tokens
pub token_index: TokenIndex,
/// incremented when a market requires this position to stay alive
pub in_use_count: u8,
pub reserved: [u8; 5],
}
unsafe impl bytemuck::Pod for TokenPosition {}
unsafe impl bytemuck::Zeroable for TokenPosition {}
const_assert_eq!(size_of::<TokenPosition>(), 24);
const_assert_eq!(size_of::<TokenPosition>() % 8, 0);
impl Default for TokenPosition {
fn default() -> Self {
TokenPosition {
indexed_position: I80F48::ZERO,
token_index: TokenIndex::MAX,
in_use_count: 0,
reserved: Default::default(),
}
}
}
impl TokenPosition {
pub fn is_active(&self) -> bool {
self.token_index != TokenIndex::MAX
}
pub fn is_active_for_token(&self, token_index: TokenIndex) -> bool {
self.token_index == token_index
}
pub fn native(&self, bank: &Bank) -> I80F48 {
if self.indexed_position.is_positive() {
self.indexed_position * bank.deposit_index
} else {
self.indexed_position * bank.borrow_index
}
}
pub fn ui(&self, bank: &Bank) -> I80F48 {
if self.indexed_position.is_positive() {
(self.indexed_position * bank.deposit_index)
/ I80F48::from_num(10u64.pow(bank.mint_decimals as u32))
} else {
(self.indexed_position * bank.borrow_index)
/ I80F48::from_num(10u64.pow(bank.mint_decimals as u32))
}
}
pub fn is_in_use(&self) -> bool {
self.in_use_count > 0
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
pub struct Serum3Orders {
pub open_orders: Pubkey,
// tracks reserved funds in open orders account,
// used for bookkeeping of potentital loans which
// can be charged with loan origination fees
// e.g. serum3 settle funds ix
pub previous_native_coin_reserved: u64,
pub previous_native_pc_reserved: u64,
pub market_index: Serum3MarketIndex,
/// Store the base/quote token index, so health computations don't need
/// to get passed the static SerumMarket to find which tokens a market
/// uses and look up the correct oracles.
pub base_token_index: TokenIndex,
pub quote_token_index: TokenIndex,
pub reserved: [u8; 2],
}
const_assert_eq!(size_of::<Serum3Orders>(), 32 + 8 * 2 + 2 * 3 + 2); // 56
const_assert_eq!(size_of::<Serum3Orders>() % 8, 0);
unsafe impl bytemuck::Pod for Serum3Orders {}
unsafe impl bytemuck::Zeroable for Serum3Orders {}
impl Serum3Orders {
pub fn is_active(&self) -> bool {
self.market_index != Serum3MarketIndex::MAX
}
pub fn is_active_for_market(&self, market_index: Serum3MarketIndex) -> bool {
self.market_index == market_index
}
}
impl Default for Serum3Orders {
fn default() -> Self {
Self {
open_orders: Pubkey::default(),
market_index: Serum3MarketIndex::MAX,
base_token_index: TokenIndex::MAX,
quote_token_index: TokenIndex::MAX,
reserved: Default::default(),
previous_native_coin_reserved: 0,
previous_native_pc_reserved: 0,
}
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct PerpPositions {
pub market_index: PerpMarketIndex,
pub reserved: [u8; 6],
/// Active position size, measured in base lots
pub base_position_lots: i64,
/// Active position in quote (conversation rate is that of the time the order was settled)
/// measured in native quote
pub quote_position_native: I80F48,
/// Already settled funding
pub long_settled_funding: I80F48,
pub short_settled_funding: I80F48,
/// Base lots in bids
pub bids_base_lots: i64,
/// Base lots in asks
pub asks_base_lots: i64,
/// Liquidity mining rewards
// pub mngo_accrued: u64,
/// Amount that's on EventQueue waiting to be processed
pub taker_base_lots: i64,
pub taker_quote_lots: i64,
}
impl std::fmt::Debug for PerpPositions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PerpAccount")
.field("market_index", &self.market_index)
.field("base_position_lots", &self.base_position_lots)
.field("quote_position_native", &self.quote_position_native)
.field("bids_base_lots", &self.bids_base_lots)
.field("asks_base_lots", &self.asks_base_lots)
.field("taker_base_lots", &self.taker_base_lots)
.field("taker_quote_lots", &self.taker_quote_lots)
.finish()
}
}
const_assert_eq!(size_of::<PerpPositions>(), 8 + 8 * 5 + 3 * 16); // 96
const_assert_eq!(size_of::<PerpPositions>() % 8, 0);
unsafe impl bytemuck::Pod for PerpPositions {}
unsafe impl bytemuck::Zeroable for PerpPositions {}
impl Default for PerpPositions {
fn default() -> Self {
Self {
market_index: PerpMarketIndex::MAX,
base_position_lots: 0,
quote_position_native: I80F48::ZERO,
bids_base_lots: 0,
asks_base_lots: 0,
taker_base_lots: 0,
taker_quote_lots: 0,
reserved: Default::default(),
long_settled_funding: I80F48::ZERO,
short_settled_funding: I80F48::ZERO,
}
}
}
impl PerpPositions {
/// Add taker trade after it has been matched but before it has been process on EventQueue
pub fn add_taker_trade(&mut self, side: Side, base_lots: i64, quote_lots: i64) {
match side {
Side::Bid => {
self.taker_base_lots = cm!(self.taker_base_lots + base_lots);
self.taker_quote_lots = cm!(self.taker_quote_lots - quote_lots);
}
Side::Ask => {
self.taker_base_lots = cm!(self.taker_base_lots - base_lots);
self.taker_quote_lots = cm!(self.taker_quote_lots + quote_lots);
}
}
}
/// Remove taker trade after it has been processed on EventQueue
pub fn remove_taker_trade(&mut self, base_change: i64, quote_change: i64) {
self.taker_base_lots = cm!(self.taker_base_lots - base_change);
self.taker_quote_lots = cm!(self.taker_quote_lots - quote_change);
}
pub fn is_active(&self) -> bool {
self.market_index != PerpMarketIndex::MAX
}
pub fn is_active_for_market(&self, market_index: PerpMarketIndex) -> bool {
self.market_index == market_index
}
/// This assumes settle_funding was already called
pub fn change_base_position(&mut self, perp_market: &mut PerpMarket, base_change: i64) {
let start = self.base_position_lots;
self.base_position_lots += base_change;
perp_market.open_interest += self.base_position_lots.abs() - start.abs();
}
/// Move unrealized funding payments into the quote_position
pub fn settle_funding(&mut self, perp_market: &PerpMarket) {
match self.base_position_lots.cmp(&0) {
Ordering::Greater => {
self.quote_position_native -= (perp_market.long_funding
- self.long_settled_funding)
* I80F48::from_num(self.base_position_lots);
}
Ordering::Less => {
self.quote_position_native -= (perp_market.short_funding
- self.short_settled_funding)
* I80F48::from_num(self.base_position_lots);
}
Ordering::Equal => (),
}
self.long_settled_funding = perp_market.long_funding;
self.short_settled_funding = perp_market.short_funding;
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
pub struct PerpOpenOrders {
pub order_side: Side, // TODO: storing enums isn't POD
pub reserved1: [u8; 1],
pub order_market: PerpMarketIndex,
pub reserved2: [u8; 4],
pub client_order_id: u64,
pub order_id: i128,
}
impl Default for PerpOpenOrders {
fn default() -> Self {
Self {
order_side: Side::Bid,
reserved1: Default::default(),
order_market: FREE_ORDER_SLOT,
reserved2: Default::default(),
client_order_id: 0,
order_id: 0,
}
}
}
unsafe impl bytemuck::Pod for PerpOpenOrders {}
unsafe impl bytemuck::Zeroable for PerpOpenOrders {}
const_assert_eq!(size_of::<PerpOpenOrders>(), 1 + 1 + 2 + 4 + 8 + 16);
const_assert_eq!(size_of::<PerpOpenOrders>() % 8, 0);
#[macro_export]
macro_rules! account_seeds {
( $account:expr ) => {
&[
$account.group.as_ref(),
b"MangoAccount".as_ref(),
$account.owner.as_ref(),
&$account.account_num.to_le_bytes(),
&[$account.bump],
]
};
}
pub use account_seeds;

View File

@ -1,8 +1,10 @@
pub use bank::*;
pub use dynamic_account::*;
pub use equity::*;
pub use group::*;
pub use health::*;
pub use mango_account::*;
pub use mango_account_components::*;
pub use mint_info::*;
pub use oracle::*;
pub use orderbook::*;
@ -10,10 +12,12 @@ pub use perp_market::*;
pub use serum3_market::*;
mod bank;
mod dynamic_account;
mod equity;
mod group;
mod health;
mod mango_account;
mod mango_account_components;
mod mint_info;
mod oracle;
mod orderbook;

View File

@ -10,7 +10,7 @@ use switchboard_v2::AggregatorAccountData;
use crate::accounts_zerocopy::*;
use crate::checked_math as cm;
use crate::error::MangoError;
use crate::error::*;
const LOOKUP_START: i8 = -12;
const LOOKUP: [I80F48; 25] = [
@ -144,14 +144,21 @@ pub fn oracle_price(
cm!(price * decimal_adj)
}
OracleType::SwitchboardV2 => {
fn from_foreign_error(e: impl std::fmt::Display) -> Error {
error_msg!("{}", e)
}
let feed = bytemuck::from_bytes::<AggregatorAccountData>(&data[8..]);
let feed_result = feed.get_result()?;
let price_decimal: f64 = feed_result.try_into()?;
let feed_result = feed.get_result().map_err(from_foreign_error)?;
let price_decimal: f64 = feed_result.try_into().map_err(from_foreign_error)?;
let price = I80F48::from_num(price_decimal);
// Filter out bad prices
let std_deviation_decimal: f64 =
feed.latest_confirmed_round.std_deviation.try_into()?;
let std_deviation_decimal: f64 = feed
.latest_confirmed_round
.std_deviation
.try_into()
.map_err(from_foreign_error)?;
if I80F48::from_num(std_deviation_decimal) > oracle_conf_filter * price {
msg!(
"Switchboard v2 std deviation too high; pubkey {} price: {} latest_confirmed_round.std_deviation: {}",

View File

@ -1,12 +1,12 @@
use std::cell::RefMut;
use crate::accounts_zerocopy::*;
use crate::state::MangoAccountRefMut;
use crate::{
error::MangoError,
state::{
orderbook::{bookside::BookSide, nodes::LeafNode},
EventQueue, MangoAccount, MangoAccountPerpPositions, PerpMarket, FREE_ORDER_SLOT,
MAX_PERP_OPEN_ORDERS,
EventQueue, PerpMarket, FREE_ORDER_SLOT,
},
};
use anchor_lang::prelude::*;
@ -159,7 +159,7 @@ impl<'a> Book<'a> {
perp_market: &mut PerpMarket,
event_queue: &mut EventQueue,
oracle_price: I80F48,
mango_account_perps: &mut MangoAccountPerpPositions,
mango_account: &mut MangoAccountRefMut,
mango_account_pk: &Pubkey,
price_lots: i64,
max_base_lots: i64,
@ -264,8 +264,8 @@ impl<'a> Book<'a> {
// Record the taker trade in the account already, even though it will only be
// realized when the fill event gets executed
let perp_account = mango_account_perps
.get_account_mut_or_create(market.perp_market_index)?
let perp_account = mango_account
.perp_get_account_mut_or_create(market.perp_market_index)?
.0;
perp_account.add_taker_trade(side, match_base_lots, match_quote_lots);
@ -346,8 +346,8 @@ impl<'a> Book<'a> {
event_queue.push_back(cast(event)).unwrap();
}
let owner_slot = mango_account_perps
.next_order_slot()
let owner_slot = mango_account
.perp_next_order_slot()
.ok_or_else(|| error!(MangoError::SomeError))?;
let new_order = LeafNode::new(
owner_slot as u8,
@ -373,13 +373,13 @@ impl<'a> Book<'a> {
price_lots
);
mango_account_perps.add_order(market.perp_market_index, side, &new_order)?;
mango_account.perp_add_order(market.perp_market_index, side, &new_order)?;
}
// if there were matched taker quote apply ref fees
// we know ref_fee_rate is not None if total_quote_taken > 0
if total_quote_lots_taken > 0 {
apply_fees(market, mango_account_perps, total_quote_lots_taken)?;
apply_fees(market, mango_account, total_quote_lots_taken)?;
}
Ok(())
@ -387,20 +387,21 @@ impl<'a> Book<'a> {
pub fn cancel_all_order(
&mut self,
mango_account: &mut MangoAccount,
mango_account: &mut MangoAccountRefMut,
perp_market: &mut PerpMarket,
mut limit: u8,
side_to_cancel_option: Option<Side>,
) -> Result<()> {
for i in 0..MAX_PERP_OPEN_ORDERS {
if mango_account.perps.order_market[i] == FREE_ORDER_SLOT
|| mango_account.perps.order_market[i] != perp_market.perp_market_index
for i in 0..mango_account.header.perp_oo_count() {
let oo = mango_account.perp_oo_get_raw(i);
if oo.order_market == FREE_ORDER_SLOT
|| oo.order_market != perp_market.perp_market_index
{
continue;
}
let order_id = mango_account.perps.order_id[i];
let order_side = mango_account.perps.order_side[i];
let order_id = oo.order_id;
let order_side = oo.order_side;
if let Some(side_to_cancel) = side_to_cancel_option {
if side_to_cancel != order_side {
@ -410,8 +411,7 @@ impl<'a> Book<'a> {
if let Ok(leaf_node) = self.cancel_order(order_id, order_side) {
mango_account
.perps
.remove_order(leaf_node.owner_slot as usize, leaf_node.quantity)?
.perp_remove_order(leaf_node.owner_slot as usize, leaf_node.quantity)?
};
limit -= 1;
@ -441,7 +441,7 @@ impl<'a> Book<'a> {
/// both the maker and taker fees.
fn apply_fees(
market: &mut PerpMarket,
mango_account_perps: &mut MangoAccountPerpPositions,
mango_account: &mut MangoAccountRefMut,
total_quote_taken: i64,
) -> Result<()> {
let taker_quote_native = I80F48::from_num(
@ -458,8 +458,8 @@ fn apply_fees(
let maker_fees = taker_quote_native * market.maker_fee;
let taker_fees = taker_quote_native * market.taker_fee;
let perp_account = mango_account_perps
.get_account_mut_or_create(market.perp_market_index)?
let perp_account = mango_account
.perp_get_account_mut_or_create(market.perp_market_index)?
.0;
perp_account.quote_position_native -= taker_fees;
market.fees_accrued += taker_fees + maker_fees;

View File

@ -15,7 +15,8 @@ pub mod queue;
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{MangoAccountPerpPositions, PerpMarket, FREE_ORDER_SLOT};
use crate::state::{MangoAccount, MangoAccountValue, PerpMarket, FREE_ORDER_SLOT};
use anchor_lang::prelude::*;
use bytemuck::Zeroable;
use fixed::types::I80F48;
use solana_program::pubkey::Pubkey;
@ -99,7 +100,8 @@ mod tests {
let mut new_order =
|book: &mut Book, event_queue: &mut EventQueue, side, price, now_ts| -> i128 {
let mut account_perps = MangoAccountPerpPositions::new();
let buffer = MangoAccount::default().try_to_vec().unwrap();
let mut account = MangoAccountValue::from_bytes(&buffer).unwrap();
let quantity = 1;
let tif = 100;
@ -109,7 +111,7 @@ mod tests {
&mut perp_market,
event_queue,
oracle_price,
&mut account_perps,
&mut account.borrow_mut(),
&Pubkey::default(),
price,
quantity,
@ -121,7 +123,7 @@ mod tests {
u8::MAX,
)
.unwrap();
account_perps.order_id[0]
account.perp_oo_get_raw(0).order_id
};
// insert bids until book side is full
@ -193,8 +195,10 @@ mod tests {
market.maker_fee = I80F48::from_num(-0.001f64);
market.taker_fee = I80F48::from_num(0.01f64);
let mut maker = MangoAccountPerpPositions::new();
let mut taker = MangoAccountPerpPositions::new();
let buffer = MangoAccount::default().try_to_vec().unwrap();
let mut maker = MangoAccountValue::from_bytes(&buffer).unwrap();
let mut taker = MangoAccountValue::from_bytes(&buffer).unwrap();
let maker_pk = Pubkey::new_unique();
let taker_pk = Pubkey::new_unique();
let now_ts = 1000000;
@ -207,7 +211,7 @@ mod tests {
&mut market,
&mut event_queue,
oracle_price,
&mut maker,
&mut maker.borrow_mut(),
&maker_pk,
price,
bid_quantity,
@ -219,19 +223,28 @@ mod tests {
u8::MAX,
)
.unwrap();
assert_eq!(maker.order_market[0], market.perp_market_index);
assert_eq!(maker.order_market[1], FREE_ORDER_SLOT);
assert_ne!(maker.order_id[0], 0);
assert_eq!(maker.client_order_id[0], 42);
assert_eq!(maker.order_side[0], Side::Bid);
assert!(bookside_contains_key(&book.bids, maker.order_id[0]));
assert_eq!(
maker.perp_oo_get_mut_raw(0).order_market,
market.perp_market_index
);
assert_eq!(maker.perp_oo_get_mut_raw(1).order_market, FREE_ORDER_SLOT);
assert_ne!(maker.perp_oo_get_mut_raw(0).order_id, 0);
assert_eq!(maker.perp_oo_get_mut_raw(0).client_order_id, 42);
assert_eq!(maker.perp_oo_get_mut_raw(0).order_side, Side::Bid);
assert!(bookside_contains_key(
&book.bids,
maker.perp_oo_get_mut_raw(0).order_id
));
assert!(bookside_contains_price(&book.bids, price));
assert_eq!(maker.accounts[0].bids_base_lots, bid_quantity);
assert_eq!(maker.accounts[0].asks_base_lots, 0);
assert_eq!(maker.accounts[0].taker_base_lots, 0);
assert_eq!(maker.accounts[0].taker_quote_lots, 0);
assert_eq!(maker.accounts[0].base_position_lots, 0);
assert_eq!(maker.accounts[0].quote_position_native, 0);
assert_eq!(maker.perp_get_raw(0).bids_base_lots, bid_quantity);
assert_eq!(maker.perp_get_raw(0).asks_base_lots, 0);
assert_eq!(maker.perp_get_raw(0).taker_base_lots, 0);
assert_eq!(maker.perp_get_raw(0).taker_quote_lots, 0);
assert_eq!(maker.perp_get_raw(0).base_position_lots, 0);
assert_eq!(
maker.perp_get_raw(0).quote_position_native.to_num::<u32>(),
0
);
assert_eq!(event_queue.len(), 0);
// Take the order partially
@ -241,7 +254,7 @@ mod tests {
&mut market,
&mut event_queue,
oracle_price,
&mut taker,
&mut taker.borrow_mut(),
&taker_pk,
price,
match_quantity,
@ -255,7 +268,7 @@ mod tests {
.unwrap();
// the remainder of the maker order is still on the book
// (the maker account is unchanged: it was not even passed in)
let order = bookside_leaf_by_key(&book.bids, maker.order_id[0]).unwrap();
let order = bookside_leaf_by_key(&book.bids, maker.perp_oo_get_raw(0).order_id).unwrap();
assert_eq!(order.price(), price);
assert_eq!(order.quantity, bid_quantity - match_quantity);
@ -267,14 +280,17 @@ mod tests {
);
// the taker account is updated
assert_eq!(taker.order_market[0], FREE_ORDER_SLOT);
assert_eq!(taker.accounts[0].bids_base_lots, 0);
assert_eq!(taker.accounts[0].asks_base_lots, 0);
assert_eq!(taker.accounts[0].taker_base_lots, -match_quantity);
assert_eq!(taker.accounts[0].taker_quote_lots, match_quantity * price);
assert_eq!(taker.accounts[0].base_position_lots, 0);
assert_eq!(taker.perp_oo_get_raw(0).order_market, FREE_ORDER_SLOT);
assert_eq!(taker.perp_get_raw(0).bids_base_lots, 0);
assert_eq!(taker.perp_get_raw(0).asks_base_lots, 0);
assert_eq!(taker.perp_get_raw(0).taker_base_lots, -match_quantity);
assert_eq!(
taker.accounts[0].quote_position_native,
taker.perp_get_raw(0).taker_quote_lots,
match_quantity * price
);
assert_eq!(taker.perp_get_raw(0).base_position_lots, 0);
assert_eq!(
taker.perp_get_raw(0).quote_position_native,
-match_quote * market.taker_fee
);
@ -294,34 +310,34 @@ mod tests {
// simulate event queue processing
maker
.execute_maker(market.perp_market_index, &mut market, &fill)
.perp_execute_maker(market.perp_market_index, &mut market, &fill)
.unwrap();
taker
.execute_taker(market.perp_market_index, &mut market, &fill)
.perp_execute_taker(market.perp_market_index, &mut market, &fill)
.unwrap();
assert_eq!(market.open_interest, 2 * match_quantity);
assert_eq!(maker.order_market[0], 0);
assert_eq!(maker.perp_oo_get_raw(0).order_market, 0);
assert_eq!(
maker.accounts[0].bids_base_lots,
maker.perp_get_raw(0).bids_base_lots,
bid_quantity - match_quantity
);
assert_eq!(maker.accounts[0].asks_base_lots, 0);
assert_eq!(maker.accounts[0].taker_base_lots, 0);
assert_eq!(maker.accounts[0].taker_quote_lots, 0);
assert_eq!(maker.accounts[0].base_position_lots, match_quantity);
assert_eq!(maker.perp_get_raw(0).asks_base_lots, 0);
assert_eq!(maker.perp_get_raw(0).taker_base_lots, 0);
assert_eq!(maker.perp_get_raw(0).taker_quote_lots, 0);
assert_eq!(maker.perp_get_raw(0).base_position_lots, match_quantity);
assert_eq!(
maker.accounts[0].quote_position_native,
maker.perp_get_raw(0).quote_position_native,
-match_quote - match_quote * market.maker_fee
);
assert_eq!(taker.accounts[0].bids_base_lots, 0);
assert_eq!(taker.accounts[0].asks_base_lots, 0);
assert_eq!(taker.accounts[0].taker_base_lots, 0);
assert_eq!(taker.accounts[0].taker_quote_lots, 0);
assert_eq!(taker.accounts[0].base_position_lots, -match_quantity);
assert_eq!(taker.perp_get_raw(0).bids_base_lots, 0);
assert_eq!(taker.perp_get_raw(0).asks_base_lots, 0);
assert_eq!(taker.perp_get_raw(0).taker_base_lots, 0);
assert_eq!(taker.perp_get_raw(0).taker_quote_lots, 0);
assert_eq!(taker.perp_get_raw(0).base_position_lots, -match_quantity);
assert_eq!(
taker.accounts[0].quote_position_native,
taker.perp_get_raw(0).quote_position_native,
match_quote - match_quote * market.taker_fee
);
}

View File

@ -9,6 +9,7 @@ use mango_v4::instructions::{
CpiData, FlashLoanWithdraw, InterestRateParams, Serum3OrderType, Serum3SelfTradeBehavior,
Serum3Side,
};
use mango_v4::state::{MangoAccount, MangoAccountValue};
use solana_program::instruction::Instruction;
use solana_program_test::BanksClientError;
use solana_sdk::instruction;
@ -28,6 +29,11 @@ pub trait ClientAccountLoader {
let bytes = self.load_bytes(pubkey).await?;
AccountDeserialize::try_deserialize(&mut &bytes[..]).ok()
}
async fn load_mango_account(&self, pubkey: &Pubkey) -> Option<MangoAccountValue> {
self.load_bytes(pubkey)
.await
.map(|v| MangoAccountValue::from_bytes(&v[8..]).unwrap())
}
}
#[async_trait::async_trait(?Send)]
@ -118,11 +124,15 @@ fn make_instruction(
async fn get_mint_info_by_mint(
account_loader: &impl ClientAccountLoader,
account: &MangoAccount,
account: &MangoAccountValue,
mint: Pubkey,
) -> MintInfo {
let mint_info_pk = Pubkey::find_program_address(
&[account.group.as_ref(), b"MintInfo".as_ref(), mint.as_ref()],
&[
account.fixed.group.as_ref(),
b"MintInfo".as_ref(),
mint.as_ref(),
],
&mango_v4::id(),
)
.0;
@ -131,12 +141,12 @@ async fn get_mint_info_by_mint(
async fn get_mint_info_by_token_index(
account_loader: &impl ClientAccountLoader,
account: &MangoAccount,
account: &MangoAccountValue,
token_index: TokenIndex,
) -> MintInfo {
let bank_pk = Pubkey::find_program_address(
&[
account.group.as_ref(),
account.fixed.group.as_ref(),
b"Bank".as_ref(),
&token_index.to_le_bytes(),
&0u64.to_le_bytes(),
@ -163,7 +173,7 @@ fn get_perp_market_address_by_index(group: Pubkey, perp_market_index: PerpMarket
// all the accounts that instructions like deposit/withdraw need to compute account health
async fn derive_health_check_remaining_account_metas(
account_loader: &impl ClientAccountLoader,
account: &MangoAccount,
account: &MangoAccountValue,
affected_bank: Option<Pubkey>,
writable_banks: bool,
affected_perp_market_index: Option<PerpMarketIndex>,
@ -172,21 +182,19 @@ async fn derive_health_check_remaining_account_metas(
if let Some(affected_bank) = affected_bank {
let bank: Bank = account_loader.load(&affected_bank).await.unwrap();
adjusted_account
.tokens
.get_mut_or_create(bank.token_index)
.token_get_mut_or_create(bank.token_index)
.unwrap();
}
if let Some(affected_perp_market_index) = affected_perp_market_index {
adjusted_account
.perps
.get_account_mut_or_create(affected_perp_market_index)
.perp_get_account_mut_or_create(affected_perp_market_index)
.unwrap();
}
// figure out all the banks/oracles that need to be passed for the health check
let mut banks = vec![];
let mut oracles = vec![];
for position in adjusted_account.tokens.iter_active() {
for position in adjusted_account.token_iter_active() {
let mint_info =
get_mint_info_by_token_index(account_loader, account, position.token_index).await;
// TODO: ALTs are unavailable
@ -202,11 +210,10 @@ async fn derive_health_check_remaining_account_metas(
}
let perp_markets = adjusted_account
.perps
.iter_active_accounts()
.map(|perp| get_perp_market_address_by_index(account.group, perp.market_index));
.perp_iter_active_accounts()
.map(|perp| get_perp_market_address_by_index(account.fixed.group, perp.market_index));
let serum_oos = account.serum3.iter_active().map(|&s| s.open_orders);
let serum_oos = account.serum3_iter_active().map(|&s| s.open_orders);
let to_account_meta = |pubkey| AccountMeta {
pubkey,
@ -229,8 +236,8 @@ async fn derive_health_check_remaining_account_metas(
async fn derive_liquidation_remaining_account_metas(
account_loader: &impl ClientAccountLoader,
liqee: &MangoAccount,
liqor: &MangoAccount,
liqee: &MangoAccountValue,
liqor: &MangoAccountValue,
asset_token_index: TokenIndex,
asset_bank_index: usize,
liab_token_index: TokenIndex,
@ -239,9 +246,8 @@ async fn derive_liquidation_remaining_account_metas(
let mut banks = vec![];
let mut oracles = vec![];
let token_indexes = liqee
.tokens
.iter_active()
.chain(liqor.tokens.iter_active())
.token_iter_active()
.chain(liqor.token_iter_active())
.map(|ta| ta.token_index)
.unique();
for token_index in token_indexes {
@ -269,16 +275,14 @@ async fn derive_liquidation_remaining_account_metas(
}
let perp_markets = liqee
.perps
.iter_active_accounts()
.chain(liqee.perps.iter_active_accounts())
.map(|perp| get_perp_market_address_by_index(liqee.group, perp.market_index))
.perp_iter_active_accounts()
.chain(liqee.perp_iter_active_accounts())
.map(|perp| get_perp_market_address_by_index(liqee.fixed.group, perp.market_index))
.unique();
let serum_oos = liqee
.serum3
.iter_active()
.chain(liqor.serum3.iter_active())
.serum3_iter_active()
.chain(liqor.serum3_iter_active())
.map(|&s| s.open_orders);
let to_account_meta = |pubkey| AccountMeta {
@ -304,29 +308,32 @@ fn from_serum_style_pubkey(d: &[u64; 4]) -> Pubkey {
Pubkey::new(bytemuck::cast_slice(d as &[_]))
}
pub async fn get_mango_account(solana: &SolanaCookie, account: Pubkey) -> MangoAccountValue {
let bytes = solana.get_account_data(account).await.unwrap();
MangoAccountValue::from_bytes(&bytes[8..]).unwrap()
}
pub async fn account_position(solana: &SolanaCookie, account: Pubkey, bank: Pubkey) -> i64 {
let account_data: MangoAccount = solana.get_account(account).await;
let account_data = get_mango_account(solana, account).await;
let bank_data: Bank = solana.get_account(bank).await;
let native = account_data
.tokens
.find(bank_data.token_index)
.token_find(bank_data.token_index)
.unwrap()
.native(&bank_data);
native.round().to_num::<i64>()
}
pub async fn account_position_closed(solana: &SolanaCookie, account: Pubkey, bank: Pubkey) -> bool {
let account_data: MangoAccount = solana.get_account(account).await;
let account_data = get_mango_account(solana, account).await;
let bank_data: Bank = solana.get_account(bank).await;
account_data.tokens.find(bank_data.token_index).is_none()
account_data.token_find(bank_data.token_index).is_none()
}
pub async fn account_position_f64(solana: &SolanaCookie, account: Pubkey, bank: Pubkey) -> f64 {
let account_data: MangoAccount = solana.get_account(account).await;
let account_data = get_mango_account(solana, account).await;
let bank_data: Bank = solana.get_account(bank).await;
let native = account_data
.tokens
.find(bank_data.token_index)
.token_find(bank_data.token_index)
.unwrap()
.native(&bank_data);
native.to_num::<f64>()
@ -358,10 +365,13 @@ impl<'keypair> ClientInstruction for FlashLoanInstruction<'keypair> {
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
owner: self.owner.pubkey(),
token_program: Token::id(),
@ -493,7 +503,10 @@ impl<'keypair> ClientInstruction for FlashLoan2EndInstruction<'keypair> {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
@ -505,7 +518,7 @@ impl<'keypair> ClientInstruction for FlashLoan2EndInstruction<'keypair> {
.await;
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
owner: self.owner.pubkey(),
token_program: Token::id(),
@ -597,7 +610,10 @@ impl<'keypair> ClientInstruction for FlashLoan3EndInstruction<'keypair> {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
@ -660,10 +676,13 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
// load accounts, find PDAs, find remainingAccounts
let token_account: TokenAccount = account_loader.load(&self.token_account).await.unwrap();
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let mint_info = Pubkey::find_program_address(
&[
account.group.as_ref(),
account.fixed.group.as_ref(),
b"MintInfo".as_ref(),
token_account.mint.as_ref(),
],
@ -682,7 +701,7 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
.await;
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
owner: self.owner.pubkey(),
bank: mint_info.banks[self.bank_index],
@ -725,10 +744,13 @@ impl ClientInstruction for TokenDepositInstruction {
// load account so we know its mint
let token_account: TokenAccount = account_loader.load(&self.token_account).await.unwrap();
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let mint_info = Pubkey::find_program_address(
&[
account.group.as_ref(),
account.fixed.group.as_ref(),
b"MintInfo".as_ref(),
token_account.mint.as_ref(),
],
@ -747,7 +769,7 @@ impl ClientInstruction for TokenDepositInstruction {
.await;
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
bank: mint_info.banks[self.bank_index],
vault: mint_info.vaults[self.bank_index],
@ -800,7 +822,11 @@ impl<'keypair> ClientInstruction for TokenRegisterInstruction<'keypair> {
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
name: "some_ticker".to_string(),
name: format!(
"{}{}",
"some_ticker".to_string(),
self.token_index.to_string()
),
token_index: self.token_index,
bank_num: 0,
oracle_config: OracleConfig {
@ -1276,7 +1302,7 @@ impl<'keypair> ClientInstruction for GroupCloseInstruction<'keypair> {
pub struct AccountCreateInstruction<'keypair> {
pub account_num: u8,
pub account_size: AccountSize,
pub group: Pubkey,
pub owner: &'keypair Keypair,
pub payer: &'keypair Keypair,
@ -1292,6 +1318,7 @@ impl<'keypair> ClientInstruction for AccountCreateInstruction<'keypair> {
let program_id = mango_v4::id();
let instruction = mango_v4::instruction::AccountCreate {
account_num: self.account_num,
account_size: self.account_size,
name: "my_mango_account".to_string(),
};
@ -1323,6 +1350,51 @@ impl<'keypair> ClientInstruction for AccountCreateInstruction<'keypair> {
}
}
pub struct AccountExpandInstruction<'keypair> {
pub account_num: u8,
pub group: Pubkey,
pub owner: &'keypair Keypair,
pub payer: &'keypair Keypair,
}
#[async_trait::async_trait(?Send)]
impl<'keypair> ClientInstruction for AccountExpandInstruction<'keypair> {
type Accounts = mango_v4::accounts::AccountExpand;
type Instruction = mango_v4::instruction::AccountExpand;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = mango_v4::instruction::AccountExpand {};
let account = Pubkey::find_program_address(
&[
self.group.as_ref(),
b"MangoAccount".as_ref(),
self.owner.pubkey().as_ref(),
&self.account_num.to_le_bytes(),
],
&program_id,
)
.0;
let accounts = mango_v4::accounts::AccountExpand {
group: self.group,
account,
owner: self.owner.pubkey(),
payer: self.payer.pubkey(),
system_program: System::id(),
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<&Keypair> {
vec![self.owner, self.payer]
}
}
pub struct AccountEditInstruction<'keypair> {
pub account_num: u8,
pub group: Pubkey,
@ -1641,11 +1713,13 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
limit: self.limit,
};
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
let open_orders = account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.unwrap()
.open_orders;
let quote_info =
@ -1686,7 +1760,7 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
.await;
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
open_orders,
quote_bank: quote_info.first_bank(),
@ -1741,11 +1815,13 @@ impl<'keypair> ClientInstruction for Serum3CancelOrderInstruction<'keypair> {
order_id: self.order_id,
};
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
let open_orders = account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.unwrap()
.open_orders;
@ -1762,7 +1838,7 @@ impl<'keypair> ClientInstruction for Serum3CancelOrderInstruction<'keypair> {
let event_q = market_external.event_q;
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
open_orders,
serum_market: self.serum_market,
@ -1800,11 +1876,13 @@ impl<'keypair> ClientInstruction for Serum3CancelAllOrdersInstruction<'keypair>
let program_id = mango_v4::id();
let instruction = Self::Instruction { limit: self.limit };
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
let open_orders = account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.unwrap()
.open_orders;
@ -1821,7 +1899,7 @@ impl<'keypair> ClientInstruction for Serum3CancelAllOrdersInstruction<'keypair>
let event_q = market_external.event_q;
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
open_orders,
serum_market: self.serum_market,
@ -1859,11 +1937,13 @@ impl<'keypair> ClientInstruction for Serum3SettleFundsInstruction<'keypair> {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
let open_orders = account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.unwrap()
.open_orders;
let quote_info =
@ -1891,7 +1971,7 @@ impl<'keypair> ClientInstruction for Serum3SettleFundsInstruction<'keypair> {
.unwrap();
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
open_orders,
quote_bank: quote_info.first_bank(),
@ -1933,11 +2013,13 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
let program_id = mango_v4::id();
let instruction = Self::Instruction { limit: self.limit };
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
let open_orders = account
.serum3
.find(serum_market.market_index)
.serum3_find(serum_market.market_index)
.unwrap()
.open_orders;
let quote_info =
@ -1977,7 +2059,7 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
.await;
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
open_orders,
quote_bank: quote_info.first_bank(),
@ -2033,8 +2115,14 @@ impl<'keypair> ClientInstruction for LiqTokenWithTokenInstruction<'keypair> {
max_liab_transfer: self.max_liab_transfer,
};
let liqee: MangoAccount = account_loader.load(&self.liqee).await.unwrap();
let liqor: MangoAccount = account_loader.load(&self.liqor).await.unwrap();
let liqee = account_loader
.load_mango_account(&self.liqee)
.await
.unwrap();
let liqor = account_loader
.load_mango_account(&self.liqor)
.await
.unwrap();
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
&liqee,
@ -2047,7 +2135,7 @@ impl<'keypair> ClientInstruction for LiqTokenWithTokenInstruction<'keypair> {
.await;
let accounts = Self::Accounts {
group: liqee.group,
group: liqee.fixed.group,
liqee: self.liqee,
liqor: self.liqor,
liqor_owner: self.liqor_owner.pubkey(),
@ -2088,8 +2176,14 @@ impl<'keypair> ClientInstruction for LiqTokenBankruptcyInstruction<'keypair> {
};
let liab_mint_info: MintInfo = account_loader.load(&self.liab_mint_info).await.unwrap();
let liqee: MangoAccount = account_loader.load(&self.liqee).await.unwrap();
let liqor: MangoAccount = account_loader.load(&self.liqor).await.unwrap();
let liqee = account_loader
.load_mango_account(&self.liqee)
.await
.unwrap();
let liqor = account_loader
.load_mango_account(&self.liqor)
.await
.unwrap();
let health_check_metas = derive_liquidation_remaining_account_metas(
&account_loader,
&liqee,
@ -2101,11 +2195,12 @@ impl<'keypair> ClientInstruction for LiqTokenBankruptcyInstruction<'keypair> {
)
.await;
let group: Group = account_loader.load(&liqee.group).await.unwrap();
let group_key = liqee.fixed.group;
let group: Group = account_loader.load(&group_key).await.unwrap();
let quote_mint_info = Pubkey::find_program_address(
&[
liqee.group.as_ref(),
liqee.fixed.group.as_ref(),
b"MintInfo".as_ref(),
group.insurance_mint.as_ref(),
],
@ -2115,13 +2210,13 @@ impl<'keypair> ClientInstruction for LiqTokenBankruptcyInstruction<'keypair> {
let quote_mint_info: MintInfo = account_loader.load(&quote_mint_info).await.unwrap();
let insurance_vault = Pubkey::find_program_address(
&[liqee.group.as_ref(), b"InsuranceVault".as_ref()],
&[group_key.as_ref(), b"InsuranceVault".as_ref()],
&program_id,
)
.0;
let accounts = Self::Accounts {
group: liqee.group,
group: group_key,
liqee: self.liqee,
liqor: self.liqor,
liqor_owner: self.liqor_owner.pubkey(),
@ -2321,7 +2416,10 @@ impl<'keypair> ClientInstruction for PerpPlaceOrderInstruction<'keypair> {
};
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
&account,
@ -2605,7 +2703,10 @@ impl ClientInstruction for ComputeAccountDataInstruction {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let health_check_metas = derive_health_check_remaining_account_metas(
&account_loader,
@ -2617,7 +2718,7 @@ impl ClientInstruction for ComputeAccountDataInstruction {
.await;
let accounts = Self::Accounts {
group: account.group,
group: account.fixed.group,
account: self.account,
};

View File

@ -44,6 +44,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 2,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -89,6 +90,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -210,7 +212,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
account_position(solana, account, borrow_token1.bank).await,
(-350.0f64 + (1000.0 / 20.0 / 1.04)).round() as i64
);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
@ -230,13 +232,13 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
)
.await
.unwrap();
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
assert!(account_position_closed(solana, account, collateral_token2.bank).await);
let borrow1_after_liq = -350.0f64 + (1000.0 / 20.0 / 1.04) + (20.0 / 20.0 / 1.04);
assert_eq!(
account_position(solana, account, borrow_token1.bank).await,
borrow1_after_liq.round() as i64
);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
@ -262,7 +264,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
account_position(solana, vault_account, borrow_token1.bank).await,
vault_before + (borrow1_after_liq.round() as i64)
);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
@ -289,7 +291,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
account_position(solana, vault_account, borrow_token2.bank).await,
(vault_amount - borrow2_amount) as i64
);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
@ -351,6 +353,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 2,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -396,6 +399,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -513,7 +517,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
.await
.unwrap();
assert!(account_position_closed(solana, account, collateral_token1.bank).await);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
@ -534,7 +538,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
.await
.unwrap();
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
@ -559,7 +563,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
)
.await
.unwrap();
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
@ -592,7 +596,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
)
.await
.unwrap();
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
@ -627,7 +631,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
)
.await
.unwrap();
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);

View File

@ -40,6 +40,20 @@ async fn test_basic() -> Result<(), TransportError> {
let account = send_tx(
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Small,
group,
owner,
payer,
},
)
.await
.unwrap()
.account;
send_tx(
solana,
AccountExpandInstruction {
account_num: 0,
group,
owner,

View File

@ -37,6 +37,7 @@ async fn test_delegate() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,

View File

@ -40,6 +40,7 @@ async fn test_group_address_lookup_tables() -> Result<()> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,

View File

@ -37,6 +37,7 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -101,6 +102,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -212,6 +214,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,

View File

@ -42,6 +42,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 2,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -97,6 +98,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -251,6 +253,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 2,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -281,6 +284,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -388,7 +392,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
-50 + 19
);
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
@ -418,7 +422,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
account_position(solana, account, collateral_token1.bank).await,
1000 - 32
);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
@ -450,7 +454,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
account_position(solana, account, collateral_token1.bank).await,
1000 - 32 - 21
);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
@ -485,7 +489,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
account_position(solana, account, collateral_token1.bank).await,
1000 - 32 - 535 - 1
);
let liqee: MangoAccount = solana.get_account(account).await;
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());

View File

@ -54,6 +54,7 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -96,6 +97,7 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -227,8 +229,8 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
margin_account_initial + withdraw_amount
);
// Check that position is fully deactivated
let account_data: MangoAccount = solana.get_account(account).await;
assert_eq!(account_data.tokens.iter_active().count(), 0);
let account_data = get_mango_account(solana, account).await;
assert_eq!(account_data.token_iter_active().count(), 0);
//
// TEST: Activating a token via margin trade
@ -370,6 +372,7 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -412,6 +415,7 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -540,8 +544,8 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
margin_account_initial + withdraw_amount
);
// Check that position is fully deactivated
let account_data: MangoAccount = solana.get_account(account).await;
assert_eq!(account_data.tokens.iter_active().count(), 0);
let account_data = get_mango_account(solana, account).await;
assert_eq!(account_data.token_iter_active().count(), 0);
//
// TEST: Activating a token via margin trade
@ -631,6 +635,7 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -673,6 +678,7 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -801,8 +807,8 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
margin_account_initial + withdraw_amount
);
// Check that position is fully deactivated
let account_data: MangoAccount = solana.get_account(account).await;
assert_eq!(account_data.tokens.iter_active().count(), 0);
let account_data = get_mango_account(solana, account).await;
assert_eq!(account_data.token_iter_active().count(), 0);
//
// TEST: Activating a token via margin trade

View File

@ -36,6 +36,7 @@ async fn test_perp() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -49,6 +50,7 @@ async fn test_perp() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -200,8 +202,8 @@ async fn test_perp() -> Result<(), TransportError> {
let order_id_to_cancel = solana
.get_account::<MangoAccount>(account_0)
.await
.perps
.order_id[0];
.perp_open_orders[0]
.order_id;
send_tx(
solana,
PerpCancelOrderInstruction {
@ -398,12 +400,12 @@ async fn test_perp() -> Result<(), TransportError> {
.unwrap();
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
assert_eq!(mango_account_0.perps.accounts[0].base_position_lots, 1);
assert!(mango_account_0.perps.accounts[0].quote_position_native < -100.019);
assert_eq!(mango_account_0.perps[0].base_position_lots, 1);
assert!(mango_account_0.perps[0].quote_position_native < -100.019);
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
assert_eq!(mango_account_1.perps.accounts[0].base_position_lots, -1);
assert_eq!(mango_account_1.perps.accounts[0].quote_position_native, 100);
assert_eq!(mango_account_1.perps[0].base_position_lots, -1);
assert_eq!(mango_account_1.perps[0].quote_position_native, 100);
send_tx(
solana,
@ -426,10 +428,10 @@ async fn test_perp() -> Result<(), TransportError> {
async fn assert_no_perp_orders(solana: &SolanaCookie, account_0: Pubkey) {
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
for i in 0..MAX_PERP_OPEN_ORDERS {
assert!(mango_account_0.perps.order_id[i] == 0);
assert!(mango_account_0.perps.order_side[i] == Side::Bid);
assert!(mango_account_0.perps.client_order_id[i] == 0);
assert!(mango_account_0.perps.order_market[i] == FREE_ORDER_SLOT);
for oo in mango_account_0.perp_open_orders.iter() {
assert!(oo.order_id == 0);
assert!(oo.order_side == Side::Bid);
assert!(oo.client_order_id == 0);
assert!(oo.order_market == FREE_ORDER_SLOT);
}
}

View File

@ -38,6 +38,7 @@ async fn test_position_lifetime() -> Result<()> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -51,6 +52,7 @@ async fn test_position_lifetime() -> Result<()> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -122,8 +124,8 @@ async fn test_position_lifetime() -> Result<()> {
}
// Check that positions are fully deactivated
let account: MangoAccount = solana.get_account(account).await;
assert_eq!(account.tokens.iter_active().count(), 0);
let account = get_mango_account(solana, account).await;
assert_eq!(account.token_iter_active().count(), 0);
// No user tokens got lost
for &payer_token in payer_mint_accounts {
@ -222,8 +224,8 @@ async fn test_position_lifetime() -> Result<()> {
.unwrap();
// Check that positions are fully deactivated
let account: MangoAccount = solana.get_account(account).await;
assert_eq!(account.tokens.iter_active().count(), 0);
let account = get_mango_account(solana, account).await;
assert_eq!(account.token_iter_active().count(), 0);
// No user tokens got lost
// TODO: -1 is a workaround for rounding down in withdraw

View File

@ -40,6 +40,7 @@ async fn test_serum() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -126,11 +127,10 @@ async fn test_serum() -> Result<(), TransportError> {
.unwrap()
.open_orders;
let account_data: MangoAccount = solana.get_account(account).await;
let account_data = get_mango_account(solana, account).await;
assert_eq!(
account_data
.serum3
.iter_active()
.serum3_iter_active()
.map(|v| (v.open_orders, v.market_index))
.collect::<Vec<_>>(),
[(open_orders, 0)]

View File

@ -36,6 +36,7 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
group,
owner,
payer,
@ -63,6 +64,7 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
group,
owner,
payer,

View File

@ -18,6 +18,6 @@ default = []
test-bpf = []
[dependencies]
anchor-lang = { version = "0.25.0", features = [] }
anchor-spl = "0.25.0"
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
anchor-spl = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
solana-program = "1.10.29"

View File

@ -2,12 +2,19 @@
set -e pipefail
ANCHOR_BRANCH=v0.25.0-mangov4
ANCHOR_FORK=$(cd ../anchor && git rev-parse --abbrev-ref HEAD)
if [ "$ANCHOR_FORK" != "$ANCHOR_BRANCH" ]; then
echo "Check out anchor fork at git@github.com:blockworks-foundation/anchor.git, and switch to branch $ANCHOR_BRANCH!"
exit 1;
fi
WALLET_WITH_FUNDS=~/.config/solana/mango-devnet.json
PROGRAM_ID=m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD
# TODO fix need for --skip-lint
# build program,
anchor build --skip-lint
cargo run --manifest-path ../anchor/cli/Cargo.toml build --skip-lint
# patch types, which we want in rust, but anchor client doesn't support
./idl-fixup.sh
@ -23,12 +30,12 @@ if [[ -z "${NO_DEPLOY}" ]]; then
-k $WALLET_WITH_FUNDS target/deploy/mango_v4.so --skip-fee-check
# # publish idl
# anchor idl upgrade --provider.cluster https://mango.devnet.rpcpool.com --provider.wallet $WALLET_WITH_FUNDS \
# --filepath target/idl/mango_v4.json $PROGRAM_ID
cargo run --manifest-path ../anchor/cli/Cargo.toml idl upgrade --provider.cluster https://mango.devnet.rpcpool.com --provider.wallet $WALLET_WITH_FUNDS \
--filepath target/idl/mango_v4.json $PROGRAM_ID
else
echo "Skipping deployment..."
fi
# # build npm package
# (cd ./ts/client && tsc)
# build npm package
(cd ./ts/client && tsc)

View File

@ -2,6 +2,13 @@
set -e pipefail
ANCHOR_BRANCH=v0.25.0-mangov4
ANCHOR_FORK=$(cd ../anchor && git rev-parse --abbrev-ref HEAD)
if [ "$ANCHOR_FORK" != "$ANCHOR_BRANCH" ]; then
echo "Check out anchor fork at git@github.com:blockworks-foundation/anchor.git, and switch to branch $ANCHOR_BRANCH!"
exit 1;
fi
WALLET_WITH_FUNDS=~/.config/solana/mango-mainnet.json
PROGRAM_ID=m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD

View File

@ -23,6 +23,7 @@ export class MangoAccount {
tokens: unknown;
serum3: Object;
perps: unknown;
perpOpenOrders: unknown;
beingLiquidated: number;
isBankrupt: number;
accountNum: number;
@ -36,9 +37,10 @@ export class MangoAccount {
obj.group,
obj.owner,
obj.delegate,
obj.tokens as { values: TokenPositionDto[] },
obj.serum3 as { values: Serum3PositionDto[] },
obj.perps as { accounts: PerpPositionDto[] },
obj.tokens as TokenPositionDto[],
obj.serum3 as Serum3PositionDto[],
obj.perps as PerpPositionDto[],
obj.perpOpenOrders as any, // TODO
obj.beingLiquidated,
obj.isBankrupt,
obj.accountNum,
@ -54,9 +56,10 @@ export class MangoAccount {
public group: PublicKey,
public owner: PublicKey,
public delegate: PublicKey,
tokens: { values: TokenPositionDto[] },
serum3: { values: Serum3PositionDto[] },
perps: { accounts: PerpPositionDto[] },
tokens: TokenPositionDto[],
serum3: Serum3PositionDto[],
perps: PerpPositionDto[],
perpOpenOrders: PerpPositionDto[],
beingLiquidated: number,
isBankrupt: number,
accountNum: number,
@ -65,9 +68,9 @@ export class MangoAccount {
public accountData: {},
) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.tokens = tokens.values.map((dto) => TokenPosition.from(dto));
this.serum3 = serum3.values.map((dto) => Serum3Orders.from(dto));
this.perps = perps.accounts.map((dto) => PerpPositions.from(dto));
this.tokens = tokens.map((dto) => TokenPosition.from(dto));
this.serum3 = serum3.map((dto) => Serum3Orders.from(dto));
this.perps = perps.map((dto) => PerpPositions.from(dto));
}
async reload(client: MangoClient, group: Group) {
@ -584,3 +587,8 @@ export class EquityDto {
tokens: { tokenIndex: number; value: I80F48Dto }[];
perps: { perpMarketIndex: number; value: I80F48Dto }[];
}
export class AccountSize {
static small = { small: {} };
static large = { large: {} };
}

View File

@ -30,7 +30,11 @@ import bs58 from 'bs58';
import { Bank, MintInfo } from './accounts/bank';
import { Group } from './accounts/group';
import { I80F48 } from './accounts/I80F48';
import { MangoAccount, MangoAccountData } from './accounts/mangoAccount';
import {
AccountSize,
MangoAccount,
MangoAccountData,
} from './accounts/mangoAccount';
import { StubOracle } from './accounts/oracle';
import { OrderType, PerpMarket, Side } from './accounts/perp';
import {
@ -424,11 +428,17 @@ export class MangoClient {
group: Group,
ownerPk: PublicKey,
accountNumber?: number,
accountSize?: AccountSize,
name?: string,
): Promise<MangoAccount> {
let mangoAccounts = await this.getMangoAccountForOwner(group, ownerPk);
if (mangoAccounts.length === 0) {
await this.createMangoAccount(group, accountNumber ?? 0, name ?? '');
await this.createMangoAccount(
group,
accountNumber ?? 0,
accountSize ?? AccountSize.small,
name ?? '',
);
mangoAccounts = await this.getMangoAccountForOwner(group, ownerPk);
}
return mangoAccounts[0];
@ -437,10 +447,11 @@ export class MangoClient {
public async createMangoAccount(
group: Group,
accountNumber: number,
accountSize: AccountSize,
name?: string,
): Promise<TransactionSignature> {
return await this.program.methods
.accountCreate(accountNumber, name ?? '')
.accountCreate(accountNumber, accountSize, name ?? '')
.accounts({
group: group.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
@ -449,6 +460,21 @@ export class MangoClient {
.rpc();
}
public async expandMangoAccount(
group: Group,
account: MangoAccount,
): Promise<TransactionSignature> {
return await this.program.methods
.accountExpand()
.accounts({
group: group.publicKey,
account: account.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
payer: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.rpc();
}
public async editMangoAccount(
group: Group,
mangoAccount: MangoAccount,
@ -462,7 +488,7 @@ export class MangoClient {
account: mangoAccount.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.rpc();
.rpc({ skipPreflight: true });
}
public async getMangoAccount(mangoAccount: MangoAccount) {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import fs from 'fs';
import { AccountSize } from '../accounts/mangoAccount';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
@ -44,6 +45,7 @@ async function main() {
group,
user.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);

View File

@ -1,7 +1,7 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { HealthType } from '../accounts/mangoAccount';
import { AccountSize, HealthType } from '../accounts/mangoAccount';
import { OrderType, Side } from '../accounts/perp';
import {
Serum3OrderType,
@ -59,6 +59,7 @@ async function main() {
group,
user.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
@ -92,13 +93,18 @@ async function main() {
if (true) {
// deposit and withdraw
console.log(`...depositing 50 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.0005 BTC`);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
await mangoAccount.reload(client, group);
try {
console.log(`...depositing 50 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.0005 BTC`);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
await mangoAccount.reload(client, group);
} catch (error) {
console.log(error);
}
// witdrawing fails if no (other) user has deposited ORCA in the group
// console.log(`Withdrawing...0.1 ORCA`);

View File

@ -46,6 +46,7 @@ async function main() {
group,
user.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);

View File

@ -61,6 +61,7 @@ async function main() {
group,
user.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);

View File

@ -40,6 +40,7 @@ async function main() {
group,
user1.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
@ -72,6 +73,7 @@ async function main() {
group,
user2.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...mangoAccount2 ${user2MangoAccount.publicKey}`);

View File

@ -46,6 +46,7 @@ async function main() {
group,
user.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);

View File

@ -47,6 +47,7 @@ async function main() {
group,
user.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);

View File

@ -2,9 +2,16 @@
set -e pipefail
ANCHOR_BRANCH=v0.25.0-mangov4
ANCHOR_FORK=$(cd ../anchor && git rev-parse --abbrev-ref HEAD)
if [ "$ANCHOR_FORK" != "$ANCHOR_BRANCH" ]; then
echo "Check out anchor fork at git@github.com:blockworks-foundation/anchor.git, and switch to branch $ANCHOR_BRANCH!"
exit 1;
fi
# TODO fix need for --skip-lint
# build program,
anchor build --skip-lint
cargo run --manifest-path ../anchor/cli/Cargo.toml build --skip-lint
# patch types, which we want in rust, but anchor client doesn't support
./idl-fixup.sh