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:
parent
46b6bce14b
commit
0b2e1e6e6c
|
@ -107,7 +107,20 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70f6ee9518f50ff4d434471ccf569186022bdd5ef65a21d14da3ea5231af944f"
|
checksum = "70f6ee9518f50ff4d434471ccf569186022bdd5ef65a21d14da3ea5231af944f"
|
||||||
dependencies = [
|
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",
|
"anyhow",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
|
@ -121,7 +134,21 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32c92bcf5388b52676d990f85bbfd838a8f5672393135063a50dc79b2b837c79"
|
checksum = "32c92bcf5388b52676d990f85bbfd838a8f5672393135063a50dc79b2b837c79"
|
||||||
dependencies = [
|
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",
|
"anyhow",
|
||||||
"bs58 0.4.0",
|
"bs58 0.4.0",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
|
@ -136,7 +163,17 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0844974ac35e8ced62056b0d63777ebcdc5807438b8b189c881e2b647450b70a"
|
checksum = "0844974ac35e8ced62056b0d63777ebcdc5807438b8b189c881e2b647450b70a"
|
||||||
dependencies = [
|
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",
|
"proc-macro2 1.0.39",
|
||||||
"syn 1.0.95",
|
"syn 1.0.95",
|
||||||
]
|
]
|
||||||
|
@ -147,7 +184,18 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f7467345e67a6f1d4b862b9763a4160ad89d18c247b8c902807768f7b6e23df"
|
checksum = "0f7467345e67a6f1d4b862b9763a4160ad89d18c247b8c902807768f7b6e23df"
|
||||||
dependencies = [
|
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",
|
"proc-macro2 1.0.39",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
"syn 1.0.95",
|
"syn 1.0.95",
|
||||||
|
@ -159,7 +207,19 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8774e4c1ac71f71a5aea7e4932fb69c30e3b8155c4fa59fd69401195434528a9"
|
checksum = "8774e4c1ac71f71a5aea7e4932fb69c30e3b8155c4fa59fd69401195434528a9"
|
||||||
dependencies = [
|
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",
|
"anyhow",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
|
@ -172,7 +232,20 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90eeb6e1c80f9f94fcef93a52813f6472186200e275e83cb3fac92b801de92f7"
|
checksum = "90eeb6e1c80f9f94fcef93a52813f6472186200e275e83cb3fac92b801de92f7"
|
||||||
dependencies = [
|
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",
|
"anyhow",
|
||||||
"heck 0.3.3",
|
"heck 0.3.3",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
|
@ -186,7 +259,19 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac515a7a5a4fea7fc768b1cec40ddb948e148ea657637c75f94f283212326cb9"
|
checksum = "ac515a7a5a4fea7fc768b1cec40ddb948e148ea657637c75f94f283212326cb9"
|
||||||
dependencies = [
|
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",
|
"anyhow",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
|
@ -199,7 +284,19 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43dc667b62ff71450f19dcfcc37b0c408fd4ddd89e8650368c2b0984b110603f"
|
checksum = "43dc667b62ff71450f19dcfcc37b0c408fd4ddd89e8650368c2b0984b110603f"
|
||||||
dependencies = [
|
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",
|
"anyhow",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
|
@ -209,10 +306,9 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anchor-client"
|
name = "anchor-client"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab#1153380487706e4d8f486071cf2f519468438eab"
|
||||||
checksum = "ee0e630f9310a0134c92df4458890a0f9c5b662d69c305690af1c17f5cd0b3ba"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-lang",
|
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -229,7 +325,19 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7354d583a06701d24800a8ec4c2b0491f62581a331af349205e23421e0b56643"
|
checksum = "7354d583a06701d24800a8ec4c2b0491f62581a331af349205e23421e0b56643"
|
||||||
dependencies = [
|
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",
|
"anyhow",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
|
@ -242,15 +350,38 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff5f57ec5e12fa6874b27f3d5c1f6f44302d3ad86c1266197ff7611bf6f5d251"
|
checksum = "ff5f57ec5e12fa6874b27f3d5c1f6f44302d3ad86c1266197ff7611bf6f5d251"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-attribute-access-control",
|
"anchor-attribute-access-control 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-attribute-account",
|
"anchor-attribute-account 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-attribute-constant",
|
"anchor-attribute-constant 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-attribute-error",
|
"anchor-attribute-error 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-attribute-event",
|
"anchor-attribute-event 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-attribute-interface",
|
"anchor-attribute-interface 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-attribute-program",
|
"anchor-attribute-program 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-attribute-state",
|
"anchor-attribute-state 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-derive-accounts",
|
"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",
|
"arrayref",
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
@ -266,7 +397,18 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d65904c3106851f6d1bb87d504044764819d69c51d2b4346d59d399d8afa7d18"
|
checksum = "d65904c3106851f6d1bb87d504044764819d69c51d2b4346d59d399d8afa7d18"
|
||||||
dependencies = [
|
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",
|
"solana-program",
|
||||||
"spl-associated-token-account",
|
"spl-associated-token-account",
|
||||||
"spl-token",
|
"spl-token",
|
||||||
|
@ -291,6 +433,24 @@ dependencies = [
|
||||||
"thiserror",
|
"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]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -1039,8 +1199,8 @@ name = "client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-client",
|
"anchor-client",
|
||||||
"anchor-lang",
|
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"anchor-spl",
|
"anchor-spl 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"fixed",
|
"fixed",
|
||||||
"fixed-macro",
|
"fixed-macro",
|
||||||
|
@ -1048,6 +1208,7 @@ dependencies = [
|
||||||
"mango-v4",
|
"mango-v4",
|
||||||
"pyth-sdk-solana",
|
"pyth-sdk-solana",
|
||||||
"serum_dex",
|
"serum_dex",
|
||||||
|
"solana-account-decoder",
|
||||||
"solana-client",
|
"solana-client",
|
||||||
"solana-sdk",
|
"solana-sdk",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -2662,8 +2823,8 @@ name = "keeper"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-client",
|
"anchor-client",
|
||||||
"anchor-lang",
|
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"anchor-spl",
|
"anchor-spl 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap 3.1.18",
|
"clap 3.1.18",
|
||||||
"client",
|
"client",
|
||||||
|
@ -2816,7 +2977,7 @@ name = "liquidator"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-client",
|
"anchor-client",
|
||||||
"anchor-lang",
|
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
@ -2935,8 +3096,8 @@ dependencies = [
|
||||||
name = "mango-v4"
|
name = "mango-v4"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-lang",
|
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"anchor-spl",
|
"anchor-spl 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
|
@ -2972,8 +3133,8 @@ dependencies = [
|
||||||
name = "margin-trade"
|
name = "margin-trade"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-lang",
|
"anchor-lang 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"anchor-spl",
|
"anchor-spl 0.25.0 (git+https://github.com/blockworks-foundation/anchor.git?rev=1153380487706e4d8f486071cf2f519468438eab)",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -6352,8 +6513,8 @@ version = "0.1.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e01007eb005d7b5d4b6daaec7db6fe0b5ce9a0c6cb28b00d5c94dc7bfacc075"
|
checksum = "1e01007eb005d7b5d4b6daaec7db6fe0b5ce9a0c6cb28b00d5c94dc7bfacc075"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anchor-lang",
|
"anchor-lang 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"anchor-spl",
|
"anchor-spl 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
|
|
|
@ -7,9 +7,9 @@ edition = "2021"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anchor-client = "0.25.0"
|
anchor-client = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anchor-lang = "0.25.0"
|
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anchor-spl = "0.25.0"
|
anchor-spl = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
||||||
fixed-macro = "^1.1.1"
|
fixed-macro = "^1.1.1"
|
||||||
|
@ -17,6 +17,7 @@ itertools = "0.10.3"
|
||||||
mango-v4 = { path = "../programs/mango-v4" }
|
mango-v4 = { path = "../programs/mango-v4" }
|
||||||
pyth-sdk-solana = "0.1.0"
|
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"] }
|
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-client = "~1.10.29"
|
||||||
solana-sdk = "~1.10.29"
|
solana-sdk = "~1.10.29"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use anchor_client::ClientError;
|
use anyhow::Context;
|
||||||
|
|
||||||
|
use anchor_client::ClientError;
|
||||||
use anchor_lang::AccountDeserialize;
|
use anchor_lang::AccountDeserialize;
|
||||||
|
|
||||||
use solana_client::rpc_client::RpcClient;
|
use solana_client::rpc_client::RpcClient;
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
use mango_v4::state::MangoAccountValue;
|
||||||
|
|
||||||
pub trait AccountFetcher: Sync + Send {
|
pub trait AccountFetcher: Sync + Send {
|
||||||
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<Account>;
|
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> {
|
) -> anyhow::Result<T> {
|
||||||
let account = fetcher.fetch_raw_account(address)?;
|
let account = fetcher.fetch_raw_account(address)?;
|
||||||
let mut data: &[u8] = &account.data;
|
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 {
|
pub struct RpcAccountFetcher {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use anchor_spl::token::Token;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
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;
|
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 mut mango_account_tuples = fetch_mango_accounts(&program, group, payer.pubkey())?;
|
||||||
let mango_account_opt = mango_account_tuples
|
let mango_account_opt = mango_account_tuples
|
||||||
.iter()
|
.iter()
|
||||||
.find(|tuple| tuple.1.name() == mango_account_name);
|
.find(|(_, account)| account.fixed.name() == mango_account_name);
|
||||||
if mango_account_opt.is_none() {
|
if mango_account_opt.is_none() {
|
||||||
mango_account_tuples
|
mango_account_tuples.sort_by(|a, b| {
|
||||||
.sort_by(|a, b| a.1.account_num.partial_cmp(&b.1.account_num).unwrap());
|
a.1.fixed
|
||||||
|
.account_num
|
||||||
|
.partial_cmp(&b.1.fixed.account_num)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
let account_num = match mango_account_tuples.last() {
|
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,
|
None => 0u8,
|
||||||
};
|
};
|
||||||
program
|
program
|
||||||
|
@ -137,6 +141,7 @@ impl MangoClient {
|
||||||
&mango_v4::instruction::AccountCreate {
|
&mango_v4::instruction::AccountCreate {
|
||||||
account_num,
|
account_num,
|
||||||
name: mango_account_name.to_owned(),
|
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 mango_account_tuples = fetch_mango_accounts(&program, group, payer.pubkey())?;
|
||||||
let index = mango_account_tuples
|
let index = mango_account_tuples
|
||||||
.iter()
|
.iter()
|
||||||
.position(|tuple| tuple.1.name() == mango_account_name)
|
.position(|tuple| tuple.1.fixed.name() == mango_account_name)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mango_account_cache = mango_account_tuples[index];
|
let mango_account_cache = &mango_account_tuples[index];
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
rpc,
|
rpc,
|
||||||
|
@ -181,8 +186,8 @@ impl MangoClient {
|
||||||
self.context.group
|
self.context.group
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mango_account(&self) -> anyhow::Result<MangoAccount> {
|
pub fn mango_account(&self) -> anyhow::Result<MangoAccountValue> {
|
||||||
account_fetcher_fetch_anchor_account(&*self.account_fetcher, self.mango_account_address)
|
account_fetcher_fetch_mango_account(&*self.account_fetcher, self.mango_account_address)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn first_bank(&self, token_index: TokenIndex) -> anyhow::Result<Bank> {
|
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(
|
pub fn derive_liquidation_health_check_remaining_account_metas(
|
||||||
&self,
|
&self,
|
||||||
liqee: &MangoAccount,
|
liqee: &MangoAccountValue,
|
||||||
asset_token_index: TokenIndex,
|
asset_token_index: TokenIndex,
|
||||||
liab_token_index: TokenIndex,
|
liab_token_index: TokenIndex,
|
||||||
) -> anyhow::Result<Vec<AccountMeta>> {
|
) -> anyhow::Result<Vec<AccountMeta>> {
|
||||||
|
@ -215,9 +220,8 @@ impl MangoClient {
|
||||||
let account = self.mango_account()?;
|
let account = self.mango_account()?;
|
||||||
|
|
||||||
let token_indexes = liqee
|
let token_indexes = liqee
|
||||||
.tokens
|
.token_iter_active()
|
||||||
.iter_active()
|
.chain(account.token_iter_active())
|
||||||
.chain(account.tokens.iter_active())
|
|
||||||
.map(|ta| ta.token_index)
|
.map(|ta| ta.token_index)
|
||||||
.unique();
|
.unique();
|
||||||
|
|
||||||
|
@ -229,14 +233,12 @@ impl MangoClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
let serum_oos = liqee
|
let serum_oos = liqee
|
||||||
.serum3
|
.serum3_iter_active()
|
||||||
.iter_active()
|
.chain(account.serum3_iter_active())
|
||||||
.chain(account.serum3.iter_active())
|
|
||||||
.map(|&s| s.open_orders);
|
.map(|&s| s.open_orders);
|
||||||
let perp_markets = liqee
|
let perp_markets = liqee
|
||||||
.perps
|
.perp_iter_active_accounts()
|
||||||
.iter_active_accounts()
|
.chain(account.perp_iter_active_accounts())
|
||||||
.chain(account.perps.iter_active_accounts())
|
|
||||||
.map(|&pa| self.context.perp_market_address(pa.market_index));
|
.map(|&pa| self.context.perp_market_address(pa.market_index));
|
||||||
|
|
||||||
let to_account_meta = |pubkey| AccountMeta {
|
let to_account_meta = |pubkey| AccountMeta {
|
||||||
|
@ -392,7 +394,7 @@ impl MangoClient {
|
||||||
let s3 = self.serum3_data(name)?;
|
let s3 = self.serum3_data(name)?;
|
||||||
|
|
||||||
let account = self.mango_account()?;
|
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)?;
|
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 s3 = self.serum3_data(name)?;
|
||||||
|
|
||||||
let account = self.mango_account()?;
|
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()
|
self.program()
|
||||||
.request()
|
.request()
|
||||||
|
@ -547,7 +549,7 @@ impl MangoClient {
|
||||||
.get(market_name)
|
.get(market_name)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let account = self.mango_account()?;
|
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_bytes = self.account_fetcher.fetch_raw_account(open_orders)?.data;
|
||||||
let open_orders_data: &serum_dex::state::OpenOrders = bytemuck::from_bytes(
|
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 s3 = self.serum3_data(market_name)?;
|
||||||
|
|
||||||
let account = self.mango_account()?;
|
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()
|
self.program()
|
||||||
.request()
|
.request()
|
||||||
|
@ -622,7 +624,7 @@ impl MangoClient {
|
||||||
|
|
||||||
pub fn liq_token_with_token(
|
pub fn liq_token_with_token(
|
||||||
&self,
|
&self,
|
||||||
liqee: (&Pubkey, &MangoAccount),
|
liqee: (&Pubkey, &MangoAccountValue),
|
||||||
asset_token_index: TokenIndex,
|
asset_token_index: TokenIndex,
|
||||||
liab_token_index: TokenIndex,
|
liab_token_index: TokenIndex,
|
||||||
max_liab_transfer: I80F48,
|
max_liab_transfer: I80F48,
|
||||||
|
@ -666,7 +668,7 @@ impl MangoClient {
|
||||||
|
|
||||||
pub fn liq_token_bankruptcy(
|
pub fn liq_token_bankruptcy(
|
||||||
&self,
|
&self,
|
||||||
liqee: (&Pubkey, &MangoAccount),
|
liqee: (&Pubkey, &MangoAccountValue),
|
||||||
liab_token_index: TokenIndex,
|
liab_token_index: TokenIndex,
|
||||||
max_liab_transfer: I80F48,
|
max_liab_transfer: I80F48,
|
||||||
) -> anyhow::Result<Signature> {
|
) -> anyhow::Result<Signature> {
|
||||||
|
|
|
@ -5,7 +5,7 @@ use anchor_client::{Client, ClientError, Cluster, Program};
|
||||||
use anchor_lang::__private::bytemuck;
|
use anchor_lang::__private::bytemuck;
|
||||||
|
|
||||||
use mango_v4::state::{
|
use mango_v4::state::{
|
||||||
MangoAccount, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market, Serum3MarketIndex,
|
MangoAccountValue, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market, Serum3MarketIndex,
|
||||||
TokenIndex,
|
TokenIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -186,43 +186,36 @@ impl MangoGroupContext {
|
||||||
|
|
||||||
pub fn derive_health_check_remaining_account_metas(
|
pub fn derive_health_check_remaining_account_metas(
|
||||||
&self,
|
&self,
|
||||||
account: &MangoAccount,
|
account: &MangoAccountValue,
|
||||||
affected_token: Option<TokenIndex>,
|
affected_token: Option<TokenIndex>,
|
||||||
writable_banks: bool,
|
writable_banks: bool,
|
||||||
) -> anyhow::Result<Vec<AccountMeta>> {
|
) -> anyhow::Result<Vec<AccountMeta>> {
|
||||||
// figure out all the banks/oracles that need to be passed for the health check
|
// figure out all the banks/oracles that need to be passed for the health check
|
||||||
let mut banks = vec![];
|
let mut banks = vec![];
|
||||||
let mut oracles = 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);
|
let mint_info = self.mint_info(position.token_index);
|
||||||
banks.push(mint_info.first_bank());
|
banks.push(mint_info.first_bank());
|
||||||
oracles.push(mint_info.oracle);
|
oracles.push(mint_info.oracle);
|
||||||
}
|
}
|
||||||
if let Some(affected_token_index) = affected_token {
|
if let Some(affected_token_index) = affected_token {
|
||||||
if account
|
if account
|
||||||
.tokens
|
.token_iter_active()
|
||||||
.iter_active()
|
|
||||||
.find(|p| p.token_index == affected_token_index)
|
.find(|p| p.token_index == affected_token_index)
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
// If there is not yet an active position for the token, we need to pass
|
// If there is not yet an active position for the token, we need to pass
|
||||||
// the bank/oracle for health check anyway.
|
// the bank/oracle for health check anyway.
|
||||||
let new_position = account
|
let new_position = account.token_iter().position(|p| !p.is_active()).unwrap();
|
||||||
.tokens
|
|
||||||
.values
|
|
||||||
.iter()
|
|
||||||
.position(|p| !p.is_active())
|
|
||||||
.unwrap();
|
|
||||||
let mint_info = self.mint_info(affected_token_index);
|
let mint_info = self.mint_info(affected_token_index);
|
||||||
banks.insert(new_position, mint_info.first_bank());
|
banks.insert(new_position, mint_info.first_bank());
|
||||||
oracles.insert(new_position, mint_info.oracle);
|
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
|
let perp_markets = account
|
||||||
.perps
|
.perp_iter_active_accounts()
|
||||||
.iter_active_accounts()
|
|
||||||
.map(|&pa| self.perp_market_address(pa.market_index));
|
.map(|&pa| self.perp_market_address(pa.market_index));
|
||||||
|
|
||||||
Ok(banks
|
Ok(banks
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use anchor_client::{ClientError, Program};
|
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_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType};
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
@ -9,8 +12,14 @@ pub fn fetch_mango_accounts(
|
||||||
program: &Program,
|
program: &Program,
|
||||||
group: Pubkey,
|
group: Pubkey,
|
||||||
owner: Pubkey,
|
owner: Pubkey,
|
||||||
) -> Result<Vec<(Pubkey, MangoAccount)>, ClientError> {
|
) -> Result<Vec<(Pubkey, MangoAccountValue)>, ClientError> {
|
||||||
program.accounts::<MangoAccount>(vec![
|
let config = RpcProgramAccountsConfig {
|
||||||
|
filters: Some(vec![
|
||||||
|
RpcFilterType::Memcmp(Memcmp {
|
||||||
|
offset: 0,
|
||||||
|
bytes: MemcmpEncodedBytes::Bytes(MangoAccount::discriminator().to_vec()),
|
||||||
|
encoding: None,
|
||||||
|
}),
|
||||||
RpcFilterType::Memcmp(Memcmp {
|
RpcFilterType::Memcmp(Memcmp {
|
||||||
offset: 8,
|
offset: 8,
|
||||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||||
|
@ -21,7 +30,19 @@ pub fn fetch_mango_accounts(
|
||||||
bytes: MemcmpEncodedBytes::Base58(owner.to_string()),
|
bytes: MemcmpEncodedBytes::Base58(owner.to_string()),
|
||||||
encoding: None,
|
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> {
|
pub fn fetch_banks(program: &Program, group: Pubkey) -> Result<Vec<(Pubkey, Bank)>, ClientError> {
|
||||||
|
|
|
@ -6,9 +6,9 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anchor-client = "0.25.0"
|
anchor-client = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anchor-lang = "0.25.0"
|
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anchor-spl = "0.25.0"
|
anchor-spl = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
clap = { version = "3.1.8", features = ["derive", "env"] }
|
clap = { version = "3.1.8", features = ["derive", "env"] }
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
|
|
|
@ -77,7 +77,7 @@ fn ensure_oo(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error> {
|
||||||
let account = mango_client.mango_account()?;
|
let account = mango_client.mango_account()?;
|
||||||
|
|
||||||
for (market_index, serum3_market) in mango_client.context.serum3_markets.iter() {
|
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())?;
|
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 bank = mango_client.first_bank(token_index)?;
|
||||||
let desired_balance = I80F48::from_num(10_000 * 10u64.pow(bank.mint_decimals as u32));
|
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 {
|
let deposit_native = match token_account_opt {
|
||||||
Some(token_account) => {
|
Some(token_account) => {
|
||||||
|
|
|
@ -4,8 +4,8 @@ version = "0.0.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anchor-client = "0.25.0"
|
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anchor-lang = "0.25.0"
|
anchor-client = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
arrayref = "0.3.6"
|
arrayref = "0.3.6"
|
||||||
async-channel = "1.6"
|
async-channel = "1.6"
|
||||||
|
|
|
@ -4,11 +4,12 @@ use crate::chain_data::*;
|
||||||
|
|
||||||
use client::AccountFetcher;
|
use client::AccountFetcher;
|
||||||
use mango_v4::accounts_zerocopy::LoadZeroCopy;
|
use mango_v4::accounts_zerocopy::LoadZeroCopy;
|
||||||
|
use mango_v4::state::MangoAccountValue;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
|
||||||
use solana_client::rpc_client::RpcClient;
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_sdk::account::AccountSharedData;
|
use solana_sdk::account::{AccountSharedData, ReadableAccount};
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
pub struct ChainDataAccountFetcher {
|
pub struct ChainDataAccountFetcher {
|
||||||
|
@ -29,6 +30,12 @@ impl ChainDataAccountFetcher {
|
||||||
.clone())
|
.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
|
// fetches via RPC, stores in ChainData, returns new version
|
||||||
pub fn fetch_fresh<T: anchor_lang::ZeroCopy + anchor_lang::Owner>(
|
pub fn fetch_fresh<T: anchor_lang::ZeroCopy + anchor_lang::Owner>(
|
||||||
&self,
|
&self,
|
||||||
|
@ -38,6 +45,11 @@ impl ChainDataAccountFetcher {
|
||||||
self.fetch(address)
|
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> {
|
pub fn fetch_raw(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
|
||||||
let chain_data = self.chain_data.read().unwrap();
|
let chain_data = self.chain_data.read().unwrap();
|
||||||
Ok(chain_data
|
Ok(chain_data
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::ChainDataAccountFetcher;
|
||||||
use client::{AccountFetcher, MangoClient, MangoClientError, MangoGroupContext};
|
use client::{AccountFetcher, MangoClient, MangoClientError, MangoGroupContext};
|
||||||
use mango_v4::state::{
|
use mango_v4::state::{
|
||||||
new_health_cache, oracle_price, Bank, FixedOrderAccountRetriever, HealthCache, HealthType,
|
new_health_cache, oracle_price, Bank, FixedOrderAccountRetriever, HealthCache, HealthType,
|
||||||
MangoAccount, TokenIndex,
|
MangoAccountValue, TokenIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use {anyhow::Context, fixed::types::I80F48, solana_sdk::pubkey::Pubkey};
|
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_(
|
pub fn new_health_cache_(
|
||||||
context: &MangoGroupContext,
|
context: &MangoGroupContext,
|
||||||
account_fetcher: &ChainDataAccountFetcher,
|
account_fetcher: &ChainDataAccountFetcher,
|
||||||
account: &MangoAccount,
|
account: &MangoAccountValue,
|
||||||
) -> anyhow::Result<HealthCache> {
|
) -> anyhow::Result<HealthCache> {
|
||||||
let active_token_len = account.tokens.iter_active().count();
|
let active_token_len = account.token_iter_active().count();
|
||||||
let active_perp_len = account.perps.iter_active_accounts().count();
|
let active_perp_len = account.perp_iter_active_accounts().count();
|
||||||
|
|
||||||
let metas = context.derive_health_check_remaining_account_metas(account, None, false)?;
|
let metas = context.derive_health_check_remaining_account_metas(account, None, false)?;
|
||||||
let accounts = metas
|
let accounts = metas
|
||||||
|
@ -34,7 +34,7 @@ pub fn new_health_cache_(
|
||||||
begin_perp: active_token_len * 2,
|
begin_perp: active_token_len * 2,
|
||||||
begin_serum3: active_token_len * 2 + active_perp_len,
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
@ -47,7 +47,7 @@ pub fn process_account(
|
||||||
let min_health_ratio = I80F48::from_num(50.0f64);
|
let min_health_ratio = I80F48::from_num(50.0f64);
|
||||||
let quote_token_index = 0;
|
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)
|
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
|
||||||
.expect("always ok")
|
.expect("always ok")
|
||||||
.health(HealthType::Maint);
|
.health(HealthType::Maint);
|
||||||
|
@ -59,7 +59,7 @@ pub fn process_account(
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"possible candidate: {}, with owner: {}, maint health: {}, bankrupt: {}",
|
"possible candidate: {}, with owner: {}, maint health: {}, bankrupt: {}",
|
||||||
pubkey,
|
pubkey,
|
||||||
account.owner,
|
account.fixed.owner,
|
||||||
maint_health,
|
maint_health,
|
||||||
account.is_bankrupt(),
|
account.is_bankrupt(),
|
||||||
);
|
);
|
||||||
|
@ -67,15 +67,14 @@ pub fn process_account(
|
||||||
// Fetch a fresh account and re-compute
|
// Fetch a fresh account and re-compute
|
||||||
// This is -- unfortunately -- needed because the websocket streams seem to not
|
// This is -- unfortunately -- needed because the websocket streams seem to not
|
||||||
// be great at providing timely updates to the account data.
|
// 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)
|
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
|
||||||
.expect("always ok")
|
.expect("always ok")
|
||||||
.health(HealthType::Maint);
|
.health(HealthType::Maint);
|
||||||
|
|
||||||
// find asset and liab tokens
|
// find asset and liab tokens
|
||||||
let mut tokens = account
|
let mut tokens = account
|
||||||
.tokens
|
.token_iter_active()
|
||||||
.iter_active()
|
|
||||||
.map(|token_position| {
|
.map(|token_position| {
|
||||||
let token = mango_client.context.token(token_position.token_index);
|
let token = mango_client.context.token(token_position.token_index);
|
||||||
let bank = account_fetcher.fetch::<Bank>(&token.mint_info.first_bank())?;
|
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 get_max_liab_transfer = |source, target| -> anyhow::Result<I80F48> {
|
||||||
let mut liqor = account_fetcher
|
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")?
|
.context("getting liquidator account")?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
// Ensure the tokens are activated, so they appear in the health cache and
|
// Ensure the tokens are activated, so they appear in the health cache and
|
||||||
// max_swap_source() will work.
|
// max_swap_source() will work.
|
||||||
liqor.tokens.get_mut_or_create(source)?;
|
liqor.token_get_mut_or_create(source)?;
|
||||||
liqor.tokens.get_mut_or_create(target)?;
|
liqor.token_get_mut_or_create(target)?;
|
||||||
|
|
||||||
let health_cache =
|
let health_cache =
|
||||||
new_health_cache_(&mango_client.context, account_fetcher, &liqor).expect("always ok");
|
new_health_cache_(&mango_client.context, account_fetcher, &liqor).expect("always ok");
|
||||||
|
|
|
@ -161,9 +161,9 @@ async fn feed_snapshots(
|
||||||
})
|
})
|
||||||
.flat_map(|mango_account| {
|
.flat_map(|mango_account| {
|
||||||
mango_account
|
mango_account
|
||||||
.serum3
|
.serum3_iter_active()
|
||||||
.iter_active()
|
|
||||||
.map(|serum3account| serum3account.open_orders)
|
.map(|serum3account| serum3account.open_orders)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.collect::<Vec<Pubkey>>();
|
.collect::<Vec<Pubkey>>();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use anchor_lang::Discriminator;
|
use anchor_lang::Discriminator;
|
||||||
use arrayref::array_ref;
|
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::account::{AccountSharedData, ReadableAccount};
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
@ -10,7 +10,7 @@ pub fn is_mango_account<'a>(
|
||||||
account: &'a AccountSharedData,
|
account: &'a AccountSharedData,
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
group_id: &Pubkey,
|
group_id: &Pubkey,
|
||||||
) -> Option<&'a MangoAccount> {
|
) -> Option<MangoAccountRefWithHeader<'a>> {
|
||||||
let data = account.data();
|
let data = account.data();
|
||||||
if account.owner() != program_id || data.is_empty() {
|
if account.owner() != program_id || data.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
|
@ -20,11 +20,9 @@ pub fn is_mango_account<'a>(
|
||||||
if disc_bytes != &MangoAccount::discriminator() {
|
if disc_bytes != &MangoAccount::discriminator() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if data.len() != 8 + std::mem::size_of::<MangoAccount>() {
|
|
||||||
return None;
|
let mango_account = MangoAccountRefWithHeader::from_bytes(&data[8..]).expect("always ok");
|
||||||
}
|
if mango_account.fixed.group != *group_id {
|
||||||
let mango_account: &MangoAccount = bytemuck::try_from_bytes(&data[8..]).expect("always Ok");
|
|
||||||
if mango_account.group != *group_id {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(mango_account)
|
Some(mango_account)
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
set -e pipefail
|
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' %;
|
# rg m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD -l | xargs -I % sed -i '' 's/m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD/5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM/g' %;
|
||||||
|
|
||||||
WALLET_WITH_FUNDS=~/.config/solana/mango-devnet.json
|
WALLET_WITH_FUNDS=~/.config/solana/mango-devnet.json
|
||||||
|
@ -9,7 +16,7 @@ PROGRAM_ID=5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM
|
||||||
|
|
||||||
# TODO fix need for --skip-lint
|
# TODO fix need for --skip-lint
|
||||||
# build program,
|
# 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
|
# patch types, which we want in rust, but anchor client doesn't support
|
||||||
./idl-fixup.sh
|
./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
|
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
|
# 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
|
else
|
||||||
echo "Skipping deployment..."
|
echo "Skipping deployment..."
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -21,8 +21,8 @@ client = ["solana-sdk"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# todo: when to fix, when to use caret? need a regular chore to bump dependencies
|
# todo: when to fix, when to use caret? need a regular chore to bump dependencies
|
||||||
# note: possibly need init-if-needed feature
|
# note: possibly need init-if-needed feature
|
||||||
anchor-lang = { version = "0.25.0", features = [] }
|
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anchor-spl = { version = "0.25.0", features = [] }
|
anchor-spl = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
arrayref = "0.3.6"
|
arrayref = "0.3.6"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
bytemuck = "^1.7.2"
|
bytemuck = "^1.7.2"
|
||||||
|
|
|
@ -8,14 +8,8 @@ use crate::state::*;
|
||||||
pub struct AccountClose<'info> {
|
pub struct AccountClose<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group, has_one = owner, close = sol_destination)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
// note: should never be the delegate
|
|
||||||
has_one = owner,
|
|
||||||
has_one = group,
|
|
||||||
close = sol_destination
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
|
@ -28,24 +22,25 @@ pub struct AccountClose<'info> {
|
||||||
pub fn account_close(ctx: Context<AccountClose>) -> Result<()> {
|
pub fn account_close(ctx: Context<AccountClose>) -> Result<()> {
|
||||||
let group = ctx.accounts.group.load()?;
|
let group = ctx.accounts.group.load()?;
|
||||||
|
|
||||||
// don't perform checks if group is just testing
|
{
|
||||||
if group.testing == 1 {
|
let account = ctx.accounts.account.load_mut()?;
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let account = ctx.accounts.account.load()?;
|
// don't perform checks if group is just testing
|
||||||
require!(!account.being_liquidated(), MangoError::SomeError);
|
if group.testing == 0 {
|
||||||
require!(!account.is_bankrupt(), MangoError::SomeError);
|
require!(!account.fixed.being_liquidated(), MangoError::SomeError);
|
||||||
require_eq!(account.delegate, Pubkey::default());
|
require!(!account.fixed.is_bankrupt(), MangoError::SomeError);
|
||||||
for ele in account.tokens.values {
|
require_eq!(account.fixed.delegate, Pubkey::default());
|
||||||
|
for ele in account.token_iter() {
|
||||||
require_eq!(ele.is_active(), false);
|
require_eq!(ele.is_active(), false);
|
||||||
}
|
}
|
||||||
for ele in account.serum3.values {
|
for ele in account.serum3_iter() {
|
||||||
require_eq!(ele.is_active(), false);
|
require_eq!(ele.is_active(), false);
|
||||||
}
|
}
|
||||||
for ele in account.perps.accounts {
|
for ele in account.perp_iter() {
|
||||||
require_eq!(ele.is_active(), false);
|
require_eq!(ele.is_active(), false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::state::*;
|
||||||
use crate::util::fill32_from_str;
|
use crate::util::fill32_from_str;
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
#[instruction(account_num: u8)]
|
#[instruction(account_num: u8, account_size: AccountSize)]
|
||||||
pub struct AccountCreate<'info> {
|
pub struct AccountCreate<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
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()],
|
seeds = [group.key().as_ref(), b"MangoAccount".as_ref(), owner.key().as_ref(), &account_num.to_le_bytes()],
|
||||||
bump,
|
bump,
|
||||||
payer = payer,
|
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>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
|
@ -25,20 +26,24 @@ pub struct AccountCreate<'info> {
|
||||||
pub system_program: Program<'info, System>,
|
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()?;
|
let mut account = ctx.accounts.account.load_init()?;
|
||||||
|
|
||||||
account.name = fill32_from_str(name)?;
|
account.fixed.name = fill32_from_str(name)?;
|
||||||
account.group = ctx.accounts.group.key();
|
account.fixed.group = ctx.accounts.group.key();
|
||||||
account.owner = ctx.accounts.owner.key();
|
account.fixed.owner = ctx.accounts.owner.key();
|
||||||
account.account_num = account_num;
|
account.fixed.account_num = account_num;
|
||||||
account.bump = *ctx.bumps.get("account").ok_or(MangoError::SomeError)?;
|
account.fixed.bump = *ctx.bumps.get("account").ok_or(MangoError::SomeError)?;
|
||||||
account.delegate = Pubkey::default();
|
account.fixed.delegate = Pubkey::default();
|
||||||
account.tokens = MangoAccountTokenPositions::default();
|
account.fixed.set_being_liquidated(false);
|
||||||
account.serum3 = MangoAccountSerum3Orders::default();
|
account.fixed.set_bankrupt(false);
|
||||||
account.perps = MangoAccountPerpPositions::default();
|
|
||||||
account.set_being_liquidated(false);
|
account.expand_dynamic_content(account_size)?;
|
||||||
account.set_bankrupt(false);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,8 @@ use crate::util::fill32_from_str;
|
||||||
pub struct AccountEdit<'info> {
|
pub struct AccountEdit<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group, has_one = owner)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
// Note: should never be the delegate
|
|
||||||
has_one = owner,
|
|
||||||
has_one = group,
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,13 +26,11 @@ pub fn account_edit(
|
||||||
|
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
|
|
||||||
// msg!("old account {:#?}", account);
|
|
||||||
|
|
||||||
// note: unchanged fields are inline, and match exact definition in create_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
|
// please maintain, and don't remove, makes it easy to reason about which support modification by owner
|
||||||
|
|
||||||
if let Some(name) = name_opt {
|
if let Some(name) = name_opt {
|
||||||
account.name = fill32_from_str(name)?;
|
account.fixed.name = fill32_from_str(name)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// unchanged -
|
// unchanged -
|
||||||
|
@ -46,7 +39,7 @@ pub fn account_edit(
|
||||||
// bump
|
// bump
|
||||||
|
|
||||||
if let Some(delegate) = delegate_opt {
|
if let Some(delegate) = delegate_opt {
|
||||||
account.delegate = delegate;
|
account.fixed.delegate = delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
// unchanged -
|
// unchanged -
|
||||||
|
@ -56,7 +49,5 @@ pub fn account_edit(
|
||||||
// being_liquidated
|
// being_liquidated
|
||||||
// is_bankrupt
|
// is_bankrupt
|
||||||
|
|
||||||
// msg!("new account {:#?}", account);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
|
@ -5,23 +5,21 @@ use anchor_lang::prelude::*;
|
||||||
pub struct ComputeAccountData<'info> {
|
pub struct ComputeAccountData<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
|
pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
|
||||||
let group_pk = ctx.accounts.group.key();
|
let group_pk = ctx.accounts.group.key();
|
||||||
|
|
||||||
let account = ctx.accounts.account.load()?;
|
let account = ctx.accounts.account.load()?;
|
||||||
|
|
||||||
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group_pk)?;
|
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 init_health = health_cache.health(HealthType::Init);
|
||||||
let maint_health = health_cache.health(HealthType::Maint);
|
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 {
|
emit!(MangoAccountData {
|
||||||
health_cache,
|
health_cache,
|
||||||
|
|
|
@ -2,8 +2,8 @@ use crate::accounts_zerocopy::*;
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::logs::{MarginTradeLog, TokenBalanceLog};
|
use crate::logs::{MarginTradeLog, TokenBalanceLog};
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
compute_health, new_fixed_order_account_retriever, AccountRetriever, Bank, Group, HealthType,
|
compute_health, new_fixed_order_account_retriever, AccountLoaderDynamic, AccountRetriever,
|
||||||
MangoAccount,
|
Bank, Group, HealthType, MangoAccount, MangoAccountRefMut,
|
||||||
};
|
};
|
||||||
use crate::{group_seeds, Mango};
|
use crate::{group_seeds, Mango};
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
@ -29,13 +29,8 @@ use std::collections::HashMap;
|
||||||
pub struct FlashLoan<'info> {
|
pub struct FlashLoan<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group, has_one = owner)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
pub token_program: Program<'info, Token>,
|
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 group = ctx.accounts.group.load()?;
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
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:
|
// Go over the banks passed as health accounts and:
|
||||||
// - Ensure that all banks that are passed in have activated positions.
|
// - 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() {
|
for (i, ai) in health_ais.iter().enumerate() {
|
||||||
match ai.load::<Bank>() {
|
match ai.load::<Bank>() {
|
||||||
Ok(bank) => {
|
Ok(bank) => {
|
||||||
require!(bank.group == account.group, MangoError::SomeError);
|
require!(bank.group == account.fixed.group, MangoError::SomeError);
|
||||||
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)?;
|
||||||
allowed_vaults.insert(bank.vault, (i, raw_token_index));
|
allowed_vaults.insert(bank.vault, (i, raw_token_index));
|
||||||
allowed_banks.insert(ai.key, bank);
|
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
|
// 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.
|
// 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 retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
|
||||||
let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?;
|
let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
|
||||||
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
|
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
|
||||||
msg!("pre_cpi_health {:?}", pre_cpi_health);
|
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
|
// Store the indexed value before the margin trade for logging purposes
|
||||||
let mut pre_indexed_positions = Vec::new();
|
let mut pre_indexed_positions = Vec::new();
|
||||||
for (_, info) in used_vaults.iter() {
|
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());
|
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 there are withdraws: figure out loan amount, mark as signer
|
||||||
if withdraw_amount > 0 {
|
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);
|
let native_position = token_account.native(&bank);
|
||||||
vault_info.loan_amount = if native_position > 0 {
|
vault_info.loan_amount = if native_position > 0 {
|
||||||
(I80F48::from(withdraw_amount) - native_position).max(I80F48::ZERO)
|
(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
|
// Track vault changes and apply them to the user's token positions
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
let inactive_tokens =
|
let inactive_tokens = adjust_for_post_cpi_vault_amounts(
|
||||||
adjust_for_post_cpi_vault_amounts(health_ais, all_cpi_ais, &used_vaults, &mut account)?;
|
health_ais,
|
||||||
|
all_cpi_ais,
|
||||||
|
&used_vaults,
|
||||||
|
&mut account.borrow_mut(),
|
||||||
|
)?;
|
||||||
|
|
||||||
// Check post-cpi health
|
// Check post-cpi health
|
||||||
let retriever = new_fixed_order_account_retriever(health_ais, &account)?;
|
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
|
||||||
let post_cpi_health = compute_health(&account, HealthType::Init, &retriever)?;
|
let post_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
|
||||||
require!(post_cpi_health >= 0, MangoError::HealthMustBePositive);
|
require!(post_cpi_health >= 0, MangoError::HealthMustBePositive);
|
||||||
msg!("post_cpi_health {:?}", post_cpi_health);
|
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 token_indexes = Vec::with_capacity(used_vaults.len());
|
||||||
let mut post_indexed_positions = Vec::with_capacity(used_vaults.len());
|
let mut post_indexed_positions = Vec::with_capacity(used_vaults.len());
|
||||||
for (_, info) in used_vaults.iter() {
|
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());
|
post_indexed_positions.push(position.indexed_position.to_bits());
|
||||||
token_indexes.push(position.token_index as u16);
|
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
|
// Deactivate inactive token accounts at the end
|
||||||
for raw_token_index in inactive_tokens {
|
for raw_token_index in inactive_tokens {
|
||||||
account.tokens.deactivate(raw_token_index);
|
account.token_deactivate(raw_token_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -375,13 +374,13 @@ fn adjust_for_post_cpi_vault_amounts(
|
||||||
health_ais: &[AccountInfo],
|
health_ais: &[AccountInfo],
|
||||||
cpi_ais: &[AccountInfo],
|
cpi_ais: &[AccountInfo],
|
||||||
used_vaults: &HashMap<&Pubkey, AllowedVault>,
|
used_vaults: &HashMap<&Pubkey, AllowedVault>,
|
||||||
account: &mut MangoAccount,
|
account: &mut MangoAccountRefMut,
|
||||||
) -> Result<Vec<usize>> {
|
) -> Result<Vec<usize>> {
|
||||||
let mut inactive_token_raw_indexes = Vec::with_capacity(used_vaults.len());
|
let mut inactive_token_raw_indexes = Vec::with_capacity(used_vaults.len());
|
||||||
for (_, info) in used_vaults.iter() {
|
for (_, info) in used_vaults.iter() {
|
||||||
let vault = Account::<TokenAccount>::try_from(&cpi_ais[info.vault_cpi_ai_index]).unwrap();
|
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 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;
|
let loan_origination_fee = info.loan_amount * bank.loan_origination_fee_rate;
|
||||||
bank.collected_fees_native += loan_origination_fee;
|
bank.collected_fees_native += loan_origination_fee;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::group_seeds;
|
||||||
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
|
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever,
|
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 crate::util::checked_math as cm;
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
@ -35,12 +35,8 @@ pub struct FlashLoan2Begin<'info> {
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct FlashLoan2End<'info> {
|
pub struct FlashLoan2End<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
#[account(
|
#[account(mut, has_one = group, has_one = owner)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
pub token_program: Program<'info, Token>,
|
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 group_seeds = group_seeds!(group);
|
||||||
|
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
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
|
// Find index at which vaults start
|
||||||
let vaults_index = ctx
|
let vaults_index = ctx
|
||||||
.remaining_accounts
|
.remaining_accounts
|
||||||
|
@ -174,7 +170,7 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_token_account.unwrap().owner == account.group
|
maybe_token_account.unwrap().owner == account.fixed.group
|
||||||
})
|
})
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?;
|
.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);
|
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
|
// 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
|
// Revoke delegation
|
||||||
let ix = token::spl_token::instruction::revoke(
|
let ix = token::spl_token::instruction::revoke(
|
||||||
|
@ -246,8 +242,8 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
|
||||||
// Check pre-cpi health
|
// Check pre-cpi health
|
||||||
// NOTE: This health check isn't strictly necessary. It will be, later, when
|
// 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.
|
// 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 retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
|
||||||
let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?;
|
let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
|
||||||
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
|
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
|
||||||
msg!("pre_cpi_health {:?}", pre_cpi_health);
|
msg!("pre_cpi_health {:?}", pre_cpi_health);
|
||||||
|
|
||||||
|
@ -255,7 +251,7 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
|
||||||
let mut prices = vec![];
|
let mut prices = vec![];
|
||||||
for change in &changes {
|
for change in &changes {
|
||||||
let (_, oracle_price) = retriever.bank_and_oracle(
|
let (_, oracle_price) = retriever.bank_and_oracle(
|
||||||
&account.group,
|
&account.fixed.group,
|
||||||
change.bank_index,
|
change.bank_index,
|
||||||
change.raw_token_index as TokenIndex,
|
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());
|
let mut token_loan_details = Vec::with_capacity(changes.len());
|
||||||
for (change, price) in changes.iter().zip(prices.iter()) {
|
for (change, price) in changes.iter().zip(prices.iter()) {
|
||||||
let mut bank = health_ais[change.bank_index].load_mut::<Bank>()?;
|
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 native = position.native(&bank);
|
||||||
let approved_amount = I80F48::from(bank.flash_loan_approved_amount);
|
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 {
|
emit!(FlashLoanLog {
|
||||||
mango_account: ctx.accounts.account.key(),
|
mango_account: ctx.accounts.account.key(),
|
||||||
token_loan_details: token_loan_details
|
token_loan_details
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check post-cpi health
|
// Check post-cpi health
|
||||||
let 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);
|
require!(post_cpi_health >= 0, MangoError::HealthMustBePositive);
|
||||||
msg!("post_cpi_health {:?}", post_cpi_health);
|
msg!("post_cpi_health {:?}", post_cpi_health);
|
||||||
|
|
||||||
// Deactivate inactive token accounts after health check
|
// Deactivate inactive token accounts after health check
|
||||||
for raw_token_index in deactivated_token_positions {
|
for raw_token_index in deactivated_token_positions {
|
||||||
account.tokens.deactivate(raw_token_index);
|
account.token_deactivate(raw_token_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -2,9 +2,10 @@ use crate::accounts_zerocopy::*;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::group_seeds;
|
use crate::group_seeds;
|
||||||
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
|
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
|
||||||
|
use crate::state::MangoAccount;
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever,
|
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 crate::util::checked_math as cm;
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
@ -38,11 +39,8 @@ pub struct FlashLoan3Begin<'info> {
|
||||||
/// the `owner` must have authority to transfer tokens out of them
|
/// the `owner` must have authority to transfer tokens out of them
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct FlashLoan3End<'info> {
|
pub struct FlashLoan3End<'info> {
|
||||||
#[account(
|
#[account(mut, has_one = owner)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
pub token_program: Program<'info, Token>,
|
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>>,
|
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan3End<'info>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
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
|
// Find index at which vaults start
|
||||||
let vaults_index = ctx
|
let vaults_index = ctx
|
||||||
|
@ -191,7 +190,7 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
|
||||||
return false;
|
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"))?;
|
.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;
|
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);
|
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
|
// 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
|
// Transfer any excess over the inital balance of the token account back
|
||||||
// into the vault. Compute the total change in the vault balance.
|
// 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
|
// Check pre-cpi health
|
||||||
// NOTE: This health check isn't strictly necessary. It will be, later, when
|
// 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.
|
// 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 retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
|
||||||
let pre_cpi_health = compute_health(&account, HealthType::Init, &retriever)?;
|
let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
|
||||||
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
|
require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive);
|
||||||
msg!("pre_cpi_health {:?}", pre_cpi_health);
|
msg!("pre_cpi_health {:?}", pre_cpi_health);
|
||||||
|
|
||||||
// Prices for logging
|
// Prices for logging
|
||||||
let mut prices = vec![];
|
let mut prices = vec![];
|
||||||
for change in &changes {
|
for change in &changes {
|
||||||
let (_, oracle_price) =
|
let (_, oracle_price) = retriever.bank_and_oracle(
|
||||||
retriever.bank_and_oracle(&account.group, change.bank_index, change.token_index)?;
|
&account.fixed.group,
|
||||||
|
change.bank_index,
|
||||||
|
change.token_index,
|
||||||
|
)?;
|
||||||
|
|
||||||
prices.push(oracle_price);
|
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());
|
let mut token_loan_details = Vec::with_capacity(changes.len());
|
||||||
for (change, price) in changes.iter().zip(prices.iter()) {
|
for (change, price) in changes.iter().zip(prices.iter()) {
|
||||||
let mut bank = health_ais[change.bank_index].load_mut::<Bank>()?;
|
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 native = position.native(&bank);
|
||||||
let approved_amount = I80F48::from(bank.flash_loan_approved_amount);
|
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 {
|
emit!(FlashLoanLog {
|
||||||
mango_account: ctx.accounts.account.key(),
|
mango_account: ctx.accounts.account.key(),
|
||||||
token_loan_details: token_loan_details
|
token_loan_details
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check post-cpi health
|
// Check post-cpi health
|
||||||
let 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);
|
require!(post_cpi_health >= 0, MangoError::HealthMustBePositive);
|
||||||
msg!("post_cpi_health {:?}", post_cpi_health);
|
msg!("post_cpi_health {:?}", post_cpi_health);
|
||||||
|
|
||||||
// Deactivate inactive token accounts after health check
|
// Deactivate inactive token accounts after health check
|
||||||
for raw_token_index in deactivated_token_positions {
|
for raw_token_index in deactivated_token_positions {
|
||||||
account.tokens.deactivate(raw_token_index);
|
account.token_deactivate(raw_token_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -21,19 +21,12 @@ pub struct LiqTokenBankruptcy<'info> {
|
||||||
)]
|
)]
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = liqor.load()?.is_owner_or_delegate(liqor_owner.key()),
|
|
||||||
)]
|
|
||||||
pub liqor: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub liqor_owner: Signer<'info>,
|
pub liqor_owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
)]
|
|
||||||
pub liqee: AccountLoader<'info, MangoAccount>,
|
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
has_one = group,
|
has_one = group,
|
||||||
|
@ -80,14 +73,20 @@ pub fn liq_token_bankruptcy(
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
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()?;
|
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_bank = bank_ais[0].load::<Bank>()?;
|
||||||
let liab_deposit_index = liab_bank.deposit_index;
|
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);
|
let mut remaining_liab_loss = -liqee_liab.native(&liab_bank);
|
||||||
require_gt!(remaining_liab_loss, I80F48::ZERO);
|
require_gt!(remaining_liab_loss, I80F48::ZERO);
|
||||||
drop(liab_bank);
|
drop(liab_bank);
|
||||||
|
@ -140,23 +139,24 @@ pub fn liq_token_bankruptcy(
|
||||||
|
|
||||||
// credit the liqor
|
// credit the liqor
|
||||||
let (liqor_quote, liqor_quote_raw_token_index, _) =
|
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)?;
|
let liqor_quote_active = quote_bank.deposit(liqor_quote, insurance_transfer_i80f48)?;
|
||||||
|
|
||||||
// transfer liab from liqee to liqor
|
// transfer liab from liqee to liqor
|
||||||
let (liqor_liab, liqor_liab_raw_token_index, _) =
|
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)?;
|
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab, liab_transfer)?;
|
||||||
|
|
||||||
// Check liqor's health
|
// 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);
|
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
|
||||||
|
|
||||||
if !liqor_quote_active {
|
if !liqor_quote_active {
|
||||||
liqor.tokens.deactivate(liqor_quote_raw_token_index);
|
liqor.token_deactivate(liqor_quote_raw_token_index);
|
||||||
}
|
}
|
||||||
if !liqor_liab_active {
|
if !liqor_liab_active {
|
||||||
liqor.tokens.deactivate(liqor_liab_raw_token_index);
|
liqor.token_deactivate(liqor_liab_raw_token_index);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For liab_token_index == QUOTE_TOKEN_INDEX: the insurance fund deposits directly into liqee,
|
// 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
|
// If the account has no more borrows then it's no longer bankrupt
|
||||||
// and should (always?) no longer be liquidated.
|
// and should (always?) no longer be liquidated.
|
||||||
let account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
|
let account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
|
||||||
let liqee_health_cache = new_health_cache(&liqee, &account_retriever)?;
|
let liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)?;
|
||||||
liqee.set_bankrupt(liqee_health_cache.has_borrows());
|
liqee.fixed.set_bankrupt(liqee_health_cache.has_borrows());
|
||||||
if !liqee.is_bankrupt() && liqee_health_cache.health(HealthType::Init) >= 0 {
|
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 {
|
if !liqee_liab_active {
|
||||||
liqee.tokens.deactivate(liqee_raw_token_index);
|
liqee.token_deactivate(liqee_raw_token_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -12,19 +12,12 @@ use crate::util::checked_math as cm;
|
||||||
pub struct LiqTokenWithToken<'info> {
|
pub struct LiqTokenWithToken<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = liqor.load()?.is_owner_or_delegate(liqor_owner.key()),
|
|
||||||
)]
|
|
||||||
pub liqor: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub liqor_owner: Signer<'info>,
|
pub liqor_owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
)]
|
|
||||||
pub liqee: AccountLoader<'info, MangoAccount>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn liq_token_with_token(
|
pub fn liq_token_with_token(
|
||||||
|
@ -40,30 +33,34 @@ pub fn liq_token_with_token(
|
||||||
.context("create account retriever")?;
|
.context("create account retriever")?;
|
||||||
|
|
||||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
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()?;
|
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
|
// Initial liqee health check
|
||||||
let mut liqee_health_cache =
|
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
|
||||||
new_health_cache(&liqee, &account_retriever).context("create liqee health cache")?;
|
.context("create liqee health cache")?;
|
||||||
let init_health = liqee_health_cache.health(HealthType::Init);
|
let init_health = liqee_health_cache.health(HealthType::Init);
|
||||||
msg!("init health: {}", init_health);
|
|
||||||
if liqee.being_liquidated() {
|
if liqee.being_liquidated() {
|
||||||
if init_health > I80F48::ZERO {
|
if init_health > I80F48::ZERO {
|
||||||
liqee.set_being_liquidated(false);
|
liqee.fixed.set_being_liquidated(false);
|
||||||
msg!("Liqee init_health above zero");
|
msg!("Liqee init_health above zero");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let maint_health = liqee_health_cache.health(HealthType::Maint);
|
let maint_health = liqee_health_cache.health(HealthType::Maint);
|
||||||
msg!("maint health: {}", maint_health);
|
|
||||||
require!(
|
require!(
|
||||||
maint_health < I80F48::ZERO,
|
maint_health < I80F48::ZERO,
|
||||||
MangoError::HealthMustBeNegative
|
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
|
// 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.
|
// 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);
|
let liqee_assets_native = liqee_asset_position.native(&asset_bank);
|
||||||
require!(liqee_assets_native.is_positive(), MangoError::SomeError);
|
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);
|
let liqee_liab_native = liqee_liab_position.native(&liab_bank);
|
||||||
require!(liqee_liab_native.is_negative(), MangoError::SomeError);
|
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);
|
let asset_transfer = cm!(liab_transfer * liab_price_adjusted / asset_price);
|
||||||
|
|
||||||
// Apply the balance changes to the liqor and liqee accounts
|
// 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_active = liab_bank.deposit(liqee_liab_position, liab_transfer)?;
|
||||||
let liqee_liab_position_indexed = liqee_liab_position.indexed_position;
|
let liqee_liab_position_indexed = liqee_liab_position.indexed_position;
|
||||||
|
|
||||||
let (liqor_liab_position, liqor_liab_raw_index, _) =
|
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_active = liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
|
||||||
let liqor_liab_position_indexed = liqor_liab_position.indexed_position;
|
let liqor_liab_position_indexed = liqor_liab_position.indexed_position;
|
||||||
|
|
||||||
let (liqor_asset_position, liqor_asset_raw_index, _) =
|
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_active = asset_bank.deposit(liqor_asset_position, asset_transfer)?;
|
||||||
let liqor_asset_position_indexed = liqor_asset_position.indexed_position;
|
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 =
|
let liqee_asset_active =
|
||||||
asset_bank.withdraw_without_fee(liqee_asset_position, asset_transfer)?;
|
asset_bank.withdraw_without_fee(liqee_asset_position, asset_transfer)?;
|
||||||
let liqee_asset_position_indexed = liqee_asset_position.indexed_position;
|
let liqee_asset_position_indexed = liqee_asset_position.indexed_position;
|
||||||
|
@ -156,8 +153,8 @@ pub fn liq_token_with_token(
|
||||||
emit!(LiquidateTokenAndTokenLog {
|
emit!(LiquidateTokenAndTokenLog {
|
||||||
liqee: ctx.accounts.liqee.key(),
|
liqee: ctx.accounts.liqee.key(),
|
||||||
liqor: ctx.accounts.liqor.key(),
|
liqor: ctx.accounts.liqor.key(),
|
||||||
asset_token_index: asset_token_index,
|
asset_token_index,
|
||||||
liab_token_index: liab_token_index,
|
liab_token_index,
|
||||||
asset_transfer: asset_transfer.to_bits(),
|
asset_transfer: asset_transfer.to_bits(),
|
||||||
liab_transfer: liab_transfer.to_bits(),
|
liab_transfer: liab_transfer.to_bits(),
|
||||||
asset_price: asset_price.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
|
// Since we use a scanning account retriever, it's safe to deactivate inactive token positions
|
||||||
if !liqee_asset_active {
|
if !liqee_asset_active {
|
||||||
liqee.tokens.deactivate(liqee_asset_raw_index);
|
liqee.token_deactivate(liqee_asset_raw_index);
|
||||||
}
|
}
|
||||||
if !liqee_liab_active {
|
if !liqee_liab_active {
|
||||||
liqee.tokens.deactivate(liqee_liab_raw_index);
|
liqee.token_deactivate(liqee_liab_raw_index);
|
||||||
}
|
}
|
||||||
if !liqor_asset_active {
|
if !liqor_asset_active {
|
||||||
liqor.tokens.deactivate(liqor_asset_raw_index);
|
liqor.token_deactivate(liqor_asset_raw_index);
|
||||||
}
|
}
|
||||||
if !liqor_liab_active {
|
if !liqor_liab_active {
|
||||||
liqor.tokens.deactivate(liqor_liab_raw_index)
|
liqor.token_deactivate(liqor_liab_raw_index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check liqee health again
|
// Check liqee health again
|
||||||
let maint_health = liqee_health_cache.health(HealthType::Maint);
|
let maint_health = liqee_health_cache.health(HealthType::Maint);
|
||||||
if maint_health < I80F48::ZERO {
|
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 {
|
} else {
|
||||||
let init_health = liqee_health_cache.health(HealthType::Init);
|
let init_health = liqee_health_cache.health(HealthType::Init);
|
||||||
|
|
||||||
// this is equivalent to one native USDC or 1e-6 USDC
|
// 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
|
// 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
|
// 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")?;
|
.context("compute liqor health")?;
|
||||||
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
|
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub use account_close::*;
|
pub use account_close::*;
|
||||||
pub use account_create::*;
|
pub use account_create::*;
|
||||||
pub use account_edit::*;
|
pub use account_edit::*;
|
||||||
|
pub use account_expand::*;
|
||||||
pub use benchmark::*;
|
pub use benchmark::*;
|
||||||
pub use compute_account_data::*;
|
pub use compute_account_data::*;
|
||||||
pub use flash_loan::*;
|
pub use flash_loan::*;
|
||||||
|
@ -43,6 +44,7 @@ pub use token_withdraw::*;
|
||||||
mod account_close;
|
mod account_close;
|
||||||
mod account_create;
|
mod account_create;
|
||||||
mod account_edit;
|
mod account_edit;
|
||||||
|
mod account_expand;
|
||||||
mod benchmark;
|
mod benchmark;
|
||||||
mod compute_account_data;
|
mod compute_account_data;
|
||||||
mod flash_loan;
|
mod flash_loan;
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::state::{Book, BookSide, Group, MangoAccount, PerpMarket};
|
use crate::state::{AccountLoaderDynamic, Book, BookSide, Group, MangoAccount, PerpMarket};
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct PerpCancelAllOrders<'info> {
|
pub struct PerpCancelAllOrders<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
|
@ -29,15 +25,20 @@ pub struct PerpCancelAllOrders<'info> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> {
|
pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> {
|
||||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
require!(!mango_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 mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||||
let bids = ctx.accounts.bids.load_mut()?;
|
let bids = ctx.accounts.bids.load_mut()?;
|
||||||
let asks = ctx.accounts.asks.load_mut()?;
|
let asks = ctx.accounts.asks.load_mut()?;
|
||||||
let mut book = Book::new(bids, asks);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
use crate::error::MangoError;
|
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)]
|
#[derive(Accounts)]
|
||||||
pub struct PerpCancelAllOrdersBySide<'info> {
|
pub struct PerpCancelAllOrdersBySide<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
|
@ -33,15 +29,26 @@ pub fn perp_cancel_all_orders_by_side(
|
||||||
side_option: Option<Side>,
|
side_option: Option<Side>,
|
||||||
limit: u8,
|
limit: u8,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
|
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 mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||||
let bids = ctx.accounts.bids.load_mut()?;
|
let bids = ctx.accounts.bids.load_mut()?;
|
||||||
let asks = ctx.accounts.asks.load_mut()?;
|
let asks = ctx.accounts.asks.load_mut()?;
|
||||||
let mut book = Book::new(bids, asks);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::state::{Book, BookSide, Group, MangoAccount, PerpMarket};
|
use crate::state::{AccountLoaderDynamic, Book, BookSide, Group, MangoAccount, PerpMarket};
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct PerpCancelOrder<'info> {
|
pub struct PerpCancelOrder<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
|
@ -29,17 +25,21 @@ pub struct PerpCancelOrder<'info> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: i128) -> Result<()> {
|
pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: i128) -> Result<()> {
|
||||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
require!(!mango_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 perp_market = ctx.accounts.perp_market.load_mut()?;
|
let perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||||
let bids = ctx.accounts.bids.load_mut()?;
|
let bids = ctx.accounts.bids.load_mut()?;
|
||||||
let asks = ctx.accounts.asks.load_mut()?;
|
let asks = ctx.accounts.asks.load_mut()?;
|
||||||
let mut book = Book::new(bids, asks);
|
let mut book = Book::new(bids, asks);
|
||||||
|
|
||||||
let side = mango_account
|
let side = account
|
||||||
.perps
|
.perp_find_order_side(perp_market.perp_market_index, order_id)
|
||||||
.find_order_side(perp_market.perp_market_index, order_id)
|
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?; // InvalidOrderId
|
.ok_or_else(|| error!(MangoError::SomeError))?; // InvalidOrderId
|
||||||
|
|
||||||
let order = book.cancel_order(order_id, side)?;
|
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
|
MangoError::SomeError // InvalidOwner
|
||||||
);
|
);
|
||||||
|
|
||||||
mango_account
|
account.perp_remove_order(order.owner_slot as usize, order.quantity)
|
||||||
.perps
|
|
||||||
.remove_order(order.owner_slot as usize, order.quantity)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::state::{Book, BookSide, Group, MangoAccount, PerpMarket};
|
use crate::state::{AccountLoaderDynamic, Book, BookSide, Group, MangoAccount, PerpMarket};
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct PerpCancelOrderByClientOrderId<'info> {
|
pub struct PerpCancelOrderByClientOrderId<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
|
@ -32,17 +28,21 @@ pub fn perp_cancel_order_by_client_order_id(
|
||||||
ctx: Context<PerpCancelOrderByClientOrderId>,
|
ctx: Context<PerpCancelOrderByClientOrderId>,
|
||||||
client_order_id: u64,
|
client_order_id: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
require!(!mango_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 perp_market = ctx.accounts.perp_market.load_mut()?;
|
let perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||||
let bids = ctx.accounts.bids.load_mut()?;
|
let bids = ctx.accounts.bids.load_mut()?;
|
||||||
let asks = ctx.accounts.asks.load_mut()?;
|
let asks = ctx.accounts.asks.load_mut()?;
|
||||||
let mut book = Book::new(bids, asks);
|
let mut book = Book::new(bids, asks);
|
||||||
|
|
||||||
let (order_id, side) = mango_account
|
let (order_id, side) = account
|
||||||
.perps
|
.perp_find_order_with_client_order_id(perp_market.perp_market_index, client_order_id)
|
||||||
.find_order_with_client_order_id(perp_market.perp_market_index, client_order_id)
|
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?;
|
.ok_or_else(|| error!(MangoError::SomeError))?;
|
||||||
|
|
||||||
let order = book.cancel_order(order_id, side)?;
|
let order = book.cancel_order(order_id, side)?;
|
||||||
|
@ -51,7 +51,5 @@ pub fn perp_cancel_order_by_client_order_id(
|
||||||
MangoError::SomeError // InvalidOwner
|
MangoError::SomeError // InvalidOwner
|
||||||
);
|
);
|
||||||
|
|
||||||
mango_account
|
account.perp_remove_order(order.owner_slot as usize, order.quantity)
|
||||||
.perps
|
|
||||||
.remove_order(order.owner_slot as usize, order.quantity)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use bytemuck::cast_ref;
|
use bytemuck::cast_ref;
|
||||||
|
|
||||||
use crate::accounts_zerocopy::*;
|
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::state::EventQueue;
|
use crate::state::{AccountLoaderDynamic, EventQueue, MangoAccount};
|
||||||
use crate::state::{EventType, FillEvent, Group, MangoAccount, OutEvent, PerpMarket};
|
use crate::state::{EventType, FillEvent, Group, OutEvent, PerpMarket};
|
||||||
|
|
||||||
use crate::logs::{emit_perp_balances, FillLog};
|
use crate::logs::{emit_perp_balances, FillLog};
|
||||||
|
|
||||||
|
@ -41,20 +40,22 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
||||||
|
|
||||||
// handle self trade separately because of rust borrow checker
|
// handle self trade separately because of rust borrow checker
|
||||||
if fill.maker == fill.taker {
|
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 => {
|
None => {
|
||||||
msg!("Unable to find account {}", fill.maker.to_string());
|
msg!("Unable to find account {}", fill.maker.to_string());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
|
|
||||||
};
|
|
||||||
|
|
||||||
ma.perps.execute_maker(
|
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,
|
perp_market.perp_market_index,
|
||||||
&mut perp_market,
|
&mut perp_market,
|
||||||
fill,
|
fill,
|
||||||
)?;
|
)?;
|
||||||
ma.perps.execute_taker(
|
ma.perp_execute_taker(
|
||||||
perp_market.perp_market_index,
|
perp_market.perp_market_index,
|
||||||
&mut perp_market,
|
&mut perp_market,
|
||||||
fill,
|
fill,
|
||||||
|
@ -63,33 +64,38 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
||||||
fill.maker,
|
fill.maker,
|
||||||
perp_market.perp_market_index as u64,
|
perp_market.perp_market_index as u64,
|
||||||
fill.price,
|
fill.price,
|
||||||
&ma.perps.accounts[perp_market.perp_market_index as usize],
|
&ma.perp_find_account(perp_market.perp_market_index).unwrap(),
|
||||||
&perp_market,
|
&perp_market,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
} else {
|
} 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 => {
|
None => {
|
||||||
msg!("Unable to find maker account {}", fill.maker.to_string());
|
msg!("Unable to find maker account {}", fill.maker.to_string());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
|
Some(ai) => {
|
||||||
};
|
let mal: AccountLoaderDynamic<MangoAccount> =
|
||||||
let mut taker = match mango_account_ais.iter().find(|ai| ai.key == &fill.taker)
|
AccountLoaderDynamic::try_from(&ai)?;
|
||||||
{
|
let mut maker = mal.load_mut()?;
|
||||||
|
|
||||||
|
match mango_account_ais.iter().find(|ai| ai.key == &fill.taker) {
|
||||||
None => {
|
None => {
|
||||||
msg!("Unable to find taker account {}", fill.taker.to_string());
|
msg!("Unable to find taker account {}", fill.taker.to_string());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Some(account_info) => account_info.load_mut::<MangoAccount>()?,
|
Some(ai) => {
|
||||||
};
|
let mal: AccountLoaderDynamic<MangoAccount> =
|
||||||
|
AccountLoaderDynamic::try_from(&ai)?;
|
||||||
|
let mut taker = mal.load_mut()?;
|
||||||
|
|
||||||
maker.perps.execute_maker(
|
maker.perp_execute_maker(
|
||||||
perp_market.perp_market_index,
|
perp_market.perp_market_index,
|
||||||
&mut perp_market,
|
&mut perp_market,
|
||||||
fill,
|
fill,
|
||||||
)?;
|
)?;
|
||||||
taker.perps.execute_taker(
|
taker.perp_execute_taker(
|
||||||
perp_market.perp_market_index,
|
perp_market.perp_market_index,
|
||||||
&mut perp_market,
|
&mut perp_market,
|
||||||
fill,
|
fill,
|
||||||
|
@ -98,17 +104,25 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
||||||
fill.maker,
|
fill.maker,
|
||||||
perp_market.perp_market_index as u64,
|
perp_market.perp_market_index as u64,
|
||||||
fill.price,
|
fill.price,
|
||||||
&maker.perps.accounts[perp_market.perp_market_index as usize],
|
&maker
|
||||||
|
.perp_find_account(perp_market.perp_market_index)
|
||||||
|
.unwrap(),
|
||||||
&perp_market,
|
&perp_market,
|
||||||
);
|
);
|
||||||
emit_perp_balances(
|
emit_perp_balances(
|
||||||
fill.taker,
|
fill.taker,
|
||||||
perp_market.perp_market_index as u64,
|
perp_market.perp_market_index as u64,
|
||||||
fill.price,
|
fill.price,
|
||||||
&taker.perps.accounts[perp_market.perp_market_index as usize],
|
&taker
|
||||||
|
.perp_find_account(perp_market.perp_market_index)
|
||||||
|
.unwrap(),
|
||||||
&perp_market,
|
&perp_market,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
emit!(FillLog {
|
emit!(FillLog {
|
||||||
mango_group: ctx.accounts.group.key(),
|
mango_group: ctx.accounts.group.key(),
|
||||||
market_index: perp_market.perp_market_index,
|
market_index: perp_market.perp_market_index,
|
||||||
|
@ -134,16 +148,19 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
||||||
EventType::Out => {
|
EventType::Out => {
|
||||||
let out: &OutEvent = cast_ref(event);
|
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 => {
|
None => {
|
||||||
msg!("Unable to find account {}", out.owner.to_string());
|
msg!("Unable to find account {}", out.owner.to_string());
|
||||||
return Ok(());
|
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
|
ma.perp_remove_order(out.owner_slot as usize, out.quantity)?;
|
||||||
.remove_order(out.owner_slot as usize, out.quantity)?;
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
EventType::Liquidate => {
|
EventType::Liquidate => {
|
||||||
// This is purely for record keeping. Can be removed if program logs are superior
|
// This is purely for record keeping. Can be removed if program logs are superior
|
||||||
|
|
|
@ -2,21 +2,18 @@ use anchor_lang::prelude::*;
|
||||||
|
|
||||||
use crate::accounts_zerocopy::*;
|
use crate::accounts_zerocopy::*;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
use crate::state::MangoAccount;
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
compute_health, new_fixed_order_account_retriever, oracle_price, Book, BookSide, EventQueue,
|
compute_health, new_fixed_order_account_retriever, oracle_price, AccountLoaderDynamic, Book,
|
||||||
Group, HealthType, MangoAccount, OrderType, PerpMarket, Side,
|
BookSide, EventQueue, Group, HealthType, OrderType, PerpMarket, Side,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct PerpPlaceOrder<'info> {
|
pub struct PerpPlaceOrder<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
|
@ -78,9 +75,14 @@ pub fn perp_place_order(
|
||||||
// When the limit is reached, processing stops and the instruction succeeds.
|
// When the limit is reached, processing stops and the instruction succeeds.
|
||||||
limit: u8,
|
limit: u8,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
|
require!(
|
||||||
let mango_account_pk = ctx.accounts.account.key();
|
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()?;
|
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||||
|
@ -118,8 +120,8 @@ pub fn perp_place_order(
|
||||||
&mut perp_market,
|
&mut perp_market,
|
||||||
&mut event_queue,
|
&mut event_queue,
|
||||||
oracle_price,
|
oracle_price,
|
||||||
&mut mango_account.perps,
|
&mut account.borrow_mut(),
|
||||||
&mango_account_pk,
|
&account_pk,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots,
|
max_base_lots,
|
||||||
max_quote_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 retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
||||||
let health = compute_health(&mango_account, HealthType::Init, &retriever)?;
|
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
|
||||||
msg!("health: {}", health);
|
msg!("health: {}", health);
|
||||||
require!(health >= 0, MangoError::HealthMustBePositive);
|
require!(health >= 0, MangoError::HealthMustBePositive);
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,8 @@ use crate::state::*;
|
||||||
pub struct Serum3CancelAllOrders<'info> {
|
pub struct Serum3CancelAllOrders<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
|
@ -50,15 +46,19 @@ pub fn serum3_cancel_all_orders(ctx: Context<Serum3CancelAllOrders>, limit: u8)
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
let account = ctx.accounts.account.load()?;
|
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()?;
|
let serum_market = ctx.accounts.serum_market.load()?;
|
||||||
|
|
||||||
// Validate open_orders
|
// Validate open_orders
|
||||||
require!(
|
require!(
|
||||||
account
|
account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?
|
.ok_or_else(|| error!(MangoError::SomeError))?
|
||||||
.open_orders
|
.open_orders
|
||||||
== ctx.accounts.open_orders.key(),
|
== ctx.accounts.open_orders.key(),
|
||||||
|
|
|
@ -14,12 +14,8 @@ use checked_math as cm;
|
||||||
pub struct Serum3CancelOrder<'info> {
|
pub struct Serum3CancelOrder<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
|
@ -63,13 +59,17 @@ pub fn serum3_cancel_order(
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
let account = ctx.accounts.account.load()?;
|
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
|
// Validate open_orders
|
||||||
require!(
|
require!(
|
||||||
account
|
account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?
|
.ok_or_else(|| error!(MangoError::SomeError))?
|
||||||
.open_orders
|
.open_orders
|
||||||
== ctx.accounts.open_orders.key(),
|
== ctx.accounts.open_orders.key(),
|
||||||
|
@ -96,7 +96,7 @@ pub fn serum3_cancel_order(
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
decrease_maybe_loan(
|
decrease_maybe_loan(
|
||||||
serum_market.market_index,
|
serum_market.market_index,
|
||||||
&mut account,
|
&mut account.borrow_mut(),
|
||||||
&before_oo,
|
&before_oo,
|
||||||
&after_oo,
|
&after_oo,
|
||||||
);
|
);
|
||||||
|
@ -109,11 +109,11 @@ pub fn serum3_cancel_order(
|
||||||
// the cached
|
// the cached
|
||||||
pub fn decrease_maybe_loan(
|
pub fn decrease_maybe_loan(
|
||||||
market_index: Serum3MarketIndex,
|
market_index: Serum3MarketIndex,
|
||||||
account: &mut MangoAccount,
|
account: &mut MangoAccountRefMut,
|
||||||
before_oo: &OpenOrdersSlim,
|
before_oo: &OpenOrdersSlim,
|
||||||
after_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 {
|
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;
|
let native_coin_free_increase = after_oo.native_coin_free - before_oo.native_coin_free;
|
||||||
|
|
|
@ -7,12 +7,8 @@ use crate::state::*;
|
||||||
pub struct Serum3CloseOpenOrders<'info> {
|
pub struct Serum3CloseOpenOrders<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
|
@ -40,13 +36,19 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
|
||||||
// Validation
|
// Validation
|
||||||
//
|
//
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
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()?;
|
let serum_market = ctx.accounts.serum_market.load()?;
|
||||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
|
||||||
// Validate open_orders
|
// Validate open_orders
|
||||||
require!(
|
require!(
|
||||||
account
|
account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?
|
.ok_or_else(|| error!(MangoError::SomeError))?
|
||||||
.open_orders
|
.open_orders
|
||||||
== ctx.accounts.open_orders.key(),
|
== 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)?;
|
cpi_close_open_orders(ctx.accounts)?;
|
||||||
|
|
||||||
// TODO: decrement in_use_count on the base token and quote token
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,8 @@ use crate::state::*;
|
||||||
pub struct Serum3CreateOpenOrders<'info> {
|
pub struct Serum3CreateOpenOrders<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
|
@ -50,9 +46,16 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
|
||||||
cpi_init_open_orders(ctx.accounts)?;
|
cpi_init_open_orders(ctx.accounts)?;
|
||||||
|
|
||||||
let serum_market = ctx.accounts.serum_market.load()?;
|
let serum_market = ctx.accounts.serum_market.load()?;
|
||||||
|
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
require!(
|
||||||
let serum_account = account.serum3.create(serum_market.market_index)?;
|
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.open_orders = ctx.accounts.open_orders.key();
|
||||||
serum_account.base_token_index = serum_market.base_token_index;
|
serum_account.base_token_index = serum_market.base_token_index;
|
||||||
serum_account.quote_token_index = serum_market.quote_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
|
// 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
|
// 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!
|
// they can't settle a market because they don't have free token_account_map!
|
||||||
let (quote_position, _, _) = account
|
let (quote_position, _, _) = account.token_get_mut_or_create(serum_market.quote_token_index)?;
|
||||||
.tokens
|
|
||||||
.get_mut_or_create(serum_market.quote_token_index)?;
|
|
||||||
quote_position.in_use_count += 1;
|
quote_position.in_use_count += 1;
|
||||||
let (base_position, _, _) = account
|
let (base_position, _, _) = account.token_get_mut_or_create(serum_market.base_token_index)?;
|
||||||
.tokens
|
|
||||||
.get_mut_or_create(serum_market.base_token_index)?;
|
|
||||||
base_position.in_use_count += 1;
|
base_position.in_use_count += 1;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -9,11 +9,8 @@ use crate::state::*;
|
||||||
pub struct Serum3LiqForceCancelOrders<'info> {
|
pub struct Serum3LiqForceCancelOrders<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
|
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
/// CHECK: Validated inline by checking against the pubkey stored in the account
|
/// 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()?;
|
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()?;
|
let serum_market = ctx.accounts.serum_market.load()?;
|
||||||
|
|
||||||
// Validate open_orders
|
// Validate open_orders
|
||||||
require!(
|
require!(
|
||||||
account
|
account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?
|
.ok_or_else(|| error!(MangoError::SomeError))?
|
||||||
.open_orders
|
.open_orders
|
||||||
== ctx.accounts.open_orders.key(),
|
== ctx.accounts.open_orders.key(),
|
||||||
|
@ -112,8 +108,9 @@ pub fn serum3_liq_force_cancel_orders(
|
||||||
{
|
{
|
||||||
let account = ctx.accounts.account.load()?;
|
let account = ctx.accounts.account.load()?;
|
||||||
|
|
||||||
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
|
let retriever =
|
||||||
let health = compute_health(&account, HealthType::Maint, &retriever)?;
|
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
||||||
|
let health = compute_health(&account.borrow(), HealthType::Maint, &retriever)?;
|
||||||
msg!("health: {}", health);
|
msg!("health: {}", health);
|
||||||
require!(health < 0, MangoError::SomeError);
|
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 base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||||
apply_vault_difference(
|
apply_vault_difference(
|
||||||
&mut account,
|
&mut account.borrow_mut(),
|
||||||
&mut base_bank,
|
&mut base_bank,
|
||||||
after_base_vault,
|
after_base_vault,
|
||||||
before_base_vault,
|
before_base_vault,
|
||||||
|
@ -151,7 +148,7 @@ pub fn serum3_liq_force_cancel_orders(
|
||||||
after_quote_vault,
|
after_quote_vault,
|
||||||
before_quote_vault,
|
before_quote_vault,
|
||||||
)?
|
)?
|
||||||
.deactivate_inactive_token_accounts(&mut account);
|
.deactivate_inactive_token_accounts(&mut account.borrow_mut());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,12 +83,8 @@ pub enum Serum3Side {
|
||||||
pub struct Serum3PlaceOrder<'info> {
|
pub struct Serum3PlaceOrder<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
|
@ -168,13 +164,16 @@ pub fn serum3_place_order(
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
let account = ctx.accounts.account.load()?;
|
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
|
// Validate open_orders
|
||||||
require!(
|
require!(
|
||||||
account
|
account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?
|
.ok_or_else(|| error!(MangoError::SomeError))?
|
||||||
.open_orders
|
.open_orders
|
||||||
== ctx.accounts.open_orders.key(),
|
== ctx.accounts.open_orders.key(),
|
||||||
|
@ -257,7 +256,7 @@ pub fn serum3_place_order(
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
let mut account = ctx.accounts.account.load_mut()?;
|
||||||
inc_maybe_loan(
|
inc_maybe_loan(
|
||||||
serum_market.market_index,
|
serum_market.market_index,
|
||||||
&mut account,
|
&mut account.borrow_mut(),
|
||||||
&before_oo,
|
&before_oo,
|
||||||
&after_oo,
|
&after_oo,
|
||||||
);
|
);
|
||||||
|
@ -279,7 +278,7 @@ pub fn serum3_place_order(
|
||||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||||
apply_vault_difference(
|
apply_vault_difference(
|
||||||
&mut account,
|
&mut account.borrow_mut(),
|
||||||
&mut base_bank,
|
&mut base_bank,
|
||||||
after_base_vault,
|
after_base_vault,
|
||||||
before_base_vault,
|
before_base_vault,
|
||||||
|
@ -292,12 +291,12 @@ pub fn serum3_place_order(
|
||||||
//
|
//
|
||||||
// Health check
|
// Health check
|
||||||
//
|
//
|
||||||
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
|
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
||||||
let health = compute_health(&account, HealthType::Init, &retriever)?;
|
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
|
||||||
msg!("health: {}", health);
|
msg!("health: {}", health);
|
||||||
require!(health >= 0, MangoError::HealthMustBePositive);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -305,11 +304,11 @@ pub fn serum3_place_order(
|
||||||
// if reserved has increased, then increase cached value by the increase in reserved
|
// if reserved has increased, then increase cached value by the increase in reserved
|
||||||
pub fn inc_maybe_loan(
|
pub fn inc_maybe_loan(
|
||||||
market_index: Serum3MarketIndex,
|
market_index: Serum3MarketIndex,
|
||||||
account: &mut MangoAccount,
|
account: &mut MangoAccountRefMut,
|
||||||
before_oo: &OpenOrdersSlim,
|
before_oo: &OpenOrdersSlim,
|
||||||
after_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() {
|
if after_oo.native_coin_reserved() > before_oo.native_coin_reserved() {
|
||||||
let native_coin_reserved_increase =
|
let native_coin_reserved_increase =
|
||||||
|
@ -333,18 +332,18 @@ pub struct VaultDifferenceResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
if !self.base_active {
|
||||||
account.tokens.deactivate(self.base_raw_index);
|
account.token_deactivate(self.base_raw_index);
|
||||||
}
|
}
|
||||||
if !self.quote_active {
|
if !self.quote_active {
|
||||||
account.tokens.deactivate(self.quote_raw_index);
|
account.token_deactivate(self.quote_raw_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_vault_difference(
|
pub fn apply_vault_difference(
|
||||||
account: &mut MangoAccount,
|
account: &mut MangoAccountRefMut,
|
||||||
base_bank: &mut Bank,
|
base_bank: &mut Bank,
|
||||||
after_base_vault: u64,
|
after_base_vault: u64,
|
||||||
before_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
|
// 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!
|
// 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_change = I80F48::from(after_base_vault) - I80F48::from(before_base_vault);
|
||||||
let base_active = base_bank.change_with_fee(base_position, base_change)?;
|
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_change = I80F48::from(after_quote_vault) - I80F48::from(before_quote_vault);
|
||||||
let quote_active = quote_bank.change_with_fee(quote_position, quote_change)?;
|
let quote_active = quote_bank.change_with_fee(quote_position, quote_change)?;
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,8 @@ use super::{apply_vault_difference, OpenOrdersReserved, OpenOrdersSlim};
|
||||||
pub struct Serum3SettleFunds<'info> {
|
pub struct Serum3SettleFunds<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
constraint = account.load()?.is_owner_or_delegate(owner.key()),
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
|
@ -77,13 +73,17 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
let account = ctx.accounts.account.load()?;
|
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
|
// Validate open_orders
|
||||||
require!(
|
require!(
|
||||||
account
|
account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?
|
.ok_or_else(|| error!(MangoError::SomeError))?
|
||||||
.open_orders
|
.open_orders
|
||||||
== ctx.accounts.open_orders.key(),
|
== ctx.accounts.open_orders.key(),
|
||||||
|
@ -126,15 +126,14 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
||||||
cpi_settle_funds(ctx.accounts)?;
|
cpi_settle_funds(ctx.accounts)?;
|
||||||
|
|
||||||
let after_oo = OpenOrdersSlim::from_oo(&open_orders);
|
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 base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||||
charge_maybe_fees(
|
charge_maybe_fees(
|
||||||
serum_market.market_index,
|
serum_market.market_index,
|
||||||
&mut base_bank,
|
&mut base_bank,
|
||||||
&mut quote_bank,
|
&mut quote_bank,
|
||||||
account,
|
&mut account.borrow_mut(),
|
||||||
&after_oo,
|
&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 base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||||
apply_vault_difference(
|
apply_vault_difference(
|
||||||
&mut account,
|
&mut account.borrow_mut(),
|
||||||
&mut base_bank,
|
&mut base_bank,
|
||||||
after_base_vault,
|
after_base_vault,
|
||||||
before_base_vault,
|
before_base_vault,
|
||||||
|
@ -161,7 +160,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
||||||
after_quote_vault,
|
after_quote_vault,
|
||||||
before_quote_vault,
|
before_quote_vault,
|
||||||
)?
|
)?
|
||||||
.deactivate_inactive_token_accounts(&mut account);
|
.deactivate_inactive_token_accounts(&mut account.borrow_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -172,10 +171,10 @@ pub fn charge_maybe_fees(
|
||||||
market_index: Serum3MarketIndex,
|
market_index: Serum3MarketIndex,
|
||||||
coin_bank: &mut Bank,
|
coin_bank: &mut Bank,
|
||||||
pc_bank: &mut Bank,
|
pc_bank: &mut Bank,
|
||||||
account: &mut MangoAccount,
|
account: &mut MangoAccountRefMut,
|
||||||
after_oo: &OpenOrdersSlim,
|
after_oo: &OpenOrdersSlim,
|
||||||
) -> Result<()> {
|
) -> 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>(
|
let maybe_actualized_coin_loan = I80F48::from_num::<u64>(
|
||||||
serum3_account
|
serum3_account
|
||||||
|
@ -185,9 +184,10 @@ pub fn charge_maybe_fees(
|
||||||
|
|
||||||
if maybe_actualized_coin_loan > 0 {
|
if maybe_actualized_coin_loan > 0 {
|
||||||
serum3_account.previous_native_coin_reserved = after_oo.native_coin_reserved();
|
serum3_account.previous_native_coin_reserved = after_oo.native_coin_reserved();
|
||||||
|
drop(serum3_account);
|
||||||
|
|
||||||
// loan origination fees
|
// 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);
|
let coin_token_native = coin_token_account.native(&coin_bank);
|
||||||
|
|
||||||
if coin_token_native.is_negative() {
|
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>(
|
let maybe_actualized_pc_loan = I80F48::from_num::<u64>(
|
||||||
serum3_account
|
serum3_account
|
||||||
.previous_native_pc_reserved
|
.previous_native_pc_reserved
|
||||||
|
@ -211,7 +212,7 @@ pub fn charge_maybe_fees(
|
||||||
serum3_account.previous_native_pc_reserved = after_oo.native_pc_reserved();
|
serum3_account.previous_native_pc_reserved = after_oo.native_pc_reserved();
|
||||||
|
|
||||||
// loan origination fees
|
// 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);
|
let pc_token_native = pc_token_account.native(&pc_bank);
|
||||||
|
|
||||||
if pc_token_native.is_negative() {
|
if pc_token_native.is_negative() {
|
||||||
|
|
|
@ -14,11 +14,8 @@ use crate::logs::{DepositLog, TokenBalanceLog};
|
||||||
pub struct TokenDeposit<'info> {
|
pub struct TokenDeposit<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
mut,
|
mut,
|
||||||
|
@ -61,10 +58,10 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
||||||
|
|
||||||
// Get the account's position for that token index
|
// Get the account's position for that token index
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
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) =
|
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 amount_i80f48 = I80F48::from(amount);
|
||||||
let position_is_active = {
|
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 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) =
|
let (bank, oracle_price) =
|
||||||
retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?;
|
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)
|
// 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 {
|
emit!(TokenBalanceLog {
|
||||||
mango_account: ctx.accounts.account.key(),
|
mango_account: ctx.accounts.account.key(),
|
||||||
token_index: token_index,
|
token_index,
|
||||||
indexed_position: indexed_position.to_bits(),
|
indexed_position: indexed_position.to_bits(),
|
||||||
deposit_index: bank.deposit_index.to_bits(),
|
deposit_index: bank.deposit_index.to_bits(),
|
||||||
borrow_index: bank.borrow_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
|
// TODO: This will be used to disable is_bankrupt or being_liquidated
|
||||||
// when health recovers sufficiently
|
// 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")?;
|
.context("post-deposit init health")?;
|
||||||
msg!("health: {}", 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.
|
// Deposits can deactivate a position if they cancel out a previous borrow.
|
||||||
//
|
//
|
||||||
if !position_is_active {
|
if !position_is_active {
|
||||||
account.tokens.deactivate(raw_token_index);
|
account.token_deactivate(raw_token_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit!(DepositLog {
|
emit!(DepositLog {
|
||||||
|
|
|
@ -14,13 +14,8 @@ use crate::util::checked_math as cm;
|
||||||
pub struct TokenWithdraw<'info> {
|
pub struct TokenWithdraw<'info> {
|
||||||
pub group: AccountLoader<'info, Group>,
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
#[account(
|
#[account(mut, has_one = group, has_one = owner)]
|
||||||
mut,
|
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||||
has_one = group,
|
|
||||||
// note: should never be the delegate
|
|
||||||
has_one = owner,
|
|
||||||
)]
|
|
||||||
pub account: AccountLoader<'info, MangoAccount>,
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
|
||||||
#[account(
|
#[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
|
// Get the account's position for that token index
|
||||||
let mut account = ctx.accounts.account.load_mut()?;
|
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) =
|
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
|
// The bank will also be passed in remainingAccounts. Use an explicit scope
|
||||||
// to drop the &mut before we borrow it immutably again later.
|
// 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 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) =
|
let (bank, oracle_price) =
|
||||||
retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?;
|
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)
|
// 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 {
|
emit!(TokenBalanceLog {
|
||||||
mango_account: ctx.accounts.account.key(),
|
mango_account: ctx.accounts.account.key(),
|
||||||
token_index: token_index,
|
token_index,
|
||||||
indexed_position: indexed_position.to_bits(),
|
indexed_position: indexed_position.to_bits(),
|
||||||
deposit_index: bank.deposit_index.to_bits(),
|
deposit_index: bank.deposit_index.to_bits(),
|
||||||
borrow_index: bank.borrow_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
|
// Health check
|
||||||
//
|
//
|
||||||
let health = compute_health(&account, HealthType::Init, &retriever)
|
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)
|
||||||
.context("post-withdraw init health")?;
|
.context("post-withdraw init health")?;
|
||||||
msg!("health: {}", health);
|
msg!("health: {}", health);
|
||||||
require!(health >= 0, MangoError::HealthMustBePositive);
|
require!(health >= 0, MangoError::HealthMustBePositive);
|
||||||
|
@ -151,13 +147,13 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
||||||
// deactivated.
|
// deactivated.
|
||||||
//
|
//
|
||||||
if !position_is_active {
|
if !position_is_active {
|
||||||
account.tokens.deactivate(raw_token_index);
|
account.token_deactivate(raw_token_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit!(WithdrawLog {
|
emit!(WithdrawLog {
|
||||||
mango_account: ctx.accounts.account.key(),
|
mango_account: ctx.accounts.account.key(),
|
||||||
signer: ctx.accounts.owner.key(),
|
signer: ctx.accounts.owner.key(),
|
||||||
token_index: token_index,
|
token_index,
|
||||||
quantity: amount,
|
quantity: amount,
|
||||||
price: oracle_price.to_bits(),
|
price: oracle_price.to_bits(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,18 +15,20 @@ pub mod error;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod instructions;
|
pub mod instructions;
|
||||||
pub mod logs;
|
pub mod logs;
|
||||||
mod serum3_cpi;
|
pub mod serum3_cpi;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use state::{OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex};
|
use state::{
|
||||||
|
AccountSize, OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex,
|
||||||
|
};
|
||||||
|
|
||||||
declare_id!("m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD");
|
declare_id!("m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD");
|
||||||
|
|
||||||
#[program]
|
#[program]
|
||||||
pub mod mango_v4 {
|
pub mod mango_v4 {
|
||||||
|
|
||||||
use crate::state::OracleConfig;
|
use crate::state::{AccountSize, OracleConfig};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -125,9 +127,14 @@ pub mod mango_v4 {
|
||||||
pub fn account_create(
|
pub fn account_create(
|
||||||
ctx: Context<AccountCreate>,
|
ctx: Context<AccountCreate>,
|
||||||
account_num: u8,
|
account_num: u8,
|
||||||
|
account_size: AccountSize,
|
||||||
name: String,
|
name: String,
|
||||||
) -> Result<()> {
|
) -> 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(
|
pub fn account_edit(
|
||||||
|
|
|
@ -11,8 +11,8 @@ pub fn emit_perp_balances(
|
||||||
pm: &PerpMarket,
|
pm: &PerpMarket,
|
||||||
) {
|
) {
|
||||||
emit!(PerpBalanceLog {
|
emit!(PerpBalanceLog {
|
||||||
mango_account: mango_account,
|
mango_account,
|
||||||
market_index: market_index,
|
market_index,
|
||||||
base_position: pp.base_position_lots,
|
base_position: pp.base_position_lots,
|
||||||
quote_position: pp.quote_position_native.to_bits(),
|
quote_position: pp.quote_position_native.to_bits(),
|
||||||
long_settled_funding: pp.long_settled_funding.to_bits(),
|
long_settled_funding: pp.long_settled_funding.to_bits(),
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
|
@ -6,16 +6,16 @@ use fixed::types::I80F48;
|
||||||
|
|
||||||
use crate::events::{Equity, TokenEquity};
|
use crate::events::{Equity, TokenEquity};
|
||||||
|
|
||||||
use super::{MangoAccount, ScanningAccountRetriever};
|
use super::{MangoAccountRef, ScanningAccountRetriever};
|
||||||
|
|
||||||
pub fn compute_equity(
|
pub fn compute_equity(
|
||||||
account: &MangoAccount,
|
account: &MangoAccountRef,
|
||||||
retriever: &ScanningAccountRetriever,
|
retriever: &ScanningAccountRetriever,
|
||||||
) -> Result<Equity> {
|
) -> Result<Equity> {
|
||||||
let mut token_equity_map = HashMap::new();
|
let mut token_equity_map = HashMap::new();
|
||||||
|
|
||||||
// token contributions
|
// 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)?;
|
let (bank, oracle_price) = retriever.scanned_bank_and_oracle(position.token_index)?;
|
||||||
// converts the token value to the basis token value for health computations
|
// converts the token value to the basis token value for health computations
|
||||||
// TODO: health basis token == USDC?
|
// TODO: health basis token == USDC?
|
||||||
|
@ -24,7 +24,7 @@ pub fn compute_equity(
|
||||||
}
|
}
|
||||||
|
|
||||||
// token contributions from Serum3
|
// 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)?;
|
let oo = retriever.scanned_serum_oo(&serum_account.open_orders)?;
|
||||||
|
|
||||||
// note base token value
|
// note base token value
|
||||||
|
|
|
@ -11,9 +11,11 @@ use std::collections::HashMap;
|
||||||
use crate::accounts_zerocopy::*;
|
use crate::accounts_zerocopy::*;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::serum3_cpi;
|
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 crate::util::checked_math as cm;
|
||||||
|
|
||||||
|
use super::MangoAccountRef;
|
||||||
|
|
||||||
const BANKRUPTCY_DUST_THRESHOLD: I80F48 = I80F48!(0.000001);
|
const BANKRUPTCY_DUST_THRESHOLD: I80F48 = I80F48!(0.000001);
|
||||||
|
|
||||||
/// This trait abstracts how to find accounts needed for the health computation.
|
/// 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>(
|
pub fn new_fixed_order_account_retriever<'a, 'info>(
|
||||||
ais: &'a [AccountInfo<'info>],
|
ais: &'a [AccountInfo<'info>],
|
||||||
account: &MangoAccount,
|
account: &MangoAccountRef,
|
||||||
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
|
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
|
||||||
let active_token_len = account.tokens.iter_active().count();
|
let active_token_len = account.token_iter_active().count();
|
||||||
let active_serum3_len = account.serum3.iter_active().count();
|
let active_serum3_len = account.serum3_iter_active().count();
|
||||||
let active_perp_len = account.perps.iter_active_accounts().count();
|
let active_perp_len = account.perp_iter_active_accounts().count();
|
||||||
let expected_ais = cm!(active_token_len * 2 // banks + oracles
|
let expected_ais = cm!(active_token_len * 2 // banks + oracles
|
||||||
+ active_perp_len // PerpMarkets
|
+ active_perp_len // PerpMarkets
|
||||||
+ active_serum3_len); // open_orders
|
+ active_serum3_len); // open_orders
|
||||||
|
@ -377,13 +379,13 @@ pub enum HealthType {
|
||||||
///
|
///
|
||||||
/// These account infos must fit the fixed layout defined by FixedOrderAccountRetriever.
|
/// These account infos must fit the fixed layout defined by FixedOrderAccountRetriever.
|
||||||
pub fn compute_health_from_fixed_accounts(
|
pub fn compute_health_from_fixed_accounts(
|
||||||
account: &MangoAccount,
|
account: &MangoAccountRef,
|
||||||
health_type: HealthType,
|
health_type: HealthType,
|
||||||
ais: &[AccountInfo],
|
ais: &[AccountInfo],
|
||||||
) -> Result<I80F48> {
|
) -> Result<I80F48> {
|
||||||
let active_token_len = account.tokens.iter_active().count();
|
let active_token_len = account.token_iter_active().count();
|
||||||
let active_serum3_len = account.serum3.iter_active().count();
|
let active_serum3_len = account.serum3_iter_active().count();
|
||||||
let active_perp_len = account.perps.iter_active_accounts().count();
|
let active_perp_len = account.perp_iter_active_accounts().count();
|
||||||
let expected_ais = cm!(active_token_len * 2 // banks + oracles
|
let expected_ais = cm!(active_token_len * 2 // banks + oracles
|
||||||
+ active_perp_len // PerpMarkets
|
+ active_perp_len // PerpMarkets
|
||||||
+ active_serum3_len); // open_orders
|
+ active_serum3_len); // open_orders
|
||||||
|
@ -403,7 +405,7 @@ pub fn compute_health_from_fixed_accounts(
|
||||||
|
|
||||||
/// Compute health with an arbitrary AccountRetriever
|
/// Compute health with an arbitrary AccountRetriever
|
||||||
pub fn compute_health(
|
pub fn compute_health(
|
||||||
account: &MangoAccount,
|
account: &MangoAccountRef,
|
||||||
health_type: HealthType,
|
health_type: HealthType,
|
||||||
retriever: &impl AccountRetriever,
|
retriever: &impl AccountRetriever,
|
||||||
) -> Result<I80F48> {
|
) -> 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.
|
/// Generate a HealthCache for an account and its health accounts.
|
||||||
pub fn new_health_cache(
|
pub fn new_health_cache(
|
||||||
account: &MangoAccount,
|
account: &MangoAccountRef,
|
||||||
retriever: &impl AccountRetriever,
|
retriever: &impl AccountRetriever,
|
||||||
) -> Result<HealthCache> {
|
) -> Result<HealthCache> {
|
||||||
// token contribution from token accounts
|
// token contribution from token accounts
|
||||||
let mut token_infos = vec![];
|
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) =
|
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
|
// converts the token value to the basis token value for health computations
|
||||||
// TODO: health basis token == USDC?
|
// 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
|
// Fill the TokenInfo balance with free funds in serum3 oo accounts, and fill
|
||||||
// the serum3_max_reserved with their reserved funds. Also build Serum3Infos.
|
// the serum3_max_reserved with their reserved funds. Also build Serum3Infos.
|
||||||
let mut serum3_infos = vec![];
|
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)?;
|
let oo = retriever.serum_oo(i, &serum_account.open_orders)?;
|
||||||
|
|
||||||
// find the TokenInfos for the market's base and quote tokens
|
// 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
|
// TODO: also account for perp funding in health
|
||||||
// health contribution from perp accounts
|
// health contribution from perp accounts
|
||||||
let mut perp_infos = Vec::with_capacity(account.perps.iter_active_accounts().count());
|
let mut perp_infos = Vec::with_capacity(account.perp_iter_active_accounts().count());
|
||||||
for (i, perp_account) in account.perps.iter_active_accounts().enumerate() {
|
for (i, perp_account) in account.perp_iter_active_accounts().enumerate() {
|
||||||
let perp_market = retriever.perp_market(&account.group, i, perp_account.market_index)?;
|
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
|
// 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)?;
|
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 {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::state::oracle::StubOracle;
|
use crate::state::oracle::StubOracle;
|
||||||
|
use crate::state::{MangoAccount, MangoAccountValue};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert::identity;
|
use std::convert::identity;
|
||||||
use std::mem::size_of;
|
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)
|
// Run a health test that includes all the side values (like referrer_rebates_accrued)
|
||||||
#[test]
|
#[test]
|
||||||
fn test_health0() {
|
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 group = Pubkey::new_unique();
|
||||||
|
|
||||||
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
|
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
|
||||||
|
@ -1073,20 +1079,20 @@ mod tests {
|
||||||
bank1
|
bank1
|
||||||
.data()
|
.data()
|
||||||
.deposit(
|
.deposit(
|
||||||
account.tokens.get_mut_or_create(1).unwrap().0,
|
account.token_get_mut_or_create(1).unwrap().0,
|
||||||
I80F48::from(100),
|
I80F48::from(100),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bank2
|
bank2
|
||||||
.data()
|
.data()
|
||||||
.withdraw_without_fee(
|
.withdraw_without_fee(
|
||||||
account.tokens.get_mut_or_create(4).unwrap().0,
|
account.token_get_mut_or_create(4).unwrap().0,
|
||||||
I80F48::from(10),
|
I80F48::from(10),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
|
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.open_orders = oo1.pubkey;
|
||||||
serum3account.base_token_index = 4;
|
serum3account.base_token_index = 4;
|
||||||
serum3account.quote_token_index = 1;
|
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().maint_liab_weight = I80F48::from_num(1.0 + 0.1f64);
|
||||||
perp1.data().quote_lot_size = 100;
|
perp1.data().quote_lot_size = 100;
|
||||||
perp1.data().base_lot_size = 10;
|
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.base_position_lots = 3;
|
||||||
perpaccount.quote_position_native = -I80F48::from(310u16);
|
perpaccount.quote_position_native = -I80F48::from(310u16);
|
||||||
perpaccount.bids_base_lots = 7;
|
perpaccount.bids_base_lots = 7;
|
||||||
|
@ -1142,7 +1148,7 @@ mod tests {
|
||||||
let health3 =
|
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);
|
(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(
|
assert!(health_eq(
|
||||||
compute_health(&account, HealthType::Init, &retriever).unwrap(),
|
compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(),
|
||||||
health1 + health2 + health3
|
health1 + health2 + health3
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -1226,7 +1232,9 @@ mod tests {
|
||||||
expected_health: f64,
|
expected_health: f64,
|
||||||
}
|
}
|
||||||
fn test_health1_runner(testcase: &TestHealth1Case) {
|
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 group = Pubkey::new_unique();
|
||||||
|
|
||||||
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
|
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
|
||||||
|
@ -1235,27 +1243,27 @@ mod tests {
|
||||||
bank1
|
bank1
|
||||||
.data()
|
.data()
|
||||||
.change_without_fee(
|
.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),
|
I80F48::from(testcase.token1),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bank2
|
bank2
|
||||||
.data()
|
.data()
|
||||||
.change_without_fee(
|
.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),
|
I80F48::from(testcase.token2),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bank3
|
bank3
|
||||||
.data()
|
.data()
|
||||||
.change_without_fee(
|
.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),
|
I80F48::from(testcase.token3),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
|
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.open_orders = oo1.pubkey;
|
||||||
serum3account1.base_token_index = 4;
|
serum3account1.base_token_index = 4;
|
||||||
serum3account1.quote_token_index = 1;
|
serum3account1.quote_token_index = 1;
|
||||||
|
@ -1263,7 +1271,7 @@ mod tests {
|
||||||
oo1.data().native_coin_total = testcase.oo_1_2.1;
|
oo1.data().native_coin_total = testcase.oo_1_2.1;
|
||||||
|
|
||||||
let mut oo2 = TestAccount::<OpenOrders>::new_zeroed();
|
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.open_orders = oo2.pubkey;
|
||||||
serum3account2.base_token_index = 5;
|
serum3account2.base_token_index = 5;
|
||||||
serum3account2.quote_token_index = 1;
|
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().maint_liab_weight = I80F48::from_num(1.0 + 0.1f64);
|
||||||
perp1.data().quote_lot_size = 100;
|
perp1.data().quote_lot_size = 100;
|
||||||
perp1.data().base_lot_size = 10;
|
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.base_position_lots = testcase.perp1.0;
|
||||||
perpaccount.quote_position_native = I80F48::from(testcase.perp1.1);
|
perpaccount.quote_position_native = I80F48::from(testcase.perp1.1);
|
||||||
perpaccount.bids_base_lots = testcase.perp1.2;
|
perpaccount.bids_base_lots = testcase.perp1.2;
|
||||||
|
@ -1310,7 +1318,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(health_eq(
|
assert!(health_eq(
|
||||||
compute_health(&account, HealthType::Init, &retriever).unwrap(),
|
compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(),
|
||||||
testcase.expected_health
|
testcase.expected_health
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
|
@ -1,8 +1,10 @@
|
||||||
pub use bank::*;
|
pub use bank::*;
|
||||||
|
pub use dynamic_account::*;
|
||||||
pub use equity::*;
|
pub use equity::*;
|
||||||
pub use group::*;
|
pub use group::*;
|
||||||
pub use health::*;
|
pub use health::*;
|
||||||
pub use mango_account::*;
|
pub use mango_account::*;
|
||||||
|
pub use mango_account_components::*;
|
||||||
pub use mint_info::*;
|
pub use mint_info::*;
|
||||||
pub use oracle::*;
|
pub use oracle::*;
|
||||||
pub use orderbook::*;
|
pub use orderbook::*;
|
||||||
|
@ -10,10 +12,12 @@ pub use perp_market::*;
|
||||||
pub use serum3_market::*;
|
pub use serum3_market::*;
|
||||||
|
|
||||||
mod bank;
|
mod bank;
|
||||||
|
mod dynamic_account;
|
||||||
mod equity;
|
mod equity;
|
||||||
mod group;
|
mod group;
|
||||||
mod health;
|
mod health;
|
||||||
mod mango_account;
|
mod mango_account;
|
||||||
|
mod mango_account_components;
|
||||||
mod mint_info;
|
mod mint_info;
|
||||||
mod oracle;
|
mod oracle;
|
||||||
mod orderbook;
|
mod orderbook;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use switchboard_v2::AggregatorAccountData;
|
||||||
|
|
||||||
use crate::accounts_zerocopy::*;
|
use crate::accounts_zerocopy::*;
|
||||||
use crate::checked_math as cm;
|
use crate::checked_math as cm;
|
||||||
use crate::error::MangoError;
|
use crate::error::*;
|
||||||
|
|
||||||
const LOOKUP_START: i8 = -12;
|
const LOOKUP_START: i8 = -12;
|
||||||
const LOOKUP: [I80F48; 25] = [
|
const LOOKUP: [I80F48; 25] = [
|
||||||
|
@ -144,14 +144,21 @@ pub fn oracle_price(
|
||||||
cm!(price * decimal_adj)
|
cm!(price * decimal_adj)
|
||||||
}
|
}
|
||||||
OracleType::SwitchboardV2 => {
|
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 = bytemuck::from_bytes::<AggregatorAccountData>(&data[8..]);
|
||||||
let feed_result = feed.get_result()?;
|
let feed_result = feed.get_result().map_err(from_foreign_error)?;
|
||||||
let price_decimal: f64 = feed_result.try_into()?;
|
let price_decimal: f64 = feed_result.try_into().map_err(from_foreign_error)?;
|
||||||
let price = I80F48::from_num(price_decimal);
|
let price = I80F48::from_num(price_decimal);
|
||||||
|
|
||||||
// Filter out bad prices
|
// Filter out bad prices
|
||||||
let std_deviation_decimal: f64 =
|
let std_deviation_decimal: f64 = feed
|
||||||
feed.latest_confirmed_round.std_deviation.try_into()?;
|
.latest_confirmed_round
|
||||||
|
.std_deviation
|
||||||
|
.try_into()
|
||||||
|
.map_err(from_foreign_error)?;
|
||||||
if I80F48::from_num(std_deviation_decimal) > oracle_conf_filter * price {
|
if I80F48::from_num(std_deviation_decimal) > oracle_conf_filter * price {
|
||||||
msg!(
|
msg!(
|
||||||
"Switchboard v2 std deviation too high; pubkey {} price: {} latest_confirmed_round.std_deviation: {}",
|
"Switchboard v2 std deviation too high; pubkey {} price: {} latest_confirmed_round.std_deviation: {}",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::cell::RefMut;
|
use std::cell::RefMut;
|
||||||
|
|
||||||
use crate::accounts_zerocopy::*;
|
use crate::accounts_zerocopy::*;
|
||||||
|
use crate::state::MangoAccountRefMut;
|
||||||
use crate::{
|
use crate::{
|
||||||
error::MangoError,
|
error::MangoError,
|
||||||
state::{
|
state::{
|
||||||
orderbook::{bookside::BookSide, nodes::LeafNode},
|
orderbook::{bookside::BookSide, nodes::LeafNode},
|
||||||
EventQueue, MangoAccount, MangoAccountPerpPositions, PerpMarket, FREE_ORDER_SLOT,
|
EventQueue, PerpMarket, FREE_ORDER_SLOT,
|
||||||
MAX_PERP_OPEN_ORDERS,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
@ -159,7 +159,7 @@ impl<'a> Book<'a> {
|
||||||
perp_market: &mut PerpMarket,
|
perp_market: &mut PerpMarket,
|
||||||
event_queue: &mut EventQueue,
|
event_queue: &mut EventQueue,
|
||||||
oracle_price: I80F48,
|
oracle_price: I80F48,
|
||||||
mango_account_perps: &mut MangoAccountPerpPositions,
|
mango_account: &mut MangoAccountRefMut,
|
||||||
mango_account_pk: &Pubkey,
|
mango_account_pk: &Pubkey,
|
||||||
price_lots: i64,
|
price_lots: i64,
|
||||||
max_base_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
|
// Record the taker trade in the account already, even though it will only be
|
||||||
// realized when the fill event gets executed
|
// realized when the fill event gets executed
|
||||||
let perp_account = mango_account_perps
|
let perp_account = mango_account
|
||||||
.get_account_mut_or_create(market.perp_market_index)?
|
.perp_get_account_mut_or_create(market.perp_market_index)?
|
||||||
.0;
|
.0;
|
||||||
perp_account.add_taker_trade(side, match_base_lots, match_quote_lots);
|
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();
|
event_queue.push_back(cast(event)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let owner_slot = mango_account_perps
|
let owner_slot = mango_account
|
||||||
.next_order_slot()
|
.perp_next_order_slot()
|
||||||
.ok_or_else(|| error!(MangoError::SomeError))?;
|
.ok_or_else(|| error!(MangoError::SomeError))?;
|
||||||
let new_order = LeafNode::new(
|
let new_order = LeafNode::new(
|
||||||
owner_slot as u8,
|
owner_slot as u8,
|
||||||
|
@ -373,13 +373,13 @@ impl<'a> Book<'a> {
|
||||||
price_lots
|
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
|
// if there were matched taker quote apply ref fees
|
||||||
// we know ref_fee_rate is not None if total_quote_taken > 0
|
// we know ref_fee_rate is not None if total_quote_taken > 0
|
||||||
if total_quote_lots_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(())
|
Ok(())
|
||||||
|
@ -387,20 +387,21 @@ impl<'a> Book<'a> {
|
||||||
|
|
||||||
pub fn cancel_all_order(
|
pub fn cancel_all_order(
|
||||||
&mut self,
|
&mut self,
|
||||||
mango_account: &mut MangoAccount,
|
mango_account: &mut MangoAccountRefMut,
|
||||||
perp_market: &mut PerpMarket,
|
perp_market: &mut PerpMarket,
|
||||||
mut limit: u8,
|
mut limit: u8,
|
||||||
side_to_cancel_option: Option<Side>,
|
side_to_cancel_option: Option<Side>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for i in 0..MAX_PERP_OPEN_ORDERS {
|
for i in 0..mango_account.header.perp_oo_count() {
|
||||||
if mango_account.perps.order_market[i] == FREE_ORDER_SLOT
|
let oo = mango_account.perp_oo_get_raw(i);
|
||||||
|| mango_account.perps.order_market[i] != perp_market.perp_market_index
|
if oo.order_market == FREE_ORDER_SLOT
|
||||||
|
|| oo.order_market != perp_market.perp_market_index
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let order_id = mango_account.perps.order_id[i];
|
let order_id = oo.order_id;
|
||||||
let order_side = mango_account.perps.order_side[i];
|
let order_side = oo.order_side;
|
||||||
|
|
||||||
if let Some(side_to_cancel) = side_to_cancel_option {
|
if let Some(side_to_cancel) = side_to_cancel_option {
|
||||||
if side_to_cancel != order_side {
|
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) {
|
if let Ok(leaf_node) = self.cancel_order(order_id, order_side) {
|
||||||
mango_account
|
mango_account
|
||||||
.perps
|
.perp_remove_order(leaf_node.owner_slot as usize, leaf_node.quantity)?
|
||||||
.remove_order(leaf_node.owner_slot as usize, leaf_node.quantity)?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
limit -= 1;
|
limit -= 1;
|
||||||
|
@ -441,7 +441,7 @@ impl<'a> Book<'a> {
|
||||||
/// both the maker and taker fees.
|
/// both the maker and taker fees.
|
||||||
fn apply_fees(
|
fn apply_fees(
|
||||||
market: &mut PerpMarket,
|
market: &mut PerpMarket,
|
||||||
mango_account_perps: &mut MangoAccountPerpPositions,
|
mango_account: &mut MangoAccountRefMut,
|
||||||
total_quote_taken: i64,
|
total_quote_taken: i64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let taker_quote_native = I80F48::from_num(
|
let taker_quote_native = I80F48::from_num(
|
||||||
|
@ -458,8 +458,8 @@ fn apply_fees(
|
||||||
let maker_fees = taker_quote_native * market.maker_fee;
|
let maker_fees = taker_quote_native * market.maker_fee;
|
||||||
|
|
||||||
let taker_fees = taker_quote_native * market.taker_fee;
|
let taker_fees = taker_quote_native * market.taker_fee;
|
||||||
let perp_account = mango_account_perps
|
let perp_account = mango_account
|
||||||
.get_account_mut_or_create(market.perp_market_index)?
|
.perp_get_account_mut_or_create(market.perp_market_index)?
|
||||||
.0;
|
.0;
|
||||||
perp_account.quote_position_native -= taker_fees;
|
perp_account.quote_position_native -= taker_fees;
|
||||||
market.fees_accrued += taker_fees + maker_fees;
|
market.fees_accrued += taker_fees + maker_fees;
|
||||||
|
|
|
@ -15,7 +15,8 @@ pub mod queue;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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 bytemuck::Zeroable;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
use solana_program::pubkey::Pubkey;
|
use solana_program::pubkey::Pubkey;
|
||||||
|
@ -99,7 +100,8 @@ mod tests {
|
||||||
|
|
||||||
let mut new_order =
|
let mut new_order =
|
||||||
|book: &mut Book, event_queue: &mut EventQueue, side, price, now_ts| -> i128 {
|
|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 quantity = 1;
|
||||||
let tif = 100;
|
let tif = 100;
|
||||||
|
@ -109,7 +111,7 @@ mod tests {
|
||||||
&mut perp_market,
|
&mut perp_market,
|
||||||
event_queue,
|
event_queue,
|
||||||
oracle_price,
|
oracle_price,
|
||||||
&mut account_perps,
|
&mut account.borrow_mut(),
|
||||||
&Pubkey::default(),
|
&Pubkey::default(),
|
||||||
price,
|
price,
|
||||||
quantity,
|
quantity,
|
||||||
|
@ -121,7 +123,7 @@ mod tests {
|
||||||
u8::MAX,
|
u8::MAX,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
account_perps.order_id[0]
|
account.perp_oo_get_raw(0).order_id
|
||||||
};
|
};
|
||||||
|
|
||||||
// insert bids until book side is full
|
// insert bids until book side is full
|
||||||
|
@ -193,8 +195,10 @@ mod tests {
|
||||||
market.maker_fee = I80F48::from_num(-0.001f64);
|
market.maker_fee = I80F48::from_num(-0.001f64);
|
||||||
market.taker_fee = I80F48::from_num(0.01f64);
|
market.taker_fee = I80F48::from_num(0.01f64);
|
||||||
|
|
||||||
let mut maker = MangoAccountPerpPositions::new();
|
let buffer = MangoAccount::default().try_to_vec().unwrap();
|
||||||
let mut taker = MangoAccountPerpPositions::new();
|
let mut maker = MangoAccountValue::from_bytes(&buffer).unwrap();
|
||||||
|
let mut taker = MangoAccountValue::from_bytes(&buffer).unwrap();
|
||||||
|
|
||||||
let maker_pk = Pubkey::new_unique();
|
let maker_pk = Pubkey::new_unique();
|
||||||
let taker_pk = Pubkey::new_unique();
|
let taker_pk = Pubkey::new_unique();
|
||||||
let now_ts = 1000000;
|
let now_ts = 1000000;
|
||||||
|
@ -207,7 +211,7 @@ mod tests {
|
||||||
&mut market,
|
&mut market,
|
||||||
&mut event_queue,
|
&mut event_queue,
|
||||||
oracle_price,
|
oracle_price,
|
||||||
&mut maker,
|
&mut maker.borrow_mut(),
|
||||||
&maker_pk,
|
&maker_pk,
|
||||||
price,
|
price,
|
||||||
bid_quantity,
|
bid_quantity,
|
||||||
|
@ -219,19 +223,28 @@ mod tests {
|
||||||
u8::MAX,
|
u8::MAX,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(maker.order_market[0], market.perp_market_index);
|
assert_eq!(
|
||||||
assert_eq!(maker.order_market[1], FREE_ORDER_SLOT);
|
maker.perp_oo_get_mut_raw(0).order_market,
|
||||||
assert_ne!(maker.order_id[0], 0);
|
market.perp_market_index
|
||||||
assert_eq!(maker.client_order_id[0], 42);
|
);
|
||||||
assert_eq!(maker.order_side[0], Side::Bid);
|
assert_eq!(maker.perp_oo_get_mut_raw(1).order_market, FREE_ORDER_SLOT);
|
||||||
assert!(bookside_contains_key(&book.bids, maker.order_id[0]));
|
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!(bookside_contains_price(&book.bids, price));
|
||||||
assert_eq!(maker.accounts[0].bids_base_lots, bid_quantity);
|
assert_eq!(maker.perp_get_raw(0).bids_base_lots, bid_quantity);
|
||||||
assert_eq!(maker.accounts[0].asks_base_lots, 0);
|
assert_eq!(maker.perp_get_raw(0).asks_base_lots, 0);
|
||||||
assert_eq!(maker.accounts[0].taker_base_lots, 0);
|
assert_eq!(maker.perp_get_raw(0).taker_base_lots, 0);
|
||||||
assert_eq!(maker.accounts[0].taker_quote_lots, 0);
|
assert_eq!(maker.perp_get_raw(0).taker_quote_lots, 0);
|
||||||
assert_eq!(maker.accounts[0].base_position_lots, 0);
|
assert_eq!(maker.perp_get_raw(0).base_position_lots, 0);
|
||||||
assert_eq!(maker.accounts[0].quote_position_native, 0);
|
assert_eq!(
|
||||||
|
maker.perp_get_raw(0).quote_position_native.to_num::<u32>(),
|
||||||
|
0
|
||||||
|
);
|
||||||
assert_eq!(event_queue.len(), 0);
|
assert_eq!(event_queue.len(), 0);
|
||||||
|
|
||||||
// Take the order partially
|
// Take the order partially
|
||||||
|
@ -241,7 +254,7 @@ mod tests {
|
||||||
&mut market,
|
&mut market,
|
||||||
&mut event_queue,
|
&mut event_queue,
|
||||||
oracle_price,
|
oracle_price,
|
||||||
&mut taker,
|
&mut taker.borrow_mut(),
|
||||||
&taker_pk,
|
&taker_pk,
|
||||||
price,
|
price,
|
||||||
match_quantity,
|
match_quantity,
|
||||||
|
@ -255,7 +268,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// the remainder of the maker order is still on the book
|
// the remainder of the maker order is still on the book
|
||||||
// (the maker account is unchanged: it was not even passed in)
|
// (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.price(), price);
|
||||||
assert_eq!(order.quantity, bid_quantity - match_quantity);
|
assert_eq!(order.quantity, bid_quantity - match_quantity);
|
||||||
|
|
||||||
|
@ -267,14 +280,17 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// the taker account is updated
|
// the taker account is updated
|
||||||
assert_eq!(taker.order_market[0], FREE_ORDER_SLOT);
|
assert_eq!(taker.perp_oo_get_raw(0).order_market, FREE_ORDER_SLOT);
|
||||||
assert_eq!(taker.accounts[0].bids_base_lots, 0);
|
assert_eq!(taker.perp_get_raw(0).bids_base_lots, 0);
|
||||||
assert_eq!(taker.accounts[0].asks_base_lots, 0);
|
assert_eq!(taker.perp_get_raw(0).asks_base_lots, 0);
|
||||||
assert_eq!(taker.accounts[0].taker_base_lots, -match_quantity);
|
assert_eq!(taker.perp_get_raw(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!(
|
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
|
-match_quote * market.taker_fee
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -294,34 +310,34 @@ mod tests {
|
||||||
|
|
||||||
// simulate event queue processing
|
// simulate event queue processing
|
||||||
maker
|
maker
|
||||||
.execute_maker(market.perp_market_index, &mut market, &fill)
|
.perp_execute_maker(market.perp_market_index, &mut market, &fill)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
taker
|
taker
|
||||||
.execute_taker(market.perp_market_index, &mut market, &fill)
|
.perp_execute_taker(market.perp_market_index, &mut market, &fill)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(market.open_interest, 2 * match_quantity);
|
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!(
|
assert_eq!(
|
||||||
maker.accounts[0].bids_base_lots,
|
maker.perp_get_raw(0).bids_base_lots,
|
||||||
bid_quantity - match_quantity
|
bid_quantity - match_quantity
|
||||||
);
|
);
|
||||||
assert_eq!(maker.accounts[0].asks_base_lots, 0);
|
assert_eq!(maker.perp_get_raw(0).asks_base_lots, 0);
|
||||||
assert_eq!(maker.accounts[0].taker_base_lots, 0);
|
assert_eq!(maker.perp_get_raw(0).taker_base_lots, 0);
|
||||||
assert_eq!(maker.accounts[0].taker_quote_lots, 0);
|
assert_eq!(maker.perp_get_raw(0).taker_quote_lots, 0);
|
||||||
assert_eq!(maker.accounts[0].base_position_lots, match_quantity);
|
assert_eq!(maker.perp_get_raw(0).base_position_lots, match_quantity);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
maker.accounts[0].quote_position_native,
|
maker.perp_get_raw(0).quote_position_native,
|
||||||
-match_quote - match_quote * market.maker_fee
|
-match_quote - match_quote * market.maker_fee
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(taker.accounts[0].bids_base_lots, 0);
|
assert_eq!(taker.perp_get_raw(0).bids_base_lots, 0);
|
||||||
assert_eq!(taker.accounts[0].asks_base_lots, 0);
|
assert_eq!(taker.perp_get_raw(0).asks_base_lots, 0);
|
||||||
assert_eq!(taker.accounts[0].taker_base_lots, 0);
|
assert_eq!(taker.perp_get_raw(0).taker_base_lots, 0);
|
||||||
assert_eq!(taker.accounts[0].taker_quote_lots, 0);
|
assert_eq!(taker.perp_get_raw(0).taker_quote_lots, 0);
|
||||||
assert_eq!(taker.accounts[0].base_position_lots, -match_quantity);
|
assert_eq!(taker.perp_get_raw(0).base_position_lots, -match_quantity);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
taker.accounts[0].quote_position_native,
|
taker.perp_get_raw(0).quote_position_native,
|
||||||
match_quote - match_quote * market.taker_fee
|
match_quote - match_quote * market.taker_fee
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use mango_v4::instructions::{
|
||||||
CpiData, FlashLoanWithdraw, InterestRateParams, Serum3OrderType, Serum3SelfTradeBehavior,
|
CpiData, FlashLoanWithdraw, InterestRateParams, Serum3OrderType, Serum3SelfTradeBehavior,
|
||||||
Serum3Side,
|
Serum3Side,
|
||||||
};
|
};
|
||||||
|
use mango_v4::state::{MangoAccount, MangoAccountValue};
|
||||||
use solana_program::instruction::Instruction;
|
use solana_program::instruction::Instruction;
|
||||||
use solana_program_test::BanksClientError;
|
use solana_program_test::BanksClientError;
|
||||||
use solana_sdk::instruction;
|
use solana_sdk::instruction;
|
||||||
|
@ -28,6 +29,11 @@ pub trait ClientAccountLoader {
|
||||||
let bytes = self.load_bytes(pubkey).await?;
|
let bytes = self.load_bytes(pubkey).await?;
|
||||||
AccountDeserialize::try_deserialize(&mut &bytes[..]).ok()
|
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)]
|
#[async_trait::async_trait(?Send)]
|
||||||
|
@ -118,11 +124,15 @@ fn make_instruction(
|
||||||
|
|
||||||
async fn get_mint_info_by_mint(
|
async fn get_mint_info_by_mint(
|
||||||
account_loader: &impl ClientAccountLoader,
|
account_loader: &impl ClientAccountLoader,
|
||||||
account: &MangoAccount,
|
account: &MangoAccountValue,
|
||||||
mint: Pubkey,
|
mint: Pubkey,
|
||||||
) -> MintInfo {
|
) -> MintInfo {
|
||||||
let mint_info_pk = Pubkey::find_program_address(
|
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(),
|
&mango_v4::id(),
|
||||||
)
|
)
|
||||||
.0;
|
.0;
|
||||||
|
@ -131,12 +141,12 @@ async fn get_mint_info_by_mint(
|
||||||
|
|
||||||
async fn get_mint_info_by_token_index(
|
async fn get_mint_info_by_token_index(
|
||||||
account_loader: &impl ClientAccountLoader,
|
account_loader: &impl ClientAccountLoader,
|
||||||
account: &MangoAccount,
|
account: &MangoAccountValue,
|
||||||
token_index: TokenIndex,
|
token_index: TokenIndex,
|
||||||
) -> MintInfo {
|
) -> MintInfo {
|
||||||
let bank_pk = Pubkey::find_program_address(
|
let bank_pk = Pubkey::find_program_address(
|
||||||
&[
|
&[
|
||||||
account.group.as_ref(),
|
account.fixed.group.as_ref(),
|
||||||
b"Bank".as_ref(),
|
b"Bank".as_ref(),
|
||||||
&token_index.to_le_bytes(),
|
&token_index.to_le_bytes(),
|
||||||
&0u64.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
|
// all the accounts that instructions like deposit/withdraw need to compute account health
|
||||||
async fn derive_health_check_remaining_account_metas(
|
async fn derive_health_check_remaining_account_metas(
|
||||||
account_loader: &impl ClientAccountLoader,
|
account_loader: &impl ClientAccountLoader,
|
||||||
account: &MangoAccount,
|
account: &MangoAccountValue,
|
||||||
affected_bank: Option<Pubkey>,
|
affected_bank: Option<Pubkey>,
|
||||||
writable_banks: bool,
|
writable_banks: bool,
|
||||||
affected_perp_market_index: Option<PerpMarketIndex>,
|
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 {
|
if let Some(affected_bank) = affected_bank {
|
||||||
let bank: Bank = account_loader.load(&affected_bank).await.unwrap();
|
let bank: Bank = account_loader.load(&affected_bank).await.unwrap();
|
||||||
adjusted_account
|
adjusted_account
|
||||||
.tokens
|
.token_get_mut_or_create(bank.token_index)
|
||||||
.get_mut_or_create(bank.token_index)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
if let Some(affected_perp_market_index) = affected_perp_market_index {
|
if let Some(affected_perp_market_index) = affected_perp_market_index {
|
||||||
adjusted_account
|
adjusted_account
|
||||||
.perps
|
.perp_get_account_mut_or_create(affected_perp_market_index)
|
||||||
.get_account_mut_or_create(affected_perp_market_index)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// figure out all the banks/oracles that need to be passed for the health check
|
// figure out all the banks/oracles that need to be passed for the health check
|
||||||
let mut banks = vec![];
|
let mut banks = vec![];
|
||||||
let mut oracles = vec![];
|
let mut oracles = vec![];
|
||||||
for position in adjusted_account.tokens.iter_active() {
|
for position in adjusted_account.token_iter_active() {
|
||||||
let mint_info =
|
let mint_info =
|
||||||
get_mint_info_by_token_index(account_loader, account, position.token_index).await;
|
get_mint_info_by_token_index(account_loader, account, position.token_index).await;
|
||||||
// TODO: ALTs are unavailable
|
// TODO: ALTs are unavailable
|
||||||
|
@ -202,11 +210,10 @@ async fn derive_health_check_remaining_account_metas(
|
||||||
}
|
}
|
||||||
|
|
||||||
let perp_markets = adjusted_account
|
let perp_markets = adjusted_account
|
||||||
.perps
|
.perp_iter_active_accounts()
|
||||||
.iter_active_accounts()
|
.map(|perp| get_perp_market_address_by_index(account.fixed.group, perp.market_index));
|
||||||
.map(|perp| get_perp_market_address_by_index(account.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 {
|
let to_account_meta = |pubkey| AccountMeta {
|
||||||
pubkey,
|
pubkey,
|
||||||
|
@ -229,8 +236,8 @@ async fn derive_health_check_remaining_account_metas(
|
||||||
|
|
||||||
async fn derive_liquidation_remaining_account_metas(
|
async fn derive_liquidation_remaining_account_metas(
|
||||||
account_loader: &impl ClientAccountLoader,
|
account_loader: &impl ClientAccountLoader,
|
||||||
liqee: &MangoAccount,
|
liqee: &MangoAccountValue,
|
||||||
liqor: &MangoAccount,
|
liqor: &MangoAccountValue,
|
||||||
asset_token_index: TokenIndex,
|
asset_token_index: TokenIndex,
|
||||||
asset_bank_index: usize,
|
asset_bank_index: usize,
|
||||||
liab_token_index: TokenIndex,
|
liab_token_index: TokenIndex,
|
||||||
|
@ -239,9 +246,8 @@ async fn derive_liquidation_remaining_account_metas(
|
||||||
let mut banks = vec![];
|
let mut banks = vec![];
|
||||||
let mut oracles = vec![];
|
let mut oracles = vec![];
|
||||||
let token_indexes = liqee
|
let token_indexes = liqee
|
||||||
.tokens
|
.token_iter_active()
|
||||||
.iter_active()
|
.chain(liqor.token_iter_active())
|
||||||
.chain(liqor.tokens.iter_active())
|
|
||||||
.map(|ta| ta.token_index)
|
.map(|ta| ta.token_index)
|
||||||
.unique();
|
.unique();
|
||||||
for token_index in token_indexes {
|
for token_index in token_indexes {
|
||||||
|
@ -269,16 +275,14 @@ async fn derive_liquidation_remaining_account_metas(
|
||||||
}
|
}
|
||||||
|
|
||||||
let perp_markets = liqee
|
let perp_markets = liqee
|
||||||
.perps
|
.perp_iter_active_accounts()
|
||||||
.iter_active_accounts()
|
.chain(liqee.perp_iter_active_accounts())
|
||||||
.chain(liqee.perps.iter_active_accounts())
|
.map(|perp| get_perp_market_address_by_index(liqee.fixed.group, perp.market_index))
|
||||||
.map(|perp| get_perp_market_address_by_index(liqee.group, perp.market_index))
|
|
||||||
.unique();
|
.unique();
|
||||||
|
|
||||||
let serum_oos = liqee
|
let serum_oos = liqee
|
||||||
.serum3
|
.serum3_iter_active()
|
||||||
.iter_active()
|
.chain(liqor.serum3_iter_active())
|
||||||
.chain(liqor.serum3.iter_active())
|
|
||||||
.map(|&s| s.open_orders);
|
.map(|&s| s.open_orders);
|
||||||
|
|
||||||
let to_account_meta = |pubkey| AccountMeta {
|
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 &[_]))
|
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 {
|
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 bank_data: Bank = solana.get_account(bank).await;
|
||||||
let native = account_data
|
let native = account_data
|
||||||
.tokens
|
.token_find(bank_data.token_index)
|
||||||
.find(bank_data.token_index)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.native(&bank_data);
|
.native(&bank_data);
|
||||||
native.round().to_num::<i64>()
|
native.round().to_num::<i64>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn account_position_closed(solana: &SolanaCookie, account: Pubkey, bank: Pubkey) -> bool {
|
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;
|
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 {
|
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 bank_data: Bank = solana.get_account(bank).await;
|
||||||
let native = account_data
|
let native = account_data
|
||||||
.tokens
|
.token_find(bank_data.token_index)
|
||||||
.find(bank_data.token_index)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.native(&bank_data);
|
.native(&bank_data);
|
||||||
native.to_num::<f64>()
|
native.to_num::<f64>()
|
||||||
|
@ -358,10 +365,13 @@ impl<'keypair> ClientInstruction for FlashLoanInstruction<'keypair> {
|
||||||
) -> (Self::Accounts, instruction::Instruction) {
|
) -> (Self::Accounts, instruction::Instruction) {
|
||||||
let program_id = mango_v4::id();
|
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 {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
owner: self.owner.pubkey(),
|
owner: self.owner.pubkey(),
|
||||||
token_program: Token::id(),
|
token_program: Token::id(),
|
||||||
|
@ -493,7 +503,10 @@ impl<'keypair> ClientInstruction for FlashLoan2EndInstruction<'keypair> {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {};
|
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(
|
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||||
&account_loader,
|
&account_loader,
|
||||||
|
@ -505,7 +518,7 @@ impl<'keypair> ClientInstruction for FlashLoan2EndInstruction<'keypair> {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
owner: self.owner.pubkey(),
|
owner: self.owner.pubkey(),
|
||||||
token_program: Token::id(),
|
token_program: Token::id(),
|
||||||
|
@ -597,7 +610,10 @@ impl<'keypair> ClientInstruction for FlashLoan3EndInstruction<'keypair> {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {};
|
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(
|
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||||
&account_loader,
|
&account_loader,
|
||||||
|
@ -660,10 +676,13 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
|
||||||
|
|
||||||
// load accounts, find PDAs, find remainingAccounts
|
// load accounts, find PDAs, find remainingAccounts
|
||||||
let token_account: TokenAccount = account_loader.load(&self.token_account).await.unwrap();
|
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(
|
let mint_info = Pubkey::find_program_address(
|
||||||
&[
|
&[
|
||||||
account.group.as_ref(),
|
account.fixed.group.as_ref(),
|
||||||
b"MintInfo".as_ref(),
|
b"MintInfo".as_ref(),
|
||||||
token_account.mint.as_ref(),
|
token_account.mint.as_ref(),
|
||||||
],
|
],
|
||||||
|
@ -682,7 +701,7 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
owner: self.owner.pubkey(),
|
owner: self.owner.pubkey(),
|
||||||
bank: mint_info.banks[self.bank_index],
|
bank: mint_info.banks[self.bank_index],
|
||||||
|
@ -725,10 +744,13 @@ impl ClientInstruction for TokenDepositInstruction {
|
||||||
|
|
||||||
// load account so we know its mint
|
// load account so we know its mint
|
||||||
let token_account: TokenAccount = account_loader.load(&self.token_account).await.unwrap();
|
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(
|
let mint_info = Pubkey::find_program_address(
|
||||||
&[
|
&[
|
||||||
account.group.as_ref(),
|
account.fixed.group.as_ref(),
|
||||||
b"MintInfo".as_ref(),
|
b"MintInfo".as_ref(),
|
||||||
token_account.mint.as_ref(),
|
token_account.mint.as_ref(),
|
||||||
],
|
],
|
||||||
|
@ -747,7 +769,7 @@ impl ClientInstruction for TokenDepositInstruction {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
bank: mint_info.banks[self.bank_index],
|
bank: mint_info.banks[self.bank_index],
|
||||||
vault: mint_info.vaults[self.bank_index],
|
vault: mint_info.vaults[self.bank_index],
|
||||||
|
@ -800,7 +822,11 @@ impl<'keypair> ClientInstruction for TokenRegisterInstruction<'keypair> {
|
||||||
) -> (Self::Accounts, instruction::Instruction) {
|
) -> (Self::Accounts, instruction::Instruction) {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {
|
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,
|
token_index: self.token_index,
|
||||||
bank_num: 0,
|
bank_num: 0,
|
||||||
oracle_config: OracleConfig {
|
oracle_config: OracleConfig {
|
||||||
|
@ -1276,7 +1302,7 @@ impl<'keypair> ClientInstruction for GroupCloseInstruction<'keypair> {
|
||||||
|
|
||||||
pub struct AccountCreateInstruction<'keypair> {
|
pub struct AccountCreateInstruction<'keypair> {
|
||||||
pub account_num: u8,
|
pub account_num: u8,
|
||||||
|
pub account_size: AccountSize,
|
||||||
pub group: Pubkey,
|
pub group: Pubkey,
|
||||||
pub owner: &'keypair Keypair,
|
pub owner: &'keypair Keypair,
|
||||||
pub payer: &'keypair Keypair,
|
pub payer: &'keypair Keypair,
|
||||||
|
@ -1292,6 +1318,7 @@ impl<'keypair> ClientInstruction for AccountCreateInstruction<'keypair> {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = mango_v4::instruction::AccountCreate {
|
let instruction = mango_v4::instruction::AccountCreate {
|
||||||
account_num: self.account_num,
|
account_num: self.account_num,
|
||||||
|
account_size: self.account_size,
|
||||||
name: "my_mango_account".to_string(),
|
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 struct AccountEditInstruction<'keypair> {
|
||||||
pub account_num: u8,
|
pub account_num: u8,
|
||||||
pub group: Pubkey,
|
pub group: Pubkey,
|
||||||
|
@ -1641,11 +1713,13 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
|
||||||
limit: self.limit,
|
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 serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
|
||||||
let open_orders = account
|
let open_orders = account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.open_orders;
|
.open_orders;
|
||||||
let quote_info =
|
let quote_info =
|
||||||
|
@ -1686,7 +1760,7 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
open_orders,
|
open_orders,
|
||||||
quote_bank: quote_info.first_bank(),
|
quote_bank: quote_info.first_bank(),
|
||||||
|
@ -1741,11 +1815,13 @@ impl<'keypair> ClientInstruction for Serum3CancelOrderInstruction<'keypair> {
|
||||||
order_id: self.order_id,
|
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 serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
|
||||||
let open_orders = account
|
let open_orders = account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.open_orders;
|
.open_orders;
|
||||||
|
|
||||||
|
@ -1762,7 +1838,7 @@ impl<'keypair> ClientInstruction for Serum3CancelOrderInstruction<'keypair> {
|
||||||
let event_q = market_external.event_q;
|
let event_q = market_external.event_q;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
open_orders,
|
open_orders,
|
||||||
serum_market: self.serum_market,
|
serum_market: self.serum_market,
|
||||||
|
@ -1800,11 +1876,13 @@ impl<'keypair> ClientInstruction for Serum3CancelAllOrdersInstruction<'keypair>
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction { limit: self.limit };
|
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 serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
|
||||||
let open_orders = account
|
let open_orders = account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.open_orders;
|
.open_orders;
|
||||||
|
|
||||||
|
@ -1821,7 +1899,7 @@ impl<'keypair> ClientInstruction for Serum3CancelAllOrdersInstruction<'keypair>
|
||||||
let event_q = market_external.event_q;
|
let event_q = market_external.event_q;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
open_orders,
|
open_orders,
|
||||||
serum_market: self.serum_market,
|
serum_market: self.serum_market,
|
||||||
|
@ -1859,11 +1937,13 @@ impl<'keypair> ClientInstruction for Serum3SettleFundsInstruction<'keypair> {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {};
|
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 serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
|
||||||
let open_orders = account
|
let open_orders = account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.open_orders;
|
.open_orders;
|
||||||
let quote_info =
|
let quote_info =
|
||||||
|
@ -1891,7 +1971,7 @@ impl<'keypair> ClientInstruction for Serum3SettleFundsInstruction<'keypair> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
open_orders,
|
open_orders,
|
||||||
quote_bank: quote_info.first_bank(),
|
quote_bank: quote_info.first_bank(),
|
||||||
|
@ -1933,11 +2013,13 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction { limit: self.limit };
|
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 serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
|
||||||
let open_orders = account
|
let open_orders = account
|
||||||
.serum3
|
.serum3_find(serum_market.market_index)
|
||||||
.find(serum_market.market_index)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.open_orders;
|
.open_orders;
|
||||||
let quote_info =
|
let quote_info =
|
||||||
|
@ -1977,7 +2059,7 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
open_orders,
|
open_orders,
|
||||||
quote_bank: quote_info.first_bank(),
|
quote_bank: quote_info.first_bank(),
|
||||||
|
@ -2033,8 +2115,14 @@ impl<'keypair> ClientInstruction for LiqTokenWithTokenInstruction<'keypair> {
|
||||||
max_liab_transfer: self.max_liab_transfer,
|
max_liab_transfer: self.max_liab_transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
let liqee: MangoAccount = account_loader.load(&self.liqee).await.unwrap();
|
let liqee = account_loader
|
||||||
let liqor: MangoAccount = account_loader.load(&self.liqor).await.unwrap();
|
.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(
|
let health_check_metas = derive_liquidation_remaining_account_metas(
|
||||||
&account_loader,
|
&account_loader,
|
||||||
&liqee,
|
&liqee,
|
||||||
|
@ -2047,7 +2135,7 @@ impl<'keypair> ClientInstruction for LiqTokenWithTokenInstruction<'keypair> {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: liqee.group,
|
group: liqee.fixed.group,
|
||||||
liqee: self.liqee,
|
liqee: self.liqee,
|
||||||
liqor: self.liqor,
|
liqor: self.liqor,
|
||||||
liqor_owner: self.liqor_owner.pubkey(),
|
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 liab_mint_info: MintInfo = account_loader.load(&self.liab_mint_info).await.unwrap();
|
||||||
let liqee: MangoAccount = account_loader.load(&self.liqee).await.unwrap();
|
let liqee = account_loader
|
||||||
let liqor: MangoAccount = account_loader.load(&self.liqor).await.unwrap();
|
.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(
|
let health_check_metas = derive_liquidation_remaining_account_metas(
|
||||||
&account_loader,
|
&account_loader,
|
||||||
&liqee,
|
&liqee,
|
||||||
|
@ -2101,11 +2195,12 @@ impl<'keypair> ClientInstruction for LiqTokenBankruptcyInstruction<'keypair> {
|
||||||
)
|
)
|
||||||
.await;
|
.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(
|
let quote_mint_info = Pubkey::find_program_address(
|
||||||
&[
|
&[
|
||||||
liqee.group.as_ref(),
|
liqee.fixed.group.as_ref(),
|
||||||
b"MintInfo".as_ref(),
|
b"MintInfo".as_ref(),
|
||||||
group.insurance_mint.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("e_mint_info).await.unwrap();
|
let quote_mint_info: MintInfo = account_loader.load("e_mint_info).await.unwrap();
|
||||||
|
|
||||||
let insurance_vault = Pubkey::find_program_address(
|
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,
|
&program_id,
|
||||||
)
|
)
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: liqee.group,
|
group: group_key,
|
||||||
liqee: self.liqee,
|
liqee: self.liqee,
|
||||||
liqor: self.liqor,
|
liqor: self.liqor,
|
||||||
liqor_owner: self.liqor_owner.pubkey(),
|
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 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(
|
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||||
&account_loader,
|
&account_loader,
|
||||||
&account,
|
&account,
|
||||||
|
@ -2605,7 +2703,10 @@ impl ClientInstruction for ComputeAccountDataInstruction {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {};
|
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(
|
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||||
&account_loader,
|
&account_loader,
|
||||||
|
@ -2617,7 +2718,7 @@ impl ClientInstruction for ComputeAccountDataInstruction {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: account.group,
|
group: account.fixed.group,
|
||||||
account: self.account,
|
account: self.account,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 2,
|
account_num: 2,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -89,6 +90,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -210,7 +212,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
||||||
account_position(solana, account, borrow_token1.bank).await,
|
account_position(solana, account, borrow_token1.bank).await,
|
||||||
(-350.0f64 + (1000.0 / 20.0 / 1.04)).round() as i64
|
(-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.being_liquidated());
|
||||||
assert!(!liqee.is_bankrupt());
|
assert!(!liqee.is_bankrupt());
|
||||||
|
|
||||||
|
@ -230,13 +232,13 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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);
|
let borrow1_after_liq = -350.0f64 + (1000.0 / 20.0 / 1.04) + (20.0 / 20.0 / 1.04);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
account_position(solana, account, borrow_token1.bank).await,
|
account_position(solana, account, borrow_token1.bank).await,
|
||||||
borrow1_after_liq.round() as i64
|
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.being_liquidated());
|
||||||
assert!(liqee.is_bankrupt());
|
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,
|
account_position(solana, vault_account, borrow_token1.bank).await,
|
||||||
vault_before + (borrow1_after_liq.round() as i64)
|
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.being_liquidated());
|
||||||
assert!(liqee.is_bankrupt());
|
assert!(liqee.is_bankrupt());
|
||||||
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
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,
|
account_position(solana, vault_account, borrow_token2.bank).await,
|
||||||
(vault_amount - borrow2_amount) as i64
|
(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.being_liquidated());
|
||||||
assert!(!liqee.is_bankrupt());
|
assert!(!liqee.is_bankrupt());
|
||||||
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
|
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
|
||||||
|
@ -351,6 +353,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 2,
|
account_num: 2,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -396,6 +399,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -513,7 +517,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(account_position_closed(solana, account, collateral_token1.bank).await);
|
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.being_liquidated());
|
||||||
assert!(!liqee.is_bankrupt());
|
assert!(!liqee.is_bankrupt());
|
||||||
|
|
||||||
|
@ -534,7 +538,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
|
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.being_liquidated());
|
||||||
assert!(liqee.is_bankrupt());
|
assert!(liqee.is_bankrupt());
|
||||||
|
|
||||||
|
@ -559,7 +563,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let liqee: MangoAccount = solana.get_account(account).await;
|
let liqee = get_mango_account(solana, account).await;
|
||||||
assert!(liqee.being_liquidated());
|
assert!(liqee.being_liquidated());
|
||||||
assert!(liqee.is_bankrupt());
|
assert!(liqee.is_bankrupt());
|
||||||
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
||||||
|
@ -592,7 +596,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let liqee: MangoAccount = solana.get_account(account).await;
|
let liqee = get_mango_account(solana, account).await;
|
||||||
assert!(liqee.being_liquidated());
|
assert!(liqee.being_liquidated());
|
||||||
assert!(liqee.is_bankrupt());
|
assert!(liqee.is_bankrupt());
|
||||||
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
||||||
|
@ -627,7 +631,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let liqee: MangoAccount = solana.get_account(account).await;
|
let liqee = get_mango_account(solana, account).await;
|
||||||
assert!(!liqee.being_liquidated());
|
assert!(!liqee.being_liquidated());
|
||||||
assert!(!liqee.is_bankrupt());
|
assert!(!liqee.is_bankrupt());
|
||||||
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
||||||
|
|
|
@ -40,6 +40,20 @@ async fn test_basic() -> Result<(), TransportError> {
|
||||||
let account = send_tx(
|
let account = send_tx(
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Small,
|
||||||
|
group,
|
||||||
|
owner,
|
||||||
|
payer,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.account;
|
||||||
|
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
AccountExpandInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
|
|
|
@ -37,6 +37,7 @@ async fn test_delegate() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
|
|
@ -40,6 +40,7 @@ async fn test_group_address_lookup_tables() -> Result<()> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
|
|
@ -37,6 +37,7 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -101,6 +102,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -212,6 +214,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
|
|
@ -42,6 +42,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 2,
|
account_num: 2,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -97,6 +98,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -251,6 +253,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 2,
|
account_num: 2,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -281,6 +284,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -388,7 +392,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
||||||
-50 + 19
|
-50 + 19
|
||||||
);
|
);
|
||||||
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
|
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.being_liquidated());
|
||||||
assert!(!liqee.is_bankrupt());
|
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,
|
account_position(solana, account, collateral_token1.bank).await,
|
||||||
1000 - 32
|
1000 - 32
|
||||||
);
|
);
|
||||||
let liqee: MangoAccount = solana.get_account(account).await;
|
let liqee = get_mango_account(solana, account).await;
|
||||||
assert!(liqee.being_liquidated());
|
assert!(liqee.being_liquidated());
|
||||||
assert!(!liqee.is_bankrupt());
|
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,
|
account_position(solana, account, collateral_token1.bank).await,
|
||||||
1000 - 32 - 21
|
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.being_liquidated());
|
||||||
assert!(!liqee.is_bankrupt());
|
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,
|
account_position(solana, account, collateral_token1.bank).await,
|
||||||
1000 - 32 - 535 - 1
|
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.being_liquidated());
|
||||||
assert!(!liqee.is_bankrupt());
|
assert!(!liqee.is_bankrupt());
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 1,
|
account_num: 1,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -96,6 +97,7 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -227,8 +229,8 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
|
||||||
margin_account_initial + withdraw_amount
|
margin_account_initial + withdraw_amount
|
||||||
);
|
);
|
||||||
// Check that position is fully deactivated
|
// Check that position is fully deactivated
|
||||||
let account_data: MangoAccount = solana.get_account(account).await;
|
let account_data = get_mango_account(solana, account).await;
|
||||||
assert_eq!(account_data.tokens.iter_active().count(), 0);
|
assert_eq!(account_data.token_iter_active().count(), 0);
|
||||||
|
|
||||||
//
|
//
|
||||||
// TEST: Activating a token via margin trade
|
// TEST: Activating a token via margin trade
|
||||||
|
@ -370,6 +372,7 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 1,
|
account_num: 1,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -412,6 +415,7 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -540,8 +544,8 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
|
||||||
margin_account_initial + withdraw_amount
|
margin_account_initial + withdraw_amount
|
||||||
);
|
);
|
||||||
// Check that position is fully deactivated
|
// Check that position is fully deactivated
|
||||||
let account_data: MangoAccount = solana.get_account(account).await;
|
let account_data = get_mango_account(solana, account).await;
|
||||||
assert_eq!(account_data.tokens.iter_active().count(), 0);
|
assert_eq!(account_data.token_iter_active().count(), 0);
|
||||||
|
|
||||||
//
|
//
|
||||||
// TEST: Activating a token via margin trade
|
// TEST: Activating a token via margin trade
|
||||||
|
@ -631,6 +635,7 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 1,
|
account_num: 1,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -673,6 +678,7 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -801,8 +807,8 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
|
||||||
margin_account_initial + withdraw_amount
|
margin_account_initial + withdraw_amount
|
||||||
);
|
);
|
||||||
// Check that position is fully deactivated
|
// Check that position is fully deactivated
|
||||||
let account_data: MangoAccount = solana.get_account(account).await;
|
let account_data = get_mango_account(solana, account).await;
|
||||||
assert_eq!(account_data.tokens.iter_active().count(), 0);
|
assert_eq!(account_data.token_iter_active().count(), 0);
|
||||||
|
|
||||||
//
|
//
|
||||||
// TEST: Activating a token via margin trade
|
// TEST: Activating a token via margin trade
|
||||||
|
|
|
@ -36,6 +36,7 @@ async fn test_perp() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -49,6 +50,7 @@ async fn test_perp() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 1,
|
account_num: 1,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -200,8 +202,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
||||||
let order_id_to_cancel = solana
|
let order_id_to_cancel = solana
|
||||||
.get_account::<MangoAccount>(account_0)
|
.get_account::<MangoAccount>(account_0)
|
||||||
.await
|
.await
|
||||||
.perps
|
.perp_open_orders[0]
|
||||||
.order_id[0];
|
.order_id;
|
||||||
send_tx(
|
send_tx(
|
||||||
solana,
|
solana,
|
||||||
PerpCancelOrderInstruction {
|
PerpCancelOrderInstruction {
|
||||||
|
@ -398,12 +400,12 @@ async fn test_perp() -> Result<(), TransportError> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||||
assert_eq!(mango_account_0.perps.accounts[0].base_position_lots, 1);
|
assert_eq!(mango_account_0.perps[0].base_position_lots, 1);
|
||||||
assert!(mango_account_0.perps.accounts[0].quote_position_native < -100.019);
|
assert!(mango_account_0.perps[0].quote_position_native < -100.019);
|
||||||
|
|
||||||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
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[0].base_position_lots, -1);
|
||||||
assert_eq!(mango_account_1.perps.accounts[0].quote_position_native, 100);
|
assert_eq!(mango_account_1.perps[0].quote_position_native, 100);
|
||||||
|
|
||||||
send_tx(
|
send_tx(
|
||||||
solana,
|
solana,
|
||||||
|
@ -426,10 +428,10 @@ async fn test_perp() -> Result<(), TransportError> {
|
||||||
async fn assert_no_perp_orders(solana: &SolanaCookie, account_0: Pubkey) {
|
async fn assert_no_perp_orders(solana: &SolanaCookie, account_0: Pubkey) {
|
||||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||||
|
|
||||||
for i in 0..MAX_PERP_OPEN_ORDERS {
|
for oo in mango_account_0.perp_open_orders.iter() {
|
||||||
assert!(mango_account_0.perps.order_id[i] == 0);
|
assert!(oo.order_id == 0);
|
||||||
assert!(mango_account_0.perps.order_side[i] == Side::Bid);
|
assert!(oo.order_side == Side::Bid);
|
||||||
assert!(mango_account_0.perps.client_order_id[i] == 0);
|
assert!(oo.client_order_id == 0);
|
||||||
assert!(mango_account_0.perps.order_market[i] == FREE_ORDER_SLOT);
|
assert!(oo.order_market == FREE_ORDER_SLOT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ async fn test_position_lifetime() -> Result<()> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -51,6 +52,7 @@ async fn test_position_lifetime() -> Result<()> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 1,
|
account_num: 1,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -122,8 +124,8 @@ async fn test_position_lifetime() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that positions are fully deactivated
|
// Check that positions are fully deactivated
|
||||||
let account: MangoAccount = solana.get_account(account).await;
|
let account = get_mango_account(solana, account).await;
|
||||||
assert_eq!(account.tokens.iter_active().count(), 0);
|
assert_eq!(account.token_iter_active().count(), 0);
|
||||||
|
|
||||||
// No user tokens got lost
|
// No user tokens got lost
|
||||||
for &payer_token in payer_mint_accounts {
|
for &payer_token in payer_mint_accounts {
|
||||||
|
@ -222,8 +224,8 @@ async fn test_position_lifetime() -> Result<()> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Check that positions are fully deactivated
|
// Check that positions are fully deactivated
|
||||||
let account: MangoAccount = solana.get_account(account).await;
|
let account = get_mango_account(solana, account).await;
|
||||||
assert_eq!(account.tokens.iter_active().count(), 0);
|
assert_eq!(account.token_iter_active().count(), 0);
|
||||||
|
|
||||||
// No user tokens got lost
|
// No user tokens got lost
|
||||||
// TODO: -1 is a workaround for rounding down in withdraw
|
// TODO: -1 is a workaround for rounding down in withdraw
|
||||||
|
|
|
@ -40,6 +40,7 @@ async fn test_serum() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -126,11 +127,10 @@ async fn test_serum() -> Result<(), TransportError> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.open_orders;
|
.open_orders;
|
||||||
|
|
||||||
let account_data: MangoAccount = solana.get_account(account).await;
|
let account_data = get_mango_account(solana, account).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
account_data
|
account_data
|
||||||
.serum3
|
.serum3_iter_active()
|
||||||
.iter_active()
|
|
||||||
.map(|v| (v.open_orders, v.market_index))
|
.map(|v| (v.open_orders, v.market_index))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
[(open_orders, 0)]
|
[(open_orders, 0)]
|
||||||
|
|
|
@ -36,6 +36,7 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
@ -63,6 +64,7 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
|
||||||
solana,
|
solana,
|
||||||
AccountCreateInstruction {
|
AccountCreateInstruction {
|
||||||
account_num: 1,
|
account_num: 1,
|
||||||
|
account_size: AccountSize::Large,
|
||||||
group,
|
group,
|
||||||
owner,
|
owner,
|
||||||
payer,
|
payer,
|
||||||
|
|
|
@ -18,6 +18,6 @@ default = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anchor-lang = { version = "0.25.0", features = [] }
|
anchor-lang = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
anchor-spl = "0.25.0"
|
anchor-spl = { git = "https://github.com/blockworks-foundation/anchor.git", rev = "1153380487706e4d8f486071cf2f519468438eab" }
|
||||||
solana-program = "1.10.29"
|
solana-program = "1.10.29"
|
||||||
|
|
|
@ -2,12 +2,19 @@
|
||||||
|
|
||||||
set -e pipefail
|
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
|
WALLET_WITH_FUNDS=~/.config/solana/mango-devnet.json
|
||||||
PROGRAM_ID=m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD
|
PROGRAM_ID=m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD
|
||||||
|
|
||||||
# TODO fix need for --skip-lint
|
# TODO fix need for --skip-lint
|
||||||
# build program,
|
# 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
|
# patch types, which we want in rust, but anchor client doesn't support
|
||||||
./idl-fixup.sh
|
./idl-fixup.sh
|
||||||
|
@ -23,12 +30,12 @@ if [[ -z "${NO_DEPLOY}" ]]; then
|
||||||
-k $WALLET_WITH_FUNDS target/deploy/mango_v4.so --skip-fee-check
|
-k $WALLET_WITH_FUNDS target/deploy/mango_v4.so --skip-fee-check
|
||||||
|
|
||||||
# # publish idl
|
# # publish idl
|
||||||
# anchor idl upgrade --provider.cluster https://mango.devnet.rpcpool.com --provider.wallet $WALLET_WITH_FUNDS \
|
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
|
--filepath target/idl/mango_v4.json $PROGRAM_ID
|
||||||
else
|
else
|
||||||
echo "Skipping deployment..."
|
echo "Skipping deployment..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
# # build npm package
|
# build npm package
|
||||||
# (cd ./ts/client && tsc)
|
(cd ./ts/client && tsc)
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
set -e pipefail
|
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
|
WALLET_WITH_FUNDS=~/.config/solana/mango-mainnet.json
|
||||||
PROGRAM_ID=m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD
|
PROGRAM_ID=m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ export class MangoAccount {
|
||||||
tokens: unknown;
|
tokens: unknown;
|
||||||
serum3: Object;
|
serum3: Object;
|
||||||
perps: unknown;
|
perps: unknown;
|
||||||
|
perpOpenOrders: unknown;
|
||||||
beingLiquidated: number;
|
beingLiquidated: number;
|
||||||
isBankrupt: number;
|
isBankrupt: number;
|
||||||
accountNum: number;
|
accountNum: number;
|
||||||
|
@ -36,9 +37,10 @@ export class MangoAccount {
|
||||||
obj.group,
|
obj.group,
|
||||||
obj.owner,
|
obj.owner,
|
||||||
obj.delegate,
|
obj.delegate,
|
||||||
obj.tokens as { values: TokenPositionDto[] },
|
obj.tokens as TokenPositionDto[],
|
||||||
obj.serum3 as { values: Serum3PositionDto[] },
|
obj.serum3 as Serum3PositionDto[],
|
||||||
obj.perps as { accounts: PerpPositionDto[] },
|
obj.perps as PerpPositionDto[],
|
||||||
|
obj.perpOpenOrders as any, // TODO
|
||||||
obj.beingLiquidated,
|
obj.beingLiquidated,
|
||||||
obj.isBankrupt,
|
obj.isBankrupt,
|
||||||
obj.accountNum,
|
obj.accountNum,
|
||||||
|
@ -54,9 +56,10 @@ export class MangoAccount {
|
||||||
public group: PublicKey,
|
public group: PublicKey,
|
||||||
public owner: PublicKey,
|
public owner: PublicKey,
|
||||||
public delegate: PublicKey,
|
public delegate: PublicKey,
|
||||||
tokens: { values: TokenPositionDto[] },
|
tokens: TokenPositionDto[],
|
||||||
serum3: { values: Serum3PositionDto[] },
|
serum3: Serum3PositionDto[],
|
||||||
perps: { accounts: PerpPositionDto[] },
|
perps: PerpPositionDto[],
|
||||||
|
perpOpenOrders: PerpPositionDto[],
|
||||||
beingLiquidated: number,
|
beingLiquidated: number,
|
||||||
isBankrupt: number,
|
isBankrupt: number,
|
||||||
accountNum: number,
|
accountNum: number,
|
||||||
|
@ -65,9 +68,9 @@ export class MangoAccount {
|
||||||
public accountData: {},
|
public accountData: {},
|
||||||
) {
|
) {
|
||||||
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||||
this.tokens = tokens.values.map((dto) => TokenPosition.from(dto));
|
this.tokens = tokens.map((dto) => TokenPosition.from(dto));
|
||||||
this.serum3 = serum3.values.map((dto) => Serum3Orders.from(dto));
|
this.serum3 = serum3.map((dto) => Serum3Orders.from(dto));
|
||||||
this.perps = perps.accounts.map((dto) => PerpPositions.from(dto));
|
this.perps = perps.map((dto) => PerpPositions.from(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload(client: MangoClient, group: Group) {
|
async reload(client: MangoClient, group: Group) {
|
||||||
|
@ -584,3 +587,8 @@ export class EquityDto {
|
||||||
tokens: { tokenIndex: number; value: I80F48Dto }[];
|
tokens: { tokenIndex: number; value: I80F48Dto }[];
|
||||||
perps: { perpMarketIndex: number; value: I80F48Dto }[];
|
perps: { perpMarketIndex: number; value: I80F48Dto }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AccountSize {
|
||||||
|
static small = { small: {} };
|
||||||
|
static large = { large: {} };
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,11 @@ import bs58 from 'bs58';
|
||||||
import { Bank, MintInfo } from './accounts/bank';
|
import { Bank, MintInfo } from './accounts/bank';
|
||||||
import { Group } from './accounts/group';
|
import { Group } from './accounts/group';
|
||||||
import { I80F48 } from './accounts/I80F48';
|
import { I80F48 } from './accounts/I80F48';
|
||||||
import { MangoAccount, MangoAccountData } from './accounts/mangoAccount';
|
import {
|
||||||
|
AccountSize,
|
||||||
|
MangoAccount,
|
||||||
|
MangoAccountData,
|
||||||
|
} from './accounts/mangoAccount';
|
||||||
import { StubOracle } from './accounts/oracle';
|
import { StubOracle } from './accounts/oracle';
|
||||||
import { OrderType, PerpMarket, Side } from './accounts/perp';
|
import { OrderType, PerpMarket, Side } from './accounts/perp';
|
||||||
import {
|
import {
|
||||||
|
@ -424,11 +428,17 @@ export class MangoClient {
|
||||||
group: Group,
|
group: Group,
|
||||||
ownerPk: PublicKey,
|
ownerPk: PublicKey,
|
||||||
accountNumber?: number,
|
accountNumber?: number,
|
||||||
|
accountSize?: AccountSize,
|
||||||
name?: string,
|
name?: string,
|
||||||
): Promise<MangoAccount> {
|
): Promise<MangoAccount> {
|
||||||
let mangoAccounts = await this.getMangoAccountForOwner(group, ownerPk);
|
let mangoAccounts = await this.getMangoAccountForOwner(group, ownerPk);
|
||||||
if (mangoAccounts.length === 0) {
|
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);
|
mangoAccounts = await this.getMangoAccountForOwner(group, ownerPk);
|
||||||
}
|
}
|
||||||
return mangoAccounts[0];
|
return mangoAccounts[0];
|
||||||
|
@ -437,10 +447,11 @@ export class MangoClient {
|
||||||
public async createMangoAccount(
|
public async createMangoAccount(
|
||||||
group: Group,
|
group: Group,
|
||||||
accountNumber: number,
|
accountNumber: number,
|
||||||
|
accountSize: AccountSize,
|
||||||
name?: string,
|
name?: string,
|
||||||
): Promise<TransactionSignature> {
|
): Promise<TransactionSignature> {
|
||||||
return await this.program.methods
|
return await this.program.methods
|
||||||
.accountCreate(accountNumber, name ?? '')
|
.accountCreate(accountNumber, accountSize, name ?? '')
|
||||||
.accounts({
|
.accounts({
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||||
|
@ -449,6 +460,21 @@ export class MangoClient {
|
||||||
.rpc();
|
.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(
|
public async editMangoAccount(
|
||||||
group: Group,
|
group: Group,
|
||||||
mangoAccount: MangoAccount,
|
mangoAccount: MangoAccount,
|
||||||
|
@ -462,7 +488,7 @@ export class MangoClient {
|
||||||
account: mangoAccount.publicKey,
|
account: mangoAccount.publicKey,
|
||||||
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||||
})
|
})
|
||||||
.rpc();
|
.rpc({ skipPreflight: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMangoAccount(mangoAccount: MangoAccount) {
|
public async getMangoAccount(mangoAccount: MangoAccount) {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,7 @@
|
||||||
import { AnchorProvider, Wallet } from '@project-serum/anchor';
|
import { AnchorProvider, Wallet } from '@project-serum/anchor';
|
||||||
import { Connection, Keypair } from '@solana/web3.js';
|
import { Connection, Keypair } from '@solana/web3.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { AccountSize } from '../accounts/mangoAccount';
|
||||||
import { MangoClient } from '../client';
|
import { MangoClient } from '../client';
|
||||||
import { MANGO_V4_ID } from '../constants';
|
import { MANGO_V4_ID } from '../constants';
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ async function main() {
|
||||||
group,
|
group,
|
||||||
user.publicKey,
|
user.publicKey,
|
||||||
0,
|
0,
|
||||||
|
AccountSize.small,
|
||||||
'my_mango_account',
|
'my_mango_account',
|
||||||
);
|
);
|
||||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { AnchorProvider, Wallet } from '@project-serum/anchor';
|
import { AnchorProvider, Wallet } from '@project-serum/anchor';
|
||||||
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { HealthType } from '../accounts/mangoAccount';
|
import { AccountSize, HealthType } from '../accounts/mangoAccount';
|
||||||
import { OrderType, Side } from '../accounts/perp';
|
import { OrderType, Side } from '../accounts/perp';
|
||||||
import {
|
import {
|
||||||
Serum3OrderType,
|
Serum3OrderType,
|
||||||
|
@ -59,6 +59,7 @@ async function main() {
|
||||||
group,
|
group,
|
||||||
user.publicKey,
|
user.publicKey,
|
||||||
0,
|
0,
|
||||||
|
AccountSize.small,
|
||||||
'my_mango_account',
|
'my_mango_account',
|
||||||
);
|
);
|
||||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||||
|
@ -92,6 +93,8 @@ async function main() {
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
// deposit and withdraw
|
// deposit and withdraw
|
||||||
|
|
||||||
|
try {
|
||||||
console.log(`...depositing 50 USDC`);
|
console.log(`...depositing 50 USDC`);
|
||||||
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
|
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
|
||||||
await mangoAccount.reload(client, group);
|
await mangoAccount.reload(client, group);
|
||||||
|
@ -99,6 +102,9 @@ async function main() {
|
||||||
console.log(`...depositing 0.0005 BTC`);
|
console.log(`...depositing 0.0005 BTC`);
|
||||||
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
|
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
|
||||||
await mangoAccount.reload(client, group);
|
await mangoAccount.reload(client, group);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
// witdrawing fails if no (other) user has deposited ORCA in the group
|
// witdrawing fails if no (other) user has deposited ORCA in the group
|
||||||
// console.log(`Withdrawing...0.1 ORCA`);
|
// console.log(`Withdrawing...0.1 ORCA`);
|
||||||
|
|
|
@ -46,6 +46,7 @@ async function main() {
|
||||||
group,
|
group,
|
||||||
user.publicKey,
|
user.publicKey,
|
||||||
0,
|
0,
|
||||||
|
AccountSize.small,
|
||||||
'my_mango_account',
|
'my_mango_account',
|
||||||
);
|
);
|
||||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||||
|
|
|
@ -61,6 +61,7 @@ async function main() {
|
||||||
group,
|
group,
|
||||||
user.publicKey,
|
user.publicKey,
|
||||||
0,
|
0,
|
||||||
|
AccountSize.small,
|
||||||
'my_mango_account',
|
'my_mango_account',
|
||||||
);
|
);
|
||||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||||
|
|
|
@ -40,6 +40,7 @@ async function main() {
|
||||||
group,
|
group,
|
||||||
user1.publicKey,
|
user1.publicKey,
|
||||||
0,
|
0,
|
||||||
|
AccountSize.small,
|
||||||
'my_mango_account',
|
'my_mango_account',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -72,6 +73,7 @@ async function main() {
|
||||||
group,
|
group,
|
||||||
user2.publicKey,
|
user2.publicKey,
|
||||||
0,
|
0,
|
||||||
|
AccountSize.small,
|
||||||
'my_mango_account',
|
'my_mango_account',
|
||||||
);
|
);
|
||||||
console.log(`...mangoAccount2 ${user2MangoAccount.publicKey}`);
|
console.log(`...mangoAccount2 ${user2MangoAccount.publicKey}`);
|
||||||
|
|
|
@ -46,6 +46,7 @@ async function main() {
|
||||||
group,
|
group,
|
||||||
user.publicKey,
|
user.publicKey,
|
||||||
0,
|
0,
|
||||||
|
AccountSize.small,
|
||||||
'my_mango_account',
|
'my_mango_account',
|
||||||
);
|
);
|
||||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||||
|
|
|
@ -47,6 +47,7 @@ async function main() {
|
||||||
group,
|
group,
|
||||||
user.publicKey,
|
user.publicKey,
|
||||||
0,
|
0,
|
||||||
|
AccountSize.small,
|
||||||
'my_mango_account',
|
'my_mango_account',
|
||||||
);
|
);
|
||||||
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
|
||||||
|
|
|
@ -2,9 +2,16 @@
|
||||||
|
|
||||||
set -e pipefail
|
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
|
# TODO fix need for --skip-lint
|
||||||
# build program,
|
# 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
|
# patch types, which we want in rust, but anchor client doesn't support
|
||||||
./idl-fixup.sh
|
./idl-fixup.sh
|
||||||
|
|
Loading…
Reference in New Issue