Merge branch 'main' into deploy

This commit is contained in:
microwavedcola1 2024-07-29 11:08:58 +02:00
commit 2b02489aac
56 changed files with 3153 additions and 632 deletions

View File

@ -4,18 +4,30 @@ Update this for each program release and mainnet deployment.
## not on mainnet
### v0.24.2, 2024-7-
### v0.24.1, 2024-7-
- Program: charge collateral fee directly on borrowed tokens (#973)
- Program: fix TokenUpdateIndexAndRateResilient IX (#979)
- Program: add support for pyth v2 account (#980)
## mainnet
### v0.24.1, 2024-7-9
Deployment: Jul 9, 2024 at 15:46:15 Central European Summer Time,
https://explorer.solana.com/tx/5KYBSXV4uRCUK6vaQoZjipNFhTqEY1b1DHJeSh5jo87UUVGBBJj2xSkzTqGHZ8aTb4M88jwsTUi4KXjVWv71sxE5
- Support for switchboard on demand oracle (#974)
- Sip bad oracle in token update index and rate (#975)
## mainnet
### v0.24.0, 2024-4-18
Deployment: Apr 18, 2024 at 14:53:24 Central European Summer Time,
https://explorer.solana.com/tx/2TFCGXQkUjRvkuuojxmiKefUtHPp6q6rM1frYvALByWMGfpWbiGH5hGq5suWEH7TUKoz4jb4KCGxu9DRw7YcXNdh
- Allow skipping banks and invalid oracles when computing health (#891)
This is only possible when we know for sure that the operation would not put the account into negative health zone.

277
Cargo.lock generated
View File

@ -121,7 +121,7 @@ checksum = "faa5be5b72abea167f87c868379ba3c2be356bfca9e6f474fd055fa0f7eeb4f2"
dependencies = [
"anchor-syn",
"anyhow",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"regex",
"syn 1.0.109",
@ -136,7 +136,7 @@ dependencies = [
"anchor-syn",
"anyhow",
"bs58 0.5.0",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"rustversion",
"syn 1.0.109",
@ -149,7 +149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59948e7f9ef8144c2aefb3f32a40c5fce2798baeec765ba038389e82301017ef"
dependencies = [
"anchor-syn",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"syn 1.0.109",
]
@ -160,7 +160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc753c9d1c7981cb8948cf7e162fb0f64558999c0413058e2d43df1df5448086"
dependencies = [
"anchor-syn",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -173,7 +173,7 @@ checksum = "f38b4e172ba1b52078f53fdc9f11e3dc0668ad27997838a0aad2d148afac8c97"
dependencies = [
"anchor-syn",
"anyhow",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -186,7 +186,7 @@ checksum = "4eebd21543606ab61e2d83d9da37d24d3886a49f390f9c43a1964735e8c0f0d5"
dependencies = [
"anchor-syn",
"anyhow",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -218,7 +218,7 @@ checksum = "ec4720d899b3686396cced9508f23dab420f1308344456ec78ef76f98fda42af"
dependencies = [
"anchor-syn",
"anyhow",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -229,7 +229,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f495e85480bd96ddeb77b71d499247c7d4e8b501e75ecb234e9ef7ae7bd6552a"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -280,7 +280,7 @@ dependencies = [
"anyhow",
"bs58 0.5.0",
"heck 0.3.3",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"serde",
"serde_json",
@ -391,7 +391,7 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
dependencies = [
"num-bigint 0.4.5",
"num-traits",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -427,7 +427,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -500,7 +500,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
"synstructure",
@ -512,7 +512,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -590,7 +590,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -601,9 +601,9 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -612,9 +612,9 @@ version = "0.1.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -800,12 +800,12 @@ dependencies = [
"lazycell",
"peeking_take_while",
"prettyplease 0.2.15",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -918,7 +918,7 @@ dependencies = [
"borsh-derive-internal 0.9.3",
"borsh-schema-derive-internal 0.9.3",
"proc-macro-crate 0.1.5",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"syn 1.0.109",
]
@ -931,7 +931,7 @@ dependencies = [
"borsh-derive-internal 0.10.3",
"borsh-schema-derive-internal 0.10.3",
"proc-macro-crate 0.1.5",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"syn 1.0.109",
]
@ -941,7 +941,7 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -952,7 +952,7 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -963,7 +963,7 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -974,7 +974,7 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -1068,9 +1068,9 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -1162,7 +1162,7 @@ name = "checked_math"
version = "0.1.0"
dependencies = [
"proc-macro-error",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
"trybuild",
@ -1252,7 +1252,7 @@ checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
dependencies = [
"heck 0.4.1",
"proc-macro-error",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -1536,7 +1536,7 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
dependencies = [
"fnv",
"ident_case",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"strsim 0.10.0",
"syn 1.0.109",
@ -1550,10 +1550,10 @@ checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
dependencies = [
"fnv",
"ident_case",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"strsim 0.10.0",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -1575,7 +1575,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core 0.20.3",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -1647,7 +1647,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -1668,7 +1668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
dependencies = [
"darling 0.14.4",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -1690,7 +1690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"rustc_version 0.4.0",
"syn 1.0.109",
@ -1793,9 +1793,9 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -1881,7 +1881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f"
dependencies = [
"enum-ordinalize",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -1922,9 +1922,9 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -1935,9 +1935,9 @@ checksum = "e4f76552f53cefc9a7f64987c3701b99d982f7690606fd67de1d09712fbf52f1"
dependencies = [
"num-bigint 0.4.5",
"num-traits",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -1955,7 +1955,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -2256,9 +2256,9 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -2992,7 +2992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2"
dependencies = [
"proc-macro-crate 0.1.5",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -3467,7 +3467,7 @@ dependencies = [
[[package]]
name = "mango-v4"
version = "0.24.1"
version = "0.24.2"
dependencies = [
"anchor-lang",
"anchor-spl",
@ -3490,6 +3490,7 @@ dependencies = [
"num_enum 0.5.11",
"openbook-v2",
"pyth-sdk-solana",
"pyth-solana-receiver-sdk",
"rand 0.8.5",
"regex",
"serde",
@ -3519,6 +3520,7 @@ dependencies = [
"anyhow",
"async-channel",
"base64 0.21.7",
"chrono",
"clap 3.2.25",
"dotenv",
"fixed 1.11.0 (git+https://github.com/blockworks-foundation/fixed.git?branch=v1.11.0-borsh0_10-mango)",
@ -3918,7 +3920,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -4173,7 +4175,7 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -4184,9 +4186,9 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -4285,7 +4287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -4297,9 +4299,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -4309,9 +4311,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -4398,9 +4400,9 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -4474,7 +4476,7 @@ checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7"
dependencies = [
"Inflector",
"proc-macro-error",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -4662,9 +4664,9 @@ version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -4727,9 +4729,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83145eba741b050ef981a9a1838c843fa7665e154383325aa8b440ae703180a2"
dependencies = [
"heck 0.4.1",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -4798,7 +4800,7 @@ version = "0.3.3"
source = "git+https://github.com/nolanderc/rust-postgres-query?rev=b4422051c8a31fbba4a35f88004c1cefb1878dd5#b4422051c8a31fbba4a35f88004c1cefb1878dd5"
dependencies = [
"proc-macro-hack",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -4815,7 +4817,7 @@ version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"syn 1.0.109",
]
@ -4825,8 +4827,8 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
dependencies = [
"proc-macro2 1.0.86",
"syn 2.0.68",
"proc-macro2 1.0.79",
"syn 2.0.58",
]
[[package]]
@ -4865,7 +4867,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
"version_check 0.9.4",
@ -4877,7 +4879,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"version_check 0.9.4",
]
@ -4899,9 +4901,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.86"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
@ -4991,7 +4993,7 @@ checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
dependencies = [
"anyhow",
"itertools",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -5004,7 +5006,7 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
dependencies = [
"anyhow",
"itertools",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -5074,6 +5076,40 @@ dependencies = [
"thiserror",
]
[[package]]
name = "pyth-solana-receiver-sdk"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e6559643f0b377b6f293269251f6a804ae7332c37f7310371f50c833453cd0"
dependencies = [
"anchor-lang",
"hex",
"pythnet-sdk",
"solana-program",
]
[[package]]
name = "pythnet-sdk"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bbbc0456f9f27c9ad16b6c3bf1b2a7fea61eebf900f4d024a0468b9a84fe0c1"
dependencies = [
"anchor-lang",
"bincode",
"borsh 0.10.3",
"bytemuck",
"byteorder",
"fast-math",
"hex",
"proc-macro2 1.0.79",
"rustc_version 0.4.0",
"serde",
"sha3 0.10.8",
"slow_primes",
"solana-program",
"thiserror",
]
[[package]]
name = "qstring"
version = "0.7.2"
@ -5157,7 +5193,7 @@ version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
]
[[package]]
@ -5795,7 +5831,7 @@ version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"serde_derive_internals",
"syn 1.0.109",
@ -5828,9 +5864,9 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -5911,9 +5947,9 @@ version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -5922,7 +5958,7 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -5976,9 +6012,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
dependencies = [
"darling 0.20.3",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -6363,7 +6399,7 @@ version = "0.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63927d22a1e8b74bda98cc6e151fcdf178b7abb0dc6c4f81e0bbf5ffe2fc4ec8"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"shank_macro_impl",
"syn 1.0.109",
@ -6376,7 +6412,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce03403df682f80f4dc1efafa87a4d0cb89b03726d0565e6364bdca5b9a441"
dependencies = [
"anyhow",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"serde",
"syn 1.0.109",
@ -6458,6 +6494,15 @@ dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "slow_primes"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58267dd2fbaa6dceecba9e3e106d2d90a2b02497c0e8b01b8759beccf5113938"
dependencies = [
"num 0.4.3",
]
[[package]]
name = "smallvec"
version = "0.6.14"
@ -6859,10 +6904,10 @@ version = "1.16.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dabde7fbd88a68eb083ae9d6d5f6855b7ba1bfc45d200c786b1b448ac49da5f"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"rustc_version 0.4.0",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -7538,10 +7583,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760fdfd4b7edb02fd9173a6dcec899ffae06ac21b66b65f8c7c5f3d17b12fa64"
dependencies = [
"bs58 0.4.0",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"rustversion",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -7934,7 +7979,7 @@ checksum = "b4fa8f409b5c5e0ac571df17c981ae1424b204743daa4428430627d38717caf5"
dependencies = [
"quote 1.0.36",
"spl-discriminator-syn",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -7943,10 +7988,10 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21968d7da2f0a624c509f24580c3fee70b364a6886d90709e679e64f572eca2f"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"solana-program",
"syn 2.0.68",
"syn 2.0.58",
"thiserror",
]
@ -8000,10 +8045,10 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6709c5f41fefb730f2bd8464da741079cf0efd1d0f522e041224b98d431b9b3"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"solana-program",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -8189,7 +8234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck 0.4.1",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"rustversion",
"syn 1.0.109",
@ -8450,18 +8495,18 @@ version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.68"
version = "2.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"unicode-ident",
]
@ -8478,7 +8523,7 @@ version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
"unicode-xid 0.2.4",
@ -8525,7 +8570,7 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee42b4e559f17bce0385ebf511a7beb67d5cc33c12c96b7f4e9789919d9c10f"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -8582,9 +8627,9 @@ version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -8740,7 +8785,7 @@ version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 1.0.109",
]
@ -9064,7 +9109,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"prost-build 0.9.0",
"quote 1.0.36",
"syn 1.0.109",
@ -9077,7 +9122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4"
dependencies = [
"prettyplease 0.1.25",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"prost-build 0.11.9",
"quote 1.0.36",
"syn 1.0.109",
@ -9090,7 +9135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07"
dependencies = [
"prettyplease 0.1.25",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"prost-build 0.11.9",
"quote 1.0.36",
"syn 1.0.109",
@ -9160,9 +9205,9 @@ version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]
@ -9657,9 +9702,9 @@ dependencies = [
"bumpalo",
"log 0.4.20",
"once_cell",
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
"wasm-bindgen-shared",
]
@ -9691,9 +9736,9 @@ version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -10120,9 +10165,9 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2 1.0.86",
"proc-macro2 1.0.79",
"quote 1.0.36",
"syn 2.0.68",
"syn 2.0.58",
]
[[package]]

View File

@ -11,6 +11,7 @@ anchor-lang = "0.28.0"
anchor-spl = "0.28.0"
fixed = { git = "https://github.com/blockworks-foundation/fixed.git", branch = "v1.11.0-borsh0_10-mango" }
pyth-sdk-solana = "0.8.0"
pyth-solana-receiver-sdk = "0.3.0"
# commit c85e56d (0.5.10 plus dependency updates)
serum_dex = { git = "https://github.com/openbook-dex/program.git", default-features=false }
mango-feeds-connector = "0.2.1"

View File

@ -18,7 +18,7 @@
- Run the tests to double check there are no failures
- Tag (`git tag program-v0.xy.z HEAD`) and push it (`git push <tag>`)
- Tag (`git tag program-v0.xy.z HEAD`) and push it (`git push origin <tag>`)
- Do a verifiable build

View File

@ -28,3 +28,4 @@ solana-sdk = { workspace = true }
tokio = { version = "1.14.1", features = ["rt-multi-thread", "time", "macros", "sync"] }
itertools = "0.10.3"
tracing = "0.1"
chrono = "0.4.31"

View File

@ -11,6 +11,7 @@ use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
mod save_snapshot;
mod test_collateral_fees;
mod test_oracles;
#[derive(Parser, Debug, Clone)]
@ -240,6 +241,13 @@ enum Command {
#[clap(flatten)]
rpc: Rpc,
},
TestCollateralFees {
#[clap(short, long)]
group: String,
#[clap(flatten)]
rpc: Rpc,
},
SaveSnapshot {
#[clap(short, long)]
group: String,
@ -375,6 +383,11 @@ async fn main() -> Result<(), anyhow::Error> {
let group = pubkey_from_cli(&group);
test_oracles::run(&client, group).await?;
}
Command::TestCollateralFees { group, rpc } => {
let client = rpc.client(None)?;
let group = pubkey_from_cli(&group);
test_collateral_fees::run(&client, group).await?;
}
Command::SaveSnapshot { group, rpc, output } => {
let mango_group = pubkey_from_cli(&group);
let client = rpc.client(None)?;

View File

@ -0,0 +1,224 @@
use anchor_lang::prelude::AccountInfo;
use itertools::Itertools;
use mango_v4::accounts_zerocopy::LoadZeroCopy;
use mango_v4::instructions::token_charge_collateral_fees_internal;
use mango_v4::state::{DynamicAccount, Group};
use mango_v4_client::snapshot_source::is_mango_account;
use mango_v4_client::{
account_update_stream, chain_data, snapshot_source, websocket_source, Client, MangoGroupContext,
};
use solana_sdk::account::ReadableAccount;
use solana_sdk::pubkey::Pubkey;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{Arc, RwLock};
use std::time::Duration;
pub async fn run(client: &Client, mango_group: Pubkey) -> anyhow::Result<()> {
let rpc_async = client.rpc_async();
let group_context = MangoGroupContext::new_from_rpc(&rpc_async, mango_group).await?;
let rpc_url = client.config().cluster.url().to_string();
let ws_url = client.config().cluster.ws_url().to_string();
let slot = client.rpc_async().get_slot().await?;
let ts = chrono::Utc::now().timestamp() as u64;
let extra_accounts = group_context
.tokens
.values()
.map(|value| value.oracle)
.chain(group_context.perp_markets.values().map(|p| p.oracle))
.chain(group_context.tokens.values().flat_map(|value| value.vaults))
.chain(group_context.address_lookup_tables.iter().copied())
.unique()
.filter(|pk| *pk != Pubkey::default())
.collect::<Vec<Pubkey>>();
let serum_programs = group_context
.serum3_markets
.values()
.map(|s3| s3.serum_program)
.unique()
.collect_vec();
let (account_update_sender, account_update_receiver) =
async_channel::unbounded::<account_update_stream::Message>();
// Sourcing account and slot data from solana via websockets
websocket_source::start(
websocket_source::Config {
rpc_ws_url: ws_url.clone(),
serum_programs,
open_orders_authority: mango_group,
},
extra_accounts.clone(),
account_update_sender.clone(),
);
let first_websocket_slot = websocket_source::get_next_create_bank_slot(
account_update_receiver.clone(),
Duration::from_secs(10),
)
.await?;
// Getting solana account snapshots via jsonrpc
snapshot_source::start(
snapshot_source::Config {
rpc_http_url: rpc_url.clone(),
mango_group,
get_multiple_accounts_count: 100,
parallel_rpc_requests: 10,
snapshot_interval: Duration::from_secs(6000),
min_slot: first_websocket_slot + 10,
},
extra_accounts,
account_update_sender,
);
let mut chain_data = chain_data::ChainData::new();
use account_update_stream::Message;
loop {
let message = account_update_receiver
.recv()
.await
.expect("channel not closed");
message.update_chain_data(&mut chain_data);
match message {
Message::Account(_) => {}
Message::Snapshot(snapshot) => {
for slot in snapshot.iter().map(|a| a.slot).unique() {
chain_data.update_slot(chain_data::SlotData {
slot,
parent: None,
status: chain_data::SlotStatus::Rooted,
chain: 0,
});
}
break;
}
_ => {}
}
}
let group = &chain_data.account(&mango_group).unwrap().account.clone();
let group = group.load::<Group>()?;
let chain_data = Arc::new(RwLock::new(chain_data));
let account_fetcher = Arc::new(chain_data::AccountFetcher {
chain_data: chain_data.clone(),
rpc: client.new_rpc_async(),
});
for (key, data) in chain_data.read().unwrap().iter_accounts() {
if let Some(account) = is_mango_account(&data.account, &mango_group) {
// let dyn_part = account.dynamic.clone();
// let dyn_part = RefCell::new(*dyn_part);
let fixed = account.fixed.clone();
let fixed_cell = RefCell::new(fixed);
let mut account = DynamicAccount {
header: account.header,
fixed: fixed_cell.borrow_mut(),
dynamic: account.dynamic.iter().map(|x| *x).collect::<Vec<u8>>(),
};
let acc = account_fetcher.fetch_mango_account(key)?;
let (health_remaining_ams, _) = group_context
.derive_health_check_remaining_account_metas(
&acc,
vec![],
vec![],
vec![],
HashMap::new(),
)
.unwrap();
let mut remaining_accounts: Vec<_> = health_remaining_ams
.into_iter()
.map(|x| {
let xx = account_fetcher.fetch_raw(&x.pubkey).unwrap();
TestAccount::new(
xx.data().iter().map(|x| *x).collect(),
x.pubkey,
*xx.owner(),
)
})
.collect();
let remaining_accounts = remaining_accounts
.iter_mut()
.map(|x| return x.as_account_info())
.collect::<Vec<_>>();
let mut out = HashMap::new();
// Act like it was never charged, but not initial call (0)
account.borrow_mut().fixed.last_collateral_fee_charge = 1;
match token_charge_collateral_fees_internal(
account,
group,
remaining_accounts.as_slice(),
mango_group,
*key,
(ts, slot),
Some(&mut out),
) {
Ok(_) => {
for (x, fee) in out {
println!(
"{} -> Token: {} => {} ({} $)",
key,
group_context.tokens.get(&x).unwrap().name,
fee.0 / 2,
fee.1 / 2
);
}
}
Err(e) => {
println!("{} -> Error: {:?}", key, e);
}
}
}
}
Ok(())
}
#[derive(Clone)]
pub struct TestAccount {
pub bytes: Vec<u8>,
pub pubkey: Pubkey,
pub owner: Pubkey,
pub lamports: u64,
}
impl TestAccount {
pub fn new(bytes: Vec<u8>, pubkey: Pubkey, owner: Pubkey) -> Self {
Self {
bytes,
owner,
pubkey,
lamports: 0,
}
}
pub fn as_account_info(&mut self) -> AccountInfo {
AccountInfo {
key: &self.pubkey,
owner: &self.owner,
lamports: Rc::new(RefCell::new(&mut self.lamports)),
data: Rc::new(RefCell::new(&mut self.bytes)),
is_signer: false,
is_writable: false,
executable: false,
rent_epoch: 0,
}
}
}

View File

@ -1,11 +1,11 @@
use std::collections::HashMap;
use itertools::Itertools;
use mango_v4::accounts_zerocopy::KeyedAccount;
use mango_v4::state::OracleAccountInfos;
use mango_v4_client::{Client, MangoGroupContext};
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::pubkey::Pubkey;
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::*;
pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> {
@ -44,6 +44,7 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> {
}
let response = response.unwrap();
let slot = response.context.slot;
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
let accounts = response.value;
for (pubkey, account_opt) in oracles.iter().zip(accounts.into_iter()) {
@ -60,9 +61,10 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> {
let perp_opt = perp_markets.get(pubkey);
let mut price = None;
if let Some(bank) = bank_opt {
match bank
.oracle_price(&OracleAccountInfos::from_reader(&keyed_account), Some(slot))
{
match bank.oracle_price(
&OracleAccountInfos::from_reader(&keyed_account),
Some((now, slot)),
) {
Ok(p) => price = Some(p),
Err(e) => {
error!("could not read bank oracle {}: {e:?}", keyed_account.key);
@ -70,9 +72,10 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> {
}
}
if let Some(perp) = perp_opt {
match perp
.oracle_price(&OracleAccountInfos::from_reader(&keyed_account), Some(slot))
{
match perp.oracle_price(
&OracleAccountInfos::from_reader(&keyed_account),
Some((now, slot)),
) {
Ok(p) => price = Some(p),
Err(e) => {
error!("could not read perp oracle {}: {e:?}", keyed_account.key);

View File

@ -74,6 +74,7 @@ mod tests {
price: Default::default(),
deviation: Default::default(),
last_update_slot: 0,
last_update_time: None,
oracle_type: OracleType::Pyth,
},
&OracleConfig {

View File

@ -1,6 +1,6 @@
use std::collections::HashMap;
use anchor_client::ClientError;
use std::collections::HashMap;
use std::time::SystemTime;
use anchor_lang::__private::bytemuck;
@ -683,6 +683,10 @@ impl MangoGroupContext {
.fetch_multiple_accounts(&oracle_keys)
.await?;
let now_slot = account_fetcher.get_slot().await?;
let now_ts = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system time after epoch start")
.as_secs();
let mut stale_oracles_with_fallbacks = vec![];
for (key, acc) in oracle_accounts {
@ -691,8 +695,10 @@ impl MangoGroupContext {
&OracleAccountInfos::from_reader(&KeyedAccountSharedData::new(key, acc)),
token.decimals,
)?;
let oracle_is_valid = state
.check_confidence_and_maybe_staleness(&token.oracle_config, Some(now_slot));
let oracle_is_valid = state.check_confidence_and_maybe_staleness(
&token.oracle_config,
Some((now_ts, now_slot)),
);
if oracle_is_valid.is_err() && token.fallback_context.key != Pubkey::default() {
stale_oracles_with_fallbacks
.push((token.oracle, token.fallback_context.clone()));

View File

@ -43,7 +43,7 @@ pub async fn new(
n_perps: active_perp_len,
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len * 2,
staleness_slot: None,
now: None,
begin_fallback_oracles: metas.len(),
usdc_oracle_index: metas
.iter()
@ -88,7 +88,7 @@ pub fn new_sync(
n_perps: active_perp_len,
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len * 2,
staleness_slot: None,
now: None,
begin_fallback_oracles: metas.len(),
usdc_oracle_index: None,
sol_oracle_index: None,

View File

@ -1,5 +1,5 @@
{
"version": "0.24.1",
"version": "0.24.2",
"name": "mango_v4",
"instructions": [
{
@ -11166,6 +11166,9 @@
},
{
"name": "SwitchboardOnDemand"
},
{
"name": "PythV2"
}
]
}

View File

@ -69,6 +69,7 @@
"@iarna/toml": "2.2.5",
"@project-serum/serum": "0.13.65",
"@pythnetwork/client": "~2.14.0",
"@pythnetwork/pyth-solana-receiver": "^0.8.0",
"@raydium-io/raydium-sdk": "^1.3.1-beta.57",
"@solana/spl-token": "0.3.7",
"@solana/web3.js": "^1.78.2",
@ -91,6 +92,5 @@
"**/cross-fetch/node-fetch": "npm:@blockworks-foundation/node-fetch@2.6.11",
"**/@blockworks-foundation/mangolana/node-fetch": "npm:@blockworks-foundation/node-fetch@2.6.11"
},
"license": "MIT",
"packageManager": "yarn@4.3.1"
"license": "MIT"
}

View File

@ -1,6 +1,6 @@
[package]
name = "mango-v4"
version = "0.24.1"
version = "0.24.2"
description = "Created with Anchor"
edition = "2021"
@ -39,6 +39,7 @@ fixed = { workspace = true, features = [
] }
num_enum = "0.5.1"
pyth-sdk-solana = { workspace = true }
pyth-solana-receiver-sdk = { workspace = true }
serde = "^1.0"
serum_dex = { workspace = true, features = ["no-entrypoint", "program"] }
solana-address-lookup-table-program = { workspace = true }

View File

@ -61,7 +61,7 @@ pub struct FixedOrderAccountRetriever<T: KeyedAccountReader> {
pub n_perps: usize,
pub begin_perp: usize,
pub begin_serum3: usize,
pub staleness_slot: Option<u64>,
pub now: Option<(u64, u64)>,
pub begin_fallback_oracles: usize,
pub usdc_oracle_index: Option<usize>,
pub sol_oracle_index: Option<usize>,
@ -74,7 +74,7 @@ pub struct FixedOrderAccountRetriever<T: KeyedAccountReader> {
pub fn new_fixed_order_account_retriever<'a, 'info>(
ais: &'a [AccountInfo<'info>],
account: &MangoAccountRef,
now_slot: u64,
now: (u64, u64),
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
let active_token_len = account.active_token_positions().count();
@ -83,7 +83,7 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
ai.load::<Bank>()?;
}
new_fixed_order_account_retriever_inner(ais, account, now_slot, active_token_len)
new_fixed_order_account_retriever_inner(ais, account, now, active_token_len)
}
/// A FixedOrderAccountRetriever with n_banks <= active_token_positions().count(),
@ -94,7 +94,7 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
pub fn new_fixed_order_account_retriever_with_optional_banks<'a, 'info>(
ais: &'a [AccountInfo<'info>],
account: &MangoAccountRef,
now_slot: u64,
now: (u64, u64),
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
// Scan for the number of banks provided
let mut n_banks = 0;
@ -110,13 +110,13 @@ pub fn new_fixed_order_account_retriever_with_optional_banks<'a, 'info>(
let active_token_len = account.active_token_positions().count();
require_gte!(active_token_len, n_banks);
new_fixed_order_account_retriever_inner(ais, account, now_slot, n_banks)
new_fixed_order_account_retriever_inner(ais, account, now, n_banks)
}
pub fn new_fixed_order_account_retriever_inner<'a, 'info>(
ais: &'a [AccountInfo<'info>],
account: &MangoAccountRef,
now_slot: u64,
now: (u64, u64),
n_banks: usize,
) -> Result<FixedOrderAccountRetriever<AccountInfoRef<'a, 'info>>> {
let active_serum3_len = account.active_serum3_orders().count();
@ -142,7 +142,7 @@ pub fn new_fixed_order_account_retriever_inner<'a, 'info>(
n_perps: active_perp_len,
begin_perp: n_banks * 2,
begin_serum3: n_banks * 2 + active_perp_len * 2,
staleness_slot: Some(now_slot),
now: Some(now),
begin_fallback_oracles: expected_ais,
usdc_oracle_index,
sol_oracle_index,
@ -190,7 +190,7 @@ impl<T: KeyedAccountReader> FixedOrderAccountRetriever<T> {
fn oracle_price_perp(&self, account_index: usize, perp_market: &PerpMarket) -> Result<I80F48> {
let oracle = &self.ais[account_index];
let oracle_acc_infos = OracleAccountInfos::from_reader(oracle);
perp_market.oracle_price(&oracle_acc_infos, self.staleness_slot)
perp_market.oracle_price(&oracle_acc_infos, self.now)
}
#[inline(always)]
@ -234,7 +234,7 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
let oracle_index = self.n_banks + bank_account_index;
let oracle_acc_infos = &self.create_oracle_infos(oracle_index, &bank.fallback_oracle);
let oracle_price_result = bank.oracle_price(oracle_acc_infos, self.staleness_slot);
let oracle_price_result = bank.oracle_price(oracle_acc_infos, self.now);
let oracle_price = oracle_price_result.with_context(|| {
format!(
"getting oracle for bank with health account index {} and token index {}, passed account {}",
@ -299,7 +299,7 @@ pub struct ScannedBanksAndOracles<'a, 'info> {
oracles: Vec<AccountInfoRef<'a, 'info>>,
fallback_oracles: Vec<AccountInfoRef<'a, 'info>>,
index_map: HashMap<TokenIndex, usize>,
staleness_slot: Option<u64>,
staleness_slot: Option<(u64, u64)>,
/// index in fallback_oracles
usd_oracle_index: Option<usize>,
/// index in fallback_oracles
@ -432,13 +432,17 @@ fn can_load_as<'a, T: ZeroCopy + Owner>(
impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
pub fn new(ais: &'a [AccountInfo<'info>], group: &Pubkey) -> Result<Self> {
Self::new_with_staleness(ais, group, Some(Clock::get()?.slot))
Self::new_with_staleness(
ais,
group,
Some(Clock::get().map(|c| (c.unix_timestamp as u64, c.slot as u64))?),
)
}
pub fn new_with_staleness(
ais: &'a [AccountInfo<'info>],
group: &Pubkey,
staleness_slot: Option<u64>,
staleness_slot: Option<(u64, u64)>,
) -> Result<Self> {
// find all Bank accounts
let mut token_index_map = HashMap::with_capacity(ais.len() / 2);
@ -755,8 +759,11 @@ mod tests {
perp1.as_account_info(),
oracle2_clone.as_account_info(),
];
let retriever =
new_fixed_order_account_retriever_with_optional_banks(&ais, &account.borrow(), 0)
let retriever = new_fixed_order_account_retriever_with_optional_banks(
&ais,
&account.borrow(),
(0, 0),
)
.unwrap();
assert_eq!(retriever.available_banks(), Ok(vec![10, 20, 30]));
@ -785,8 +792,11 @@ mod tests {
perp1.as_account_info(),
oracle2_clone.as_account_info(),
];
let retriever =
new_fixed_order_account_retriever_with_optional_banks(&ais, &account.borrow(), 0)
let retriever = new_fixed_order_account_retriever_with_optional_banks(
&ais,
&account.borrow(),
(0, 0),
)
.unwrap();
assert_eq!(retriever.available_banks(), Ok(vec![10, 30]));
@ -806,8 +816,11 @@ mod tests {
// skip all
{
let ais = vec![perp1.as_account_info(), oracle2_clone.as_account_info()];
let retriever =
new_fixed_order_account_retriever_with_optional_banks(&ais, &account.borrow(), 0)
let retriever = new_fixed_order_account_retriever_with_optional_banks(
&ais,
&account.borrow(),
(0, 0),
)
.unwrap();
assert_eq!(retriever.available_banks(), Ok(vec![]));

View File

@ -96,7 +96,11 @@ pub fn compute_health_from_fixed_accounts(
ais: &[AccountInfo],
now_ts: u64,
) -> Result<I80F48> {
let retriever = new_fixed_order_account_retriever(ais, account, Clock::get()?.slot)?;
let retriever = new_fixed_order_account_retriever(
ais,
account,
Clock::get().map(|c| (c.unix_timestamp as u64, c.slot as u64))?,
)?;
Ok(new_health_cache(account, &retriever, now_ts)?.health(health_type))
}
@ -2007,7 +2011,7 @@ mod tests {
let retriever = new_fixed_order_account_retriever_with_optional_banks(
&ais,
&account.borrow(),
0,
(0, 0),
)
.unwrap();
new_health_cache_skipping_missing_banks_and_bad_oracles(

View File

@ -41,14 +41,14 @@ pub fn account_buyback_fees_with_mngo(
let mngo_oracle_ref = &AccountInfoRef::borrow(&ctx.accounts.mngo_oracle.as_ref())?;
let mngo_oracle_price = mngo_bank.oracle_price(
&OracleAccountInfos::from_reader(mngo_oracle_ref),
Some(slot),
Some((now_ts, slot)),
)?;
let mngo_asset_price = mngo_oracle_price.min(mngo_bank.stable_price());
let fees_oracle_ref = &AccountInfoRef::borrow(&ctx.accounts.fees_oracle.as_ref())?;
let fees_oracle_price = fees_bank.oracle_price(
&OracleAccountInfos::from_reader(fees_oracle_ref),
Some(slot),
Some((now_ts, slot)),
)?;
let fees_liab_price = fees_oracle_price.max(fees_bank.stable_price());

View File

@ -394,7 +394,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
let retriever = new_fixed_order_account_retriever_with_optional_banks(
health_ais,
&account.borrow(),
now_slot,
(now_ts, now_slot),
)?;
let health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
@ -523,7 +523,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
let retriever = new_fixed_order_account_retriever_with_optional_banks(
health_ais,
&account.borrow(),
now_slot,
(now_ts, now_slot),
)?;
let health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),

View File

@ -33,10 +33,13 @@ pub fn perp_force_close_position(ctx: Context<PerpForceClosePosition>) -> Result
.base_position_lots()
.min(account_b_perp_position.base_position_lots().abs())
.max(0);
let now_slot = Clock::get()?.slot;
let clock = Clock::get()?;
let (now_ts, now_slot) = (clock.unix_timestamp as u64, clock.slot);
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
let oracle_price =
perp_market.oracle_price(&OracleAccountInfos::from_reader(oracle_ref), Some(now_slot))?;
let oracle_price = perp_market.oracle_price(
&OracleAccountInfos::from_reader(oracle_ref),
Some((now_ts, now_slot)),
)?;
let quote_transfer = I80F48::from(base_transfer * perp_market.base_lot_size) * oracle_price;
account_a_perp_position.record_trade(&mut perp_market, -base_transfer, quote_transfer);

View File

@ -14,8 +14,11 @@ pub fn perp_liq_force_cancel_orders(
let (now_ts, now_slot) = clock_now();
let mut health_cache = {
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow(), now_slot)?;
let retriever = new_fixed_order_account_retriever(
ctx.remaining_accounts,
&account.borrow(),
(now_ts, now_slot),
)?;
new_health_cache(&account.borrow(), &retriever, now_ts).context("create health cache")?
};

View File

@ -34,14 +34,16 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
perp_market_index = perp_market.perp_market_index;
settle_token_index = perp_market.settle_token_index;
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
perp_oracle_price = perp_market
.oracle_price(&OracleAccountInfos::from_reader(oracle_ref), Some(now_slot))?;
perp_oracle_price = perp_market.oracle_price(
&OracleAccountInfos::from_reader(oracle_ref),
Some((now_ts, now_slot)),
)?;
let settle_bank = ctx.accounts.settle_bank.load()?;
let settle_oracle_ref = &AccountInfoRef::borrow(ctx.accounts.settle_oracle.as_ref())?;
settle_token_oracle_price = settle_bank.oracle_price(
&OracleAccountInfos::from_reader(settle_oracle_ref),
Some(now_slot),
Some((now_ts, now_slot)),
)?;
drop(settle_bank); // could be the same as insurance_bank
@ -51,7 +53,7 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
// the liqee isn't guaranteed to have an insurance fund token position.
insurance_token_oracle_price = insurance_bank.oracle_price(
&OracleAccountInfos::from_reader(insurance_oracle_ref),
Some(now_slot),
Some((now_ts, now_slot)),
)?;
}

View File

@ -70,7 +70,7 @@ pub fn perp_place_order(
let retriever = new_fixed_order_account_retriever_with_optional_banks(
ctx.remaining_accounts,
&account.borrow(),
now_slot,
(now_ts, now_slot),
)?;
let health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),

View File

@ -125,8 +125,11 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
// Verify that the result of settling did not violate the health of the account that lost money
let (now_ts, now_slot) = clock_now();
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow(), now_slot)?;
let retriever = new_fixed_order_account_retriever(
ctx.remaining_accounts,
&account.borrow(),
(now_ts, now_slot),
)?;
let health = compute_health(&account.borrow(), HealthType::Init, &retriever, now_ts)?;
require!(health >= 0, MangoError::HealthMustBePositive);

View File

@ -15,8 +15,10 @@ pub fn perp_update_funding(ctx: Context<PerpUpdateFunding>) -> Result<()> {
let now_slot = Clock::get()?.slot;
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
let oracle_state =
perp_market.oracle_state(&OracleAccountInfos::from_reader(oracle_ref), Some(now_slot))?;
let oracle_state = perp_market.oracle_state(
&OracleAccountInfos::from_reader(oracle_ref),
Some((now_ts, now_slot)),
)?;
perp_market.update_funding_and_stable_price(&book, &oracle_state, now_ts)?;

View File

@ -58,8 +58,11 @@ pub fn serum3_liq_force_cancel_orders(
//
let mut health_cache = {
let mut account = ctx.accounts.account.load_full_mut()?;
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow(), now_slot)?;
let retriever = new_fixed_order_account_retriever(
ctx.remaining_accounts,
&account.borrow(),
(now_ts, now_slot),
)?;
let health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)
.context("create health cache")?;

View File

@ -82,7 +82,7 @@ pub fn serum3_place_order(
let retriever = new_fixed_order_account_retriever_with_optional_banks(
ctx.remaining_accounts,
&account.borrow(),
now_slot,
(now_ts, now_slot),
)?;
let mut health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
@ -610,7 +610,7 @@ pub fn apply_settle_changes(
let quote_oracle_ref = &AccountInfoRef::borrow(quote_oracle_ai)?;
let quote_oracle_price = quote_bank.oracle_price(
&OracleAccountInfos::from_reader(quote_oracle_ref),
Some(clock.slot),
Some((now_ts, clock.slot)),
)?;
let quote_asset_price = quote_oracle_price.min(quote_bank.stable_price());
account

View File

@ -188,7 +188,7 @@ pub fn charge_loan_origination_fees(
let ai_ref = &AccountInfoRef::borrow(ai)?;
base_bank.oracle_price(
&OracleAccountInfos::from_reader(ai_ref),
Some(Clock::get()?.slot),
Some(Clock::get().map(|c| (c.unix_timestamp as u64, c.slot as u64))?),
)
})
.transpose()?;
@ -228,7 +228,7 @@ pub fn charge_loan_origination_fees(
let ai_ref = &AccountInfoRef::borrow(ai)?;
quote_bank.oracle_price(
&OracleAccountInfos::from_reader(ai_ref),
Some(Clock::get()?.slot),
Some(Clock::get().map(|c| (c.unix_timestamp as u64, c.slot as u64))?),
)
})
.transpose()?;

View File

@ -4,14 +4,40 @@ use crate::state::*;
use crate::util::clock_now;
use anchor_lang::prelude::*;
use fixed::types::I80F48;
use std::collections::HashMap;
use std::ops::{Deref, Div};
use crate::accounts_ix::*;
use crate::logs::{emit_stack, TokenBalanceLog, TokenCollateralFeeLog};
pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) -> Result<()> {
let group = ctx.accounts.group.load()?;
let mut account = ctx.accounts.account.load_full_mut()?;
let (now_ts, now_slot) = clock_now();
token_charge_collateral_fees_internal(
ctx.accounts.account.load_full_mut()?,
ctx.accounts.group.load()?.deref(),
&ctx.remaining_accounts,
ctx.accounts.group.key(),
ctx.accounts.account.key(),
clock_now(),
None,
)
}
pub fn token_charge_collateral_fees_internal<Header, Fixed, Dynamic>(
mut account: DynamicAccount<Header, Fixed, Dynamic>,
group: &Group,
remaining_accounts: &[AccountInfo],
group_key: Pubkey,
account_key: Pubkey,
now: (u64, u64),
mut out_fees: Option<&mut HashMap<TokenIndex, (I80F48, I80F48)>>,
) -> Result<()>
where
Header: DerefOrBorrowMut<MangoAccountDynamicHeader> + DerefOrBorrow<MangoAccountDynamicHeader>,
Fixed: DerefOrBorrowMut<MangoAccountFixed> + DerefOrBorrow<MangoAccountFixed>,
Dynamic: DerefOrBorrowMut<[u8]> + DerefOrBorrow<[u8]>,
{
let mut account = account.borrow_mut();
let (now_ts, now_slot) = now;
if group.collateral_fee_interval == 0 {
// By resetting, a new enabling of collateral fees will not immediately create a charge
@ -42,11 +68,21 @@ pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) ->
let time_scaling = I80F48::from(charge_seconds) * inv_seconds_per_day;
let health_cache = {
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow(), now_slot)?;
let retriever = new_fixed_order_account_retriever(
remaining_accounts,
&account.borrow(),
(now_ts, now_slot),
)?;
new_health_cache(&account.borrow(), &retriever, now_ts)?
};
let (_, liabs) = health_cache.assets_and_liabs();
// Account with liabs below ~100$ should not be charged any collateral fees
if liabs < 100_000_000 {
// msg!("liabs {}, below threshold to charge collateral fees", liabs);
return Ok(());
}
// We want to find the total asset health and total liab health, but don't want
// to treat borrows that moved into open orders accounts as realized. Hence we
// pretend all spot orders are closed and settled and add their funds back to
@ -80,15 +116,21 @@ pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) ->
let scaling = asset_usage_scaling * time_scaling;
let mut total_collateral_fees_in_usd = I80F48::ZERO;
let token_position_count = account.active_token_positions().count();
for bank_ai in &ctx.remaining_accounts[0..token_position_count] {
for bank_ai in &remaining_accounts[0..token_position_count] {
let mut bank = bank_ai.load_mut::<Bank>()?;
if bank.collateral_fee_per_day <= 0.0 || bank.maint_asset_weight.is_zero() {
continue;
}
let (token_position, raw_token_index) = account.token_position_mut(bank.token_index)?;
let token_balance = token_position.native(&bank);
let token_index_in_health_cache = health_cache
.token_infos
.iter()
.position(|x| x.token_index == bank.token_index)
.expect("missing token in health");
let token_balance = token_balances[token_index_in_health_cache].spot_and_perp;
if token_balance <= 0 {
continue;
}
@ -96,29 +138,66 @@ pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) ->
let fee = token_balance * scaling * I80F48::from_num(bank.collateral_fee_per_day);
assert!(fee <= token_balance);
let is_active = bank.withdraw_without_fee(token_position, fee, now_ts)?;
if !is_active {
account.deactivate_token_position_and_log(raw_token_index, ctx.accounts.account.key());
}
let token_info = health_cache.token_info(bank.token_index)?;
total_collateral_fees_in_usd += fee * token_info.prices.oracle;
bank.collected_fees_native += fee;
bank.collected_collateral_fees += fee;
let token_info = health_cache.token_info(bank.token_index)?;
let token_position = account.token_position(bank.token_index)?;
emit_stack(TokenCollateralFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
mango_group: group_key,
mango_account: account_key,
token_index: bank.token_index,
fee: fee.to_bits(),
asset_usage_fraction: asset_usage_scaling.to_bits(),
price: token_info.prices.oracle.to_bits(),
});
}
for bank_ai in &remaining_accounts[0..token_position_count] {
let mut bank = bank_ai.load_mut::<Bank>()?;
let token_info = health_cache.token_info(bank.token_index)?;
let token_index_in_health_cache = health_cache
.token_infos
.iter()
.position(|x| x.token_index == bank.token_index)
.expect("missing token in health");
let token_balance = token_balances[token_index_in_health_cache].spot_and_perp;
let health = token_info.health_contribution(HealthType::Maint, token_balance);
if health >= 0 {
continue;
}
let (token_position, raw_token_index) = account.token_position_mut(bank.token_index)?;
let borrow_scaling = (health / total_liab_health).abs();
let fee = borrow_scaling * total_collateral_fees_in_usd / token_info.prices.oracle;
if let Some(ref mut output) = out_fees {
output.insert(
token_info.token_index,
(
fee,
(fee * token_info.prices.oracle).div(I80F48::from_num(1_000_000)),
),
);
}
let is_active = bank.withdraw_without_fee(token_position, fee, now_ts)?;
if !is_active {
account.deactivate_token_position_and_log(raw_token_index, account_key);
}
bank.collected_fees_native += fee;
let token_position = account.token_position(bank.token_index)?;
emit_stack(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
mango_group: group_key,
mango_account: account_key,
token_index: bank.token_index,
indexed_position: token_position.indexed_position.to_bits(),
deposit_index: bank.deposit_index.to_bits(),

View File

@ -124,7 +124,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
let retriever = new_fixed_order_account_retriever_with_optional_banks(
remaining_accounts,
&account.borrow(),
now_slot,
(now_ts, now_slot),
)?;
// We only compute health to check if the account leaves the being_liquidated state.
@ -208,12 +208,15 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64, reduce_only: bool)
// Activating a new token position requires that the oracle is in a good state.
// Otherwise users could abuse oracle staleness to delay liquidation.
if !token_position_exists {
let now_slot = Clock::get()?.slot;
let (now_ts, now_slot) =
Clock::get().map(|c| (c.unix_timestamp as u64, c.slot as u64))?;
let bank = ctx.accounts.bank.load()?;
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
let oracle_result =
bank.oracle_price(&OracleAccountInfos::from_reader(oracle_ref), Some(now_slot));
let oracle_result = bank.oracle_price(
&OracleAccountInfos::from_reader(oracle_ref),
Some((now_ts, now_slot)),
);
if let Err(e) = oracle_result {
msg!("oracle must be valid when creating a new token position");
return Err(e);

View File

@ -40,9 +40,11 @@ pub fn token_update_index_and_rate(
// 2. we want to forbid cpi, since ix we would like to blacklist could just be called from cpi
require!(
(ix.program_id == crate::id()
&& ix.data[0..8]
== crate::instruction::TokenUpdateIndexAndRate::discriminator())
|| (ix.program_id == compute_budget::id()),
&& (ix.data[0..8]
== crate::instruction::TokenUpdateIndexAndRate::discriminator()
|| ix.data[0..8]
== crate::instruction::TokenUpdateIndexAndRateResilient::discriminator(
))) || (ix.program_id == compute_budget::id()),
MangoError::SomeError
);
@ -77,7 +79,7 @@ pub fn token_update_index_and_rate(
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
let price = some_bank.oracle_price(
&OracleAccountInfos::from_reader(oracle_ref),
Some(clock.slot),
Some((clock.unix_timestamp as u64, clock.slot)),
);
// Early exit if oracle is invalid

View File

@ -31,7 +31,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
let retriever = new_fixed_order_account_retriever_with_optional_banks(
ctx.remaining_accounts,
&account.borrow(),
now_slot,
(now_ts, now_slot),
)?;
let health_cache = new_health_cache_skipping_missing_banks_and_bad_oracles(
&account.borrow(),
@ -217,15 +217,15 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// When borrowing the price has be trustworthy, so we can do a reasonable
// net borrow check.
let slot_opt = Some(Clock::get()?.slot);
let now_opt = Some(Clock::get().map(|c| (c.unix_timestamp as u64, c.slot as u64))?);
unsafe_oracle_state
.check_confidence_and_maybe_staleness(&bank.oracle_config, slot_opt)
.check_confidence_and_maybe_staleness(&bank.oracle_config, now_opt)
.with_context(|| {
oracle_log_context(
bank.name(),
&unsafe_oracle_state,
&bank.oracle_config,
slot_opt,
now_opt,
)
})?;
bank.check_net_borrows(unsafe_oracle_state.price)?;

View File

@ -1184,45 +1184,30 @@ impl Bank {
pub fn oracle_price<T: KeyedAccountReader>(
&self,
oracle_acc_infos: &OracleAccountInfos<T>,
staleness_slot: Option<u64>,
now: Option<(u64, u64)>, // (now_ts, now_slot)
) -> Result<I80F48> {
require_keys_eq!(self.oracle, *oracle_acc_infos.oracle.key());
let primary_state = oracle::oracle_state_unchecked(oracle_acc_infos, self.mint_decimals)?;
let primary_ok =
primary_state.check_confidence_and_maybe_staleness(&self.oracle_config, staleness_slot);
primary_state.check_confidence_and_maybe_staleness(&self.oracle_config, now);
if primary_ok.is_oracle_error() && oracle_acc_infos.fallback_opt.is_some() {
let fallback_oracle_acc = oracle_acc_infos.fallback_opt.unwrap();
require_keys_eq!(self.fallback_oracle, *fallback_oracle_acc.key());
let fallback_state =
oracle::fallback_oracle_state_unchecked(&oracle_acc_infos, self.mint_decimals)?;
let fallback_ok = fallback_state
.check_confidence_and_maybe_staleness(&self.oracle_config, staleness_slot);
let fallback_ok =
fallback_state.check_confidence_and_maybe_staleness(&self.oracle_config, now);
fallback_ok.with_context(|| {
format!(
"{} {}",
oracle_log_context(
self.name(),
&primary_state,
&self.oracle_config,
staleness_slot
),
oracle_log_context(
self.name(),
&fallback_state,
&self.oracle_config,
staleness_slot
)
oracle_log_context(self.name(), &primary_state, &self.oracle_config, now),
oracle_log_context(self.name(), &fallback_state, &self.oracle_config, now)
)
})?;
Ok(fallback_state.price)
} else {
primary_ok.with_context(|| {
oracle_log_context(
self.name(),
&primary_state,
&self.oracle_config,
staleness_slot,
)
oracle_log_context(self.name(), &primary_state, &self.oracle_config, now)
})?;
Ok(primary_state.price)
}

View File

@ -1,5 +1,6 @@
use std::mem::size_of;
use super::{load_raydium_pool_state, orca_mainnet_whirlpool, raydium_mainnet};
use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::state::load_orca_pool_state;
@ -7,13 +8,12 @@ use anchor_lang::prelude::*;
use anchor_lang::{AnchorDeserialize, Discriminator};
use derivative::Derivative;
use fixed::types::I80F48;
use pyth_solana_receiver_sdk::price_update::VerificationLevel;
use static_assertions::const_assert_eq;
use switchboard_on_demand::PullFeedAccountData;
use switchboard_program::FastRoundResultAccountData;
use switchboard_v2::AggregatorAccountData;
use super::{load_raydium_pool_state, orca_mainnet_whirlpool, raydium_mainnet};
const DECIMAL_CONSTANT_ZERO_INDEX: i8 = 12;
const DECIMAL_CONSTANTS: [I80F48; 25] = [
I80F48::from_bits((1 << 48) / 10i128.pow(12u32)),
@ -126,12 +126,14 @@ pub enum OracleType {
OrcaCLMM,
RaydiumCLMM,
SwitchboardOnDemand,
PythV2,
}
pub struct OracleState {
pub price: I80F48,
pub deviation: I80F48,
pub last_update_slot: u64,
pub last_update_time: Option<u64>,
pub oracle_type: OracleType,
}
@ -140,23 +142,41 @@ impl OracleState {
pub fn check_confidence_and_maybe_staleness(
&self,
config: &OracleConfig,
staleness_slot: Option<u64>,
now: Option<(u64, u64)>, // (now_ts, now_slot)
) -> Result<()> {
if let Some(now_slot) = staleness_slot {
self.check_staleness(config, now_slot)?;
if let Some((now_ts, now_slot)) = now {
self.check_staleness(config, now_slot, now_ts)?;
}
self.check_confidence(config)
}
pub fn check_staleness(&self, config: &OracleConfig, now_slot: u64) -> Result<()> {
if config.max_staleness_slots >= 0
&& self
pub fn check_staleness(&self, config: &OracleConfig, now_slot: u64, now_ts: u64) -> Result<()> {
if config.max_staleness_slots < 0 {
return Ok(());
}
if self
.last_update_slot
.saturating_add(config.max_staleness_slots as u64)
< now_slot
{
return Err(MangoError::OracleStale.into());
}
if self.last_update_time.is_some() {
let current_time_in_msecs = now_ts * 1000;
let last_update_time_in_msecs = self.last_update_time.unwrap() * 1000;
let max_acceptable_update_age_in_ms = (config.max_staleness_slots as u64) * 450;
let oldest_acceptable_time =
current_time_in_msecs.saturating_sub(max_acceptable_update_age_in_ms);
if last_update_time_in_msecs < oldest_acceptable_time {
msg!("Oracle stale (using time fallback method: current time: {} vs published time: {})", current_time_in_msecs, last_update_time_in_msecs);
return Err(MangoError::OracleStale.into());
}
}
Ok(())
}
@ -210,6 +230,8 @@ pub fn determine_oracle_type(acc_info: &impl KeyedAccountReader) -> Result<Oracl
return Ok(OracleType::OrcaCLMM);
} else if acc_info.owner() == &raydium_mainnet::ID {
return Ok(OracleType::RaydiumCLMM);
} else if acc_info.owner() == &pyth_solana_receiver_sdk::ID {
return Ok(OracleType::PythV2);
}
Err(MangoError::UnknownOracleType.into())
@ -287,6 +309,38 @@ pub fn get_pyth_state(
last_update_slot,
deviation,
oracle_type: OracleType::Pyth,
last_update_time: None,
})
}
pub fn get_pyth_on_demand_state(
acc_info: &(impl KeyedAccountReader + ?Sized),
base_decimals: u8,
) -> Result<OracleState> {
let mut data = acc_info.data();
data = &data[8..];
let price_account =
pyth_solana_receiver_sdk::price_update::PriceUpdateV2::deserialize(&mut data).unwrap();
if price_account.verification_level != VerificationLevel::Full {
return Err(MangoError::OracleConfidence.into());
}
let decimals =
(price_account.price_message.exponent as i8) + QUOTE_DECIMALS - (base_decimals as i8);
let decimal_adj = power_of_ten(decimals);
let price = I80F48::from_num(price_account.price_message.price) * decimal_adj;
let deviation = I80F48::from_num(price_account.price_message.conf) * decimal_adj;
let last_update_slot = price_account.posted_slot;
let price_timestamp = price_account.price_message.publish_time;
require_gte!(price, 0);
Ok(OracleState {
price,
last_update_slot,
deviation,
oracle_type: OracleType::PythV2,
last_update_time: Some(price_timestamp as u64),
})
}
@ -366,9 +420,11 @@ fn oracle_state_unchecked_inner<T: KeyedAccountReader>(
last_update_slot,
deviation,
oracle_type: OracleType::Stub,
last_update_time: None,
}
}
OracleType::Pyth => get_pyth_state(oracle_info, base_decimals)?,
OracleType::PythV2 => get_pyth_on_demand_state(oracle_info, base_decimals)?,
OracleType::SwitchboardV2 => {
fn from_foreign_error(e: impl std::fmt::Display) -> Error {
error_msg!("{}", e)
@ -397,6 +453,7 @@ fn oracle_state_unchecked_inner<T: KeyedAccountReader>(
last_update_slot,
deviation,
oracle_type: OracleType::SwitchboardV2,
last_update_time: None,
}
}
OracleType::SwitchboardV1 => {
@ -417,6 +474,7 @@ fn oracle_state_unchecked_inner<T: KeyedAccountReader>(
last_update_slot,
deviation,
oracle_type: OracleType::SwitchboardV1,
last_update_time: None,
}
}
OracleType::SwitchboardOnDemand => {
@ -446,6 +504,7 @@ fn oracle_state_unchecked_inner<T: KeyedAccountReader>(
last_update_slot,
deviation,
oracle_type: OracleType::SwitchboardOnDemand,
last_update_time: None,
}
}
OracleType::OrcaCLMM => {
@ -458,6 +517,7 @@ fn oracle_state_unchecked_inner<T: KeyedAccountReader>(
last_update_slot: quote_oracle_state.last_update_slot,
deviation: quote_oracle_state.deviation,
oracle_type: OracleType::OrcaCLMM,
last_update_time: None,
}
}
OracleType::RaydiumCLMM => {
@ -470,6 +530,7 @@ fn oracle_state_unchecked_inner<T: KeyedAccountReader>(
last_update_slot: quote_oracle_state.last_update_slot,
deviation: quote_oracle_state.deviation,
oracle_type: OracleType::RaydiumCLMM,
last_update_time: None,
}
}
})
@ -479,15 +540,15 @@ pub fn oracle_log_context(
name: &str,
state: &OracleState,
oracle_config: &OracleConfig,
staleness_slot: Option<u64>,
now: Option<(u64, u64)>,
) -> String {
format!(
"name: {}, price: {}, deviation: {}, last_update_slot: {}, now_slot: {}, conf_filter: {:#?}",
"name: {}, price: {}, deviation: {}, last_update_slot: {}, now: {:?}, conf_filter: {:#?}",
name,
state.price.to_num::<f64>(),
state.deviation.to_num::<f64>(),
state.last_update_slot,
staleness_slot.unwrap_or_else(|| u64::MAX),
now.unwrap_or_else(|| (u64::MAX, u64::MAX)),
oracle_config.conf_filter.to_num::<f32>(),
)
}
@ -530,6 +591,11 @@ mod tests {
OracleType::SwitchboardOnDemand,
switchboard_on_demand_mainnet_oracle::ID,
),
(
"7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE",
OracleType::PythV2,
pyth_solana_receiver_sdk::ID,
),
];
for fixture in fixtures {
@ -613,6 +679,49 @@ mod tests {
Ok(())
}
#[test]
pub fn test_pyth_on_demand_price() -> Result<()> {
// add ability to find fixtures
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/test");
let fixtures = vec![(
"7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE",
OracleType::PythV2,
pyth_solana_receiver_sdk::ID,
6,
)];
for fixture in fixtures {
let file = format!("resources/test/{}.bin", fixture.0);
let mut data = read_file(find_file(&file).unwrap());
let data = RefCell::new(&mut data[..]);
let ai = &AccountInfoRef {
key: &Pubkey::from_str(fixture.0).unwrap(),
owner: &fixture.2,
data: data.borrow(),
};
let base_decimals = fixture.3;
let sw_ais = OracleAccountInfos {
oracle: ai,
fallback_opt: None,
usdc_opt: None,
sol_opt: None,
};
let state = oracle_state_unchecked(&sw_ais, base_decimals).unwrap();
match fixture.1 {
OracleType::PythV2 => {
assert_eq!(state.price, I80F48::from_num(140.615948634614796))
}
_ => unimplemented!(),
}
}
Ok(())
}
#[test]
pub fn test_clmm_prices() -> Result<()> {
// add ability to find fixtures
@ -780,4 +889,40 @@ mod tests {
}
Ok(())
}
#[test]
fn use_time_for_max_staleness_check() {
let fixtures = vec![
(100_000, 100_000, false),
(100_000, 50_000, true),
(100_000, 150_000, false),
(100_000, 100_000 - 44, false),
(100_000, 100_000 - 46, true),
(100_000, 100_000 + 45, false),
(100_000, 100_000 + 300, false),
];
let config = OracleConfig {
conf_filter: Default::default(),
max_staleness_slots: 100,
reserved: [0; 72],
};
for (now_ts, publish_ts, expect_error) in fixtures {
let now_slot = 0;
let state = OracleState {
price: Default::default(),
deviation: Default::default(),
last_update_slot: now_slot,
last_update_time: Some(publish_ts),
oracle_type: OracleType::Pyth,
};
println!("test case: {}, {} => {}", now_ts, publish_ts, expect_error);
assert_eq!(
expect_error,
state.check_staleness(&config, now_slot, now_ts).is_err()
);
}
}
}

View File

@ -277,23 +277,21 @@ impl PerpMarket {
pub fn oracle_price<T: KeyedAccountReader>(
&self,
oracle_acc_infos: &OracleAccountInfos<T>,
staleness_slot: Option<u64>,
now: Option<(u64, u64)>,
) -> Result<I80F48> {
Ok(self.oracle_state(oracle_acc_infos, staleness_slot)?.price)
Ok(self.oracle_state(oracle_acc_infos, now)?.price)
}
pub fn oracle_state<T: KeyedAccountReader>(
&self,
oracle_acc_infos: &OracleAccountInfos<T>,
staleness_slot: Option<u64>,
now: Option<(u64, u64)>,
) -> Result<OracleState> {
require_keys_eq!(self.oracle, *oracle_acc_infos.oracle.key());
let state = oracle::oracle_state_unchecked(oracle_acc_infos, self.base_decimals)?;
state
.check_confidence_and_maybe_staleness(&self.oracle_config, staleness_slot)
.with_context(|| {
oracle_log_context(self.name(), &state, &self.oracle_config, staleness_slot)
})?;
.check_confidence_and_maybe_staleness(&self.oracle_config, now)
.with_context(|| oracle_log_context(self.name(), &state, &self.oracle_config, now))?;
Ok(state)
}

View File

@ -1,5 +1,11 @@
#![allow(unused_assignments)]
use super::*;
use crate::cases::test_serum::SerumOrderPlacer;
use num::ToPrimitive;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
#[tokio::test]
async fn test_collateral_fees() -> Result<(), TransportError> {
@ -10,11 +16,17 @@ async fn test_collateral_fees() -> Result<(), TransportError> {
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..2];
let mut prices = HashMap::new();
// 1 unit = 1$
prices.insert(mints[0].pubkey, 1_000_000f64);
prices.insert(mints[1].pubkey, 1_000_000f64);
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
prices: prices,
..mango_setup::GroupWithTokensConfig::default()
}
.create(solana)
@ -73,37 +85,8 @@ async fn test_collateral_fees() -> Result<(), TransportError> {
.await
.unwrap();
send_tx(
solana,
TokenEdit {
group,
admin,
mint: mints[0].pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
collateral_fee_per_day_opt: Some(0.1),
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
send_tx(
solana,
TokenEdit {
group,
admin,
mint: mints[1].pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
loan_origination_fee_rate_opt: Some(0.0),
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
set_collateral_fees(solana, admin, mints, group, 0, 0.1).await;
set_loan_orig_fee(solana, admin, mints, group, 1, 0.0).await;
//
// TEST: It works on empty accounts
@ -157,19 +140,7 @@ async fn test_collateral_fees() -> Result<(), TransportError> {
// TEST: With borrows, there's an effect depending on the time that has passed
//
send_tx(
solana,
TokenWithdrawInstruction {
amount: 500, // maint: -1.2 * 500 = -600 (half of 1200)
allow_borrow: true,
account,
owner,
token_account: context.users[1].token_accounts[1],
bank_index: 0,
},
)
.await
.unwrap();
withdraw(&context, solana, owner, account, 500, 1).await; // maint: -1.2 * 500 = -600 (half of 1200)
solana.set_clock_timestamp(last_time + 9 * hour).await;
@ -177,30 +148,26 @@ async fn test_collateral_fees() -> Result<(), TransportError> {
.await
.unwrap();
last_time = solana.clock_timestamp().await;
let fee = 1500.0 * (0.1 * (9.0 / 24.0) * (600.0 / 1200.0));
println!("fee -> {}", fee);
assert_eq_f64!(
account_position_f64(solana, account, tokens[0].bank).await,
1500.0 * (1.0 - 0.1 * (9.0 / 24.0) * (600.0 / 1200.0)),
1500.0,
0.01
);
let last_balance = account_position_f64(solana, account, tokens[0].bank).await;
assert_eq_f64!(
account_position_f64(solana, account, tokens[1].bank).await,
-500.0 - fee,
0.01
);
let last_balance = account_position_f64(solana, account, tokens[1].bank).await;
//
// TEST: More borrows
//
send_tx(
solana,
TokenWithdrawInstruction {
amount: 100, // maint: -1.2 * 600 = -720
allow_borrow: true,
account,
owner,
token_account: context.users[1].token_accounts[1],
bank_index: 0,
},
)
.await
.unwrap();
withdraw(&context, solana, owner, account, 100, 1).await; // maint: -1.2 * 600 = -720
solana.set_clock_timestamp(last_time + 7 * hour).await;
@ -208,11 +175,554 @@ async fn test_collateral_fees() -> Result<(), TransportError> {
.await
.unwrap();
//last_time = solana.clock_timestamp().await;
let fee = 1500.0 * 0.1 * (7.0 / 24.0) * ((last_balance.abs() + 100.0) * 1.2 / (1500.0 * 0.8));
println!("fee -> {}", fee);
assert_eq_f64!(
account_position_f64(solana, account, tokens[0].bank).await,
last_balance * (1.0 - 0.1 * (7.0 / 24.0) * (720.0 / (last_balance * 0.8))),
1500.0,
0.01
);
assert_eq_f64!(
account_position_f64(solana, account, tokens[1].bank).await,
-(last_balance.abs() + 100.0) - fee,
0.01
);
Ok(())
}
#[tokio::test]
async fn test_collateral_fees_multi() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..4];
let mut prices = HashMap::new();
prices.insert(mints[0].pubkey, 1_000_000f64); // 1 unit = 1$
prices.insert(mints[1].pubkey, 3_000_000f64); // 1 unit = 3$
prices.insert(mints[2].pubkey, 5_000_000f64); // 1 unit = 5$
prices.insert(mints[3].pubkey, 20_000_000f64); // 1 unit = 20$
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
prices,
..mango_setup::GroupWithTokensConfig::default()
}
.create(solana)
.await;
// fund the vaults to allow borrowing
create_funded_account(
&solana,
group,
owner,
0,
&context.users[1],
mints,
1_000_000,
0,
)
.await;
let account = create_funded_account(
&solana,
group,
owner,
1,
&context.users[1],
&mints[0..2],
1_500, // maint: 0.8 * 1500 = 1200
0,
)
.await;
let hour = 60 * 60;
send_tx(
solana,
GroupEdit {
group,
admin,
options: mango_v4::instruction::GroupEdit {
collateral_fee_interval_opt: Some(6 * hour),
..group_edit_instruction_default()
},
},
)
.await
.unwrap();
// Set fees
set_collateral_fees(solana, admin, mints, group, 0, 0.1).await;
set_collateral_fees(solana, admin, mints, group, 1, 0.2).await;
set_loan_orig_fee(solana, admin, mints, group, 2, 0.0).await;
set_loan_orig_fee(solana, admin, mints, group, 3, 0.0).await;
//
// TEST: With borrows, there's an effect depending on the time that has passed
//
withdraw(&context, solana, owner, account, 50, 2).await; // maint: -1.2 * 50 = -60 (250$ -> 300$)
withdraw(&context, solana, owner, account, 100, 3).await; // maint: -1.2 * 100 = -120 (2000$ -> 2400$)
send_tx(solana, TokenChargeCollateralFeesInstruction { account })
.await
.unwrap();
let mut last_time = solana.clock_timestamp().await;
solana.set_clock_timestamp(last_time + 9 * hour).await;
// send it twice, because the first time will never charge anything
send_tx(solana, TokenChargeCollateralFeesInstruction { account })
.await
.unwrap();
last_time = solana.clock_timestamp().await;
let usage_factor = (60.0 * 5.0 + 120.0 * 20.0) / ((1500.0 + 1500.0 * 3.0) * 0.8);
let time_factor = 9.0 / 24.0;
let collateral_fee_factor = 1500.0 * 0.1 + 1500.0 * 3.0 * 0.2;
let collateral_fee = collateral_fee_factor * time_factor * usage_factor;
// println!("fee -> {}", collateral_fee);
assert_eq_f64!(
account_position_f64(solana, account, tokens[0].bank).await,
1500.0,
0.01
);
assert_eq_f64!(
account_position_f64(solana, account, tokens[1].bank).await,
1500.0,
0.01
);
assert_eq_f64!(
account_position_f64(solana, account, tokens[2].bank).await,
-50.0 - (300.0 / 2700.0) * collateral_fee / 5.0,
0.01
);
assert_eq_f64!(
account_position_f64(solana, account, tokens[3].bank).await,
-100.0 - (2400.0 / 2700.0) * collateral_fee / 20.0,
0.01
);
Ok(())
}
// Test convention
//
// T = Token without collateral fee
// Tc = Token with collateral fee
// B_x = Balance of x
// O_x = Amount in OO for x (market will be x/T1)
// F_x = Collateral Fee charged on x
//
// Asset weight = 0.8
// Liab weight = 1.2
// All amounts in USD
// Base lot is 100
#[tokio::test]
async fn test_basics() -> Result<(), TransportError> {
let test_cases = parse_test_cases("\
B_T1 ; B_T2 ; B_Tc1 ; B_Tc2 ; B_Tc3 ; B_Tc4 ; O_T1 ; O_T2 ; O_Tc1 ; O_Tc2 ; O_Tc3 ; O_Tc4 ; CF_T1 ; CF_T2 ; CF_Tc1 ; CF_Tc2 ; CF_Tc3 ; CF_Tc4 \r\n \
-2000 ; 0 ; 10000 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; -300 ; 0 ; 0 ; 0 ; 0 ; 0 \r\n \
-2000 ; 0 ; 5000 ; 5000 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; -300 ; 0 ; 0 ; 0 ; 0 ; 0 \r\n \
-500 ; -1500 ; 10000 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; 0 ; -75 ; -225 ; 0 ; 0 ; 0 ; 0 \r\n \
");
run_scenario(test_cases).await
}
#[tokio::test]
async fn test_creating_borrow_from_oo() -> Result<(), TransportError> {
let test_cases = parse_test_cases("\
B_T1 ; B_T2 ; B_Tc1 ; B_Tc2 ; B_Tc3 ; B_Tc4 ; O_T1 ; O_T2 ; O_Tc1 ; O_Tc2 ; O_Tc3 ; O_Tc4 ; CF_T1 ; CF_T2 ; CF_Tc1 ; CF_Tc2 ; CF_Tc3 ; CF_Tc4 \r\n \
-2000 ; 0 ; 10000 ; 0 ; 0 ; 0 ; 0 ; 200 ; 0 ; 0 ; 0 ; 0 ; -300 ; 0 ; 0 ; 0 ; 0 ; 0 \r\n \
-2000 ; 0 ; 10000 ; 0 ; 0 ; 0 ; 0 ; 0 ; 300 ; 0 ; 0 ; 0 ; -300 ; 0 ; 0 ; 0 ; 0 ; 0 \r\n \
");
run_scenario(test_cases).await
}
#[tokio::test]
async fn test_hiding_collateral_using_oo() -> Result<(), TransportError> {
let test_cases = parse_test_cases("\
B_T1 ; B_T2 ; B_Tc1 ; B_Tc2 ; B_Tc3 ; B_Tc4 ; O_T1 ; O_T2 ; O_Tc1 ; O_Tc2 ; O_Tc3 ; O_Tc4 ; CF_T1 ; CF_T2 ; CF_Tc1 ; CF_Tc2 ; CF_Tc3 ; CF_Tc4 \r\n \
-2000 ; 0 ; 10000 ; 0 ; 0 ; 0 ; 0 ; -200 ; 0 ; 0 ; 0 ; 0 ; -300 ; 0 ; 0 ; 0 ; 0 ; 0 \r\n \
-2000 ; 0 ; 10000 ; 0 ; 0 ; 0 ; 0 ; 0 ; -300 ; 0 ; 0 ; 0 ; -300 ; 0 ; 0 ; 0 ; 0 ; 0 \r\n \
");
run_scenario(test_cases).await
}
async fn run_scenario(test_cases: Vec<Vec<f64>>) -> Result<(), TransportError> {
for test_case in test_cases {
if test_case.len() == 0 {
continue;
}
let mut test_builder = TestContextBuilder::new();
test_builder.test().set_compute_max_units(200_000);
let context = test_builder.start_default().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..6];
let mut prices = HashMap::new();
// Setup prices
for i in 0..6 {
prices.insert(mints[i].pubkey, (i as f64 + 1.0) * 1_000_000f64); // 1 unit = i$
}
let mango_setup::GroupWithTokens { group, tokens, .. } =
mango_setup::GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
prices,
..mango_setup::GroupWithTokensConfig::default()
}
.create(solana)
.await;
// Setup fees
set_collateral_fees(solana, admin, mints, group, 2, 0.1).await;
set_collateral_fees(solana, admin, mints, group, 3, 0.1).await;
set_collateral_fees(solana, admin, mints, group, 4, 0.1).await;
set_collateral_fees(solana, admin, mints, group, 5, 0.1).await;
for i in 0..6 {
set_loan_orig_fee(solana, admin, mints, group, i, 0.0).await;
}
// fund the vaults to allow borrowing
create_funded_account(
&solana,
group,
owner,
0,
&context.users[1],
mints,
9_000_000_000,
0,
)
.await;
let account = send_tx(
solana,
AccountCreateInstruction {
account_num: 1,
group,
owner,
payer: context.users[1].key,
..Default::default()
},
)
.await
.unwrap()
.account;
// For Spot order
let hour = 60 * 60;
send_tx(
solana,
GroupEdit {
group,
admin,
options: mango_v4::instruction::GroupEdit {
collateral_fee_interval_opt: Some(24 * hour),
..group_edit_instruction_default()
},
},
)
.await
.unwrap();
// Setup balance
for (index, balance) in test_case[0..6].iter().enumerate() {
if *balance > 0.0 {
deposit(
solana,
owner,
&context.users[1],
account,
((*balance as f64) / (index + 1) as f64).ceil() as u64,
index,
)
.await;
}
}
for (index, balance) in test_case[0..6].iter().enumerate() {
if *balance < 0.0 {
withdraw(
&context,
solana,
owner,
account,
((balance.abs() as f64) / (index + 1) as f64).ceil() as u64,
index,
)
.await;
}
}
// Setup orders
for (index, order) in test_case[6..12].iter().enumerate() {
if *order == 0.0 {
continue;
}
create_order(
solana,
&context,
group,
admin,
owner,
&context.users[0],
account,
(index + 1) as f64,
(order / (index + 1) as f64).floor() as i64,
&tokens[index],
&tokens[0],
)
.await;
}
//
// TEST
//
let mut balances = vec![];
for i in 0..6 {
if test_case[i] == 0.0 {
balances.push(0f64);
} else {
balances.push(account_position_f64(solana, account, tokens[i].bank).await);
}
}
send_tx(solana, TokenChargeCollateralFeesInstruction { account })
.await
.unwrap();
let mut last_time = solana.clock_timestamp().await;
solana.set_clock_timestamp(last_time + 24 * hour).await;
// send it twice, because the first time will never charge anything
send_tx(solana, TokenChargeCollateralFeesInstruction { account })
.await
.unwrap();
last_time = solana.clock_timestamp().await;
// Assert balance change
for (index, expected_fee) in test_case[12..].iter().enumerate() {
if test_case[index] == 0.0 {
continue;
}
let current_balance = account_position_f64(solana, account, tokens[index].bank).await;
let previous_balance = balances[index];
let actual_fee = (current_balance - previous_balance) * (index + 1) as f64;
assert_eq_f64!(actual_fee, expected_fee.to_f64().unwrap(), 0.01);
}
}
Ok(())
}
fn parse_test_cases(test_cases: &str) -> Vec<Vec<f64>> {
test_cases
.split("\r\n")
.skip(1)
.map(|x| {
x.split(";")
.filter_map(|y| {
let y = y.trim();
if y.len() == 0 {
return None;
}
Some(f64::from_str(y).unwrap())
})
.collect_vec()
})
.collect_vec()
}
async fn create_order(
solana: &Arc<SolanaCookie>,
context: &TestContext,
group: Pubkey,
admin: TestKeypair,
owner: TestKeypair,
payer: &UserCookie,
account: Pubkey,
price: f64,
quantity: i64,
base_token: &Token,
quote_token: &Token,
) -> Option<(u128, u64)> {
let serum_market_cookie = context
.serum
.list_spot_market(&base_token.mint, &quote_token.mint)
.await;
//
// TEST: Register a serum market
//
let serum_market = send_tx(
solana,
Serum3RegisterMarketInstruction {
group,
admin,
serum_program: context.serum.program_id,
serum_market_external: serum_market_cookie.market,
market_index: 0,
base_bank: base_token.bank,
quote_bank: quote_token.bank,
payer: payer.key,
},
)
.await
.unwrap()
.serum_market;
//
// TEST: Create an open orders account
//
let open_orders = send_tx(
solana,
Serum3CreateOpenOrdersInstruction {
account,
serum_market,
owner,
payer: payer.key,
},
)
.await
.unwrap()
.open_orders;
let mut order_placer = SerumOrderPlacer {
solana: solana.clone(),
serum: context.serum.clone(),
account,
owner: owner.clone(),
serum_market,
open_orders,
next_client_order_id: 0,
};
if quantity > 0 {
order_placer.bid_maker(price, quantity as u64).await
} else {
order_placer.ask(price, quantity.abs() as u64).await
}
}
async fn withdraw(
context: &TestContext,
solana: &Arc<SolanaCookie>,
owner: TestKeypair,
account: Pubkey,
amount: u64,
token_index: usize,
) {
// println!("WITHDRAWING {} - token index {}", amount, token_index);
send_tx(
solana,
TokenWithdrawInstruction {
amount: amount,
allow_borrow: true,
account,
owner,
token_account: context.users[1].token_accounts[token_index],
bank_index: 0,
},
)
.await
.unwrap();
}
async fn deposit(
solana: &Arc<SolanaCookie>,
owner: TestKeypair,
payer: &UserCookie,
account: Pubkey,
amount: u64,
token_index: usize,
) {
// println!("DEPOSITING {} - token index {}", amount, token_index);
send_tx(
solana,
TokenDepositInstruction {
amount: amount,
reduce_only: false,
account,
owner,
token_account: payer.token_accounts[token_index],
token_authority: payer.key,
bank_index: 0,
},
)
.await
.unwrap();
}
async fn set_loan_orig_fee(
solana: &Arc<SolanaCookie>,
admin: TestKeypair,
mints: &[MintCookie],
group: Pubkey,
token_index: usize,
rate: f32,
) {
send_tx(
solana,
TokenEdit {
group,
admin,
mint: mints[token_index].pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
loan_origination_fee_rate_opt: Some(rate),
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
}
async fn set_collateral_fees(
solana: &Arc<SolanaCookie>,
admin: TestKeypair,
mints: &[MintCookie],
group: Pubkey,
token_index: usize,
rate: f32,
) {
send_tx(
solana,
TokenEdit {
group,
admin,
mint: mints[token_index].pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
collateral_fee_per_day_opt: Some(rate),
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
}

View File

@ -335,7 +335,7 @@ async fn test_health_compute_tokens_fallback_oracles() -> Result<(), TransportEr
println!("average success increase: {avg_success_increase}");
println!("average failure increase: {avg_failure_increase}");
assert!(avg_success_increase < 2_050);
assert!(avg_failure_increase < 19_500);
assert!(avg_failure_increase < 19_900);
Ok(())
}

View File

@ -6,14 +6,14 @@ use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side
use mango_v4::serum3_cpi::{load_open_orders_bytes, OpenOrdersSlim};
use std::sync::Arc;
struct SerumOrderPlacer {
solana: Arc<SolanaCookie>,
serum: Arc<SerumCookie>,
account: Pubkey,
owner: TestKeypair,
serum_market: Pubkey,
open_orders: Pubkey,
next_client_order_id: u64,
pub struct SerumOrderPlacer {
pub solana: Arc<SolanaCookie>,
pub serum: Arc<SerumCookie>,
pub account: Pubkey,
pub owner: TestKeypair,
pub serum_market: Pubkey,
pub open_orders: Pubkey,
pub next_client_order_id: u64,
}
impl SerumOrderPlacer {
@ -71,7 +71,7 @@ impl SerumOrderPlacer {
send_tx(&self.solana, ix).await
}
async fn bid_maker(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
pub async fn bid_maker(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
self.try_bid(limit_price, max_base, false).await.unwrap();
self.find_order_id_for_client_order_id(self.next_client_order_id - 1)
.await
@ -108,7 +108,7 @@ impl SerumOrderPlacer {
.await
}
async fn ask(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
pub async fn ask(&mut self, limit_price: f64, max_base: u64) -> Option<(u128, u64)> {
self.try_ask(limit_price, max_base).await.unwrap();
self.find_order_id_for_client_order_id(self.next_client_order_id - 1)
.await

View File

@ -98,6 +98,104 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
Ok(())
}
#[tokio::test]
async fn test_token_update_index_and_rate_resilient_ix() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..2];
//
// SETUP: Create a group and an account to fill the vaults
//
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..GroupWithTokensConfig::default()
}
.create(solana)
.await;
// deposit some funds, to the vaults aren't empty
create_funded_account(&solana, group, owner, 0, &context.users[1], mints, 10000, 0).await;
let withdraw_account = create_funded_account(
&solana,
group,
owner,
1,
&context.users[1],
&mints[1..2],
100000,
0,
)
.await;
send_tx(
solana,
TokenWithdrawInstruction {
amount: 5000,
allow_borrow: true,
account: withdraw_account,
owner,
token_account: context.users[0].token_accounts[0],
bank_index: 0,
},
)
.await
.unwrap();
let bank_before = solana.get_account::<Bank>(tokens[0].bank).await;
let time_before = solana.clock().await.unix_timestamp;
solana.advance_clock().await;
let time_after = solana.clock().await.unix_timestamp;
send_tx(
solana,
TokenUpdateIndexAndRateResilientInstruction {
mint_info: tokens[0].mint_info,
},
)
.await
.unwrap();
let bank_after = solana.get_account::<Bank>(tokens[0].bank).await;
dbg!(bank_after);
dbg!(bank_after);
let utilization = 0.5; // 10000 deposits / 5000 borrows
let diff_ts = (time_after - time_before) as f64;
let year = 31536000.0;
let loan_fee_rate = 0.0005;
let dynamic_rate = 0.07 + 0.9 * (utilization - 0.4) / (0.8 - 0.4);
let interest_change = 5000.0 * (dynamic_rate + loan_fee_rate) * diff_ts / year;
let fee_change = 5000.0 * loan_fee_rate * diff_ts / year;
assert_eq_fixed_f64!(
bank_after.native_borrows() - bank_before.native_borrows(),
interest_change,
0.1
);
assert_eq_fixed_f64!(
bank_after.native_deposits() - bank_before.native_deposits(),
interest_change,
0.1
);
assert_eq_fixed_f64!(
bank_after.collected_fees_native - bank_before.collected_fees_native,
fee_change,
0.1
);
assert_eq_fixed_f64!(bank_after.avg_utilization, utilization, 0.01);
Ok(())
}
#[tokio::test]
async fn test_token_rates_migrate() -> Result<(), TransportError> {
let context = TestContext::new().await;

View File

@ -4464,6 +4464,49 @@ impl ClientInstruction for TokenUpdateIndexAndRateInstruction {
}
}
pub struct TokenUpdateIndexAndRateResilientInstruction {
pub mint_info: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for TokenUpdateIndexAndRateResilientInstruction {
type Accounts = mango_v4::accounts::TokenUpdateIndexAndRate;
type Instruction = mango_v4::instruction::TokenUpdateIndexAndRateResilient;
async fn to_instruction(
&self,
loader: &(impl ClientAccountLoader + 'async_trait),
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let mint_info: MintInfo = loader.load(&self.mint_info).await.unwrap();
let accounts = Self::Accounts {
group: mint_info.group,
mint_info: self.mint_info,
oracle: mint_info.oracle,
instructions: solana_program::sysvar::instructions::id(),
};
let mut instruction = make_instruction(program_id, &accounts, &instruction);
let mut bank_ams = mint_info
.banks()
.iter()
.map(|bank| AccountMeta {
pubkey: *bank,
is_signer: false,
is_writable: true,
})
.collect::<Vec<_>>();
instruction.accounts.append(&mut bank_ams);
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
vec![]
}
}
pub struct ComputeAccountDataInstruction {
pub account: Pubkey,
}

View File

@ -1,16 +1,18 @@
#![allow(dead_code)]
use anchor_lang::prelude::*;
use std::collections::HashMap;
use super::mango_client::*;
use super::solana::SolanaCookie;
use super::{send_tx, MintCookie, TestKeypair, UserCookie};
use super::{MintCookie, TestKeypair, UserCookie};
#[derive(Default)]
pub struct GroupWithTokensConfig {
pub admin: TestKeypair,
pub payer: TestKeypair,
pub mints: Vec<MintCookie>,
pub prices: HashMap<Pubkey, f64>,
pub zero_token_is_quote: bool,
}
@ -38,6 +40,7 @@ impl<'a> GroupWithTokensConfig {
admin,
payer,
mints,
prices,
zero_token_is_quote,
} = self;
let create_group_accounts = send_tx(
@ -74,7 +77,7 @@ impl<'a> GroupWithTokensConfig {
group,
admin,
mint: mint.pubkey,
price: 1.0,
price: prices.get(&mint.pubkey).copied().unwrap_or(1.0f64),
oracle,
},
)

View File

@ -17,9 +17,9 @@ cp -v ./target/types/mango_v4.ts ./ts/client/src/mango_v4.ts
(cd ./ts/client && yarn tsc)
# publish program
solana --url https://mango.devnet.rpcpool.com program deploy --program-id $PROGRAM_ID \
solana --url $CLUSTER_URL program deploy --program-id $PROGRAM_ID \
-k $WALLET_WITH_FUNDS target/deploy/mango_v4.so --skip-fee-check
# publish idl
anchor idl upgrade --provider.cluster https://mango.devnet.rpcpool.com --provider.wallet $WALLET_WITH_FUNDS \
anchor idl upgrade --provider.cluster $CLUSTER_URL --provider.wallet $WALLET_WITH_FUNDS \
--filepath target/idl/mango_v4_no_docs.json $PROGRAM_ID

View File

@ -1,64 +1,91 @@
import { parsePriceData } from '@pythnetwork/client';
import { Connection, PublicKey } from '@solana/web3.js';
import {
Connection,
ParsedAccountData,
PublicKey,
SYSVAR_CLOCK_PUBKEY,
} from '@solana/web3.js';
import {
isPythOracle,
isSwitchboardOracle,
parsePythOracle,
parseSwitchboardOracle,
} from '../../src/accounts/oracle';
import { toNativeI80F48 } from '../../src/utils';
import {
PYTH_SPONSORED_ORACLES,
SB_ON_DEMAND_TESTING_ORACLES,
} from '../governanceInstructions/constants';
const { MB_CLUSTER_URL } = process.env;
async function decodePrice(conn, ai, pk): Promise<void> {
let uiPrice, price, lastUpdatedSlot, type;
async function decodePrice(
slot,
name,
conn: Connection,
ai,
pk,
): Promise<void> {
let uiPrice, price, lastUpdatedSlot, type, uiDeviation, publishedTime;
if (isPythOracle(ai!)) {
const priceData = parsePriceData(ai!.data);
uiPrice = priceData.previousPrice;
price = toNativeI80F48(uiPrice, 6 - 5);
lastUpdatedSlot = parseInt(priceData.lastSlot.toString());
const priceData = parsePythOracle(ai!, conn);
uiPrice = priceData.price;
lastUpdatedSlot = priceData.lastUpdatedSlot;
uiDeviation = priceData.uiDeviation;
publishedTime = (priceData as any).publishedTime;
type = 'pyth';
} else if (isSwitchboardOracle(ai!)) {
const priceData = await parseSwitchboardOracle(pk, ai!, conn);
uiPrice = priceData.price;
price = toNativeI80F48(uiPrice, 6 - 5);
uiDeviation = priceData.uiDeviation;
lastUpdatedSlot = priceData.lastUpdatedSlot;
type = 'sb';
}
// console.log(
// `${name.toString().padStart(10)}, ${type.padStart(4)}, ${uiPrice
// .toString()
// .padStart(10)}, ${(slot - lastUpdatedSlot) / 2}s ${uiDeviation
// .toString()
// .padStart(10)}`,
// );
const currentSlot = await conn.getSlot();
const localUnixTime = Math.floor(Date.now() / 1000);
console.log(`type ${type}`);
console.log(`uiPrice ${uiPrice}`);
console.log(`price ${price}`);
console.log(`currentSlot ${currentSlot}`);
console.log(`lastUpdatedSlot ${lastUpdatedSlot}`);
console.log(`Slot diff ${currentSlot - lastUpdatedSlot}`);
const parsedClock = await conn.getParsedAccountInfo(SYSVAR_CLOCK_PUBKEY);
const parsedClockAccount = (parsedClock.value!.data as ParsedAccountData)
.parsed;
const solanaUnixTime = parsedClockAccount.info.unixTimestamp;
console.log(
`${name}, ${localUnixTime - solanaUnixTime}, ${
localUnixTime - publishedTime
}`,
);
}
async function main(): Promise<void> {
try {
// {
// const oraclePk1 = new PublicKey(
// '4SZ1qb4MtSUrZcoeaeQ3BDzVCyqxw3VwSFpPiMTmn4GE',
// );
// const conn = new Connection(MB_CLUSTER_URL!);
// let ai = await conn.getAccountInfo(oraclePk1);
// decodePrice(conn, ai, oraclePk1);
// const oraclePk2 = new PublicKey(
// '8ihFLu5FimgTQ1Unh4dVyEHUGodJ5gJQCrQf4KUVB9bN',
// );
// ai = await conn.getAccountInfo(oraclePk2);
// decodePrice(conn, ai, oraclePk2);
// }
{
// https://ondemand.switchboard.xyz/solana/devnet/feed/23QLa7R2hDhcXDVKyUSt2rvBPtuAAbY44TrqMVoPpk1C
const oraclePk3 = new PublicKey(
'AZcoqpWhMJUaKEDUfKsfzCr3Y96gSQwv43KSQ6KpeyQ1',
);
const conn = new Connection(MB_CLUSTER_URL!);
const ai = await conn.getAccountInfo(oraclePk3);
decodePrice(conn, ai, oraclePk3);
// eslint-disable-next-line no-constant-condition
if (false) {
// https://ondemand.switchboard.xyz/solana/mainnet/user/DrnFiKkbyC5ga7LJDfDF8FzVcj6aoSUhsgirLjDMrBHH
for (const item of SB_ON_DEMAND_TESTING_ORACLES) {
const oraclePk = new PublicKey(item[1]);
const slot = await conn.getSlot();
const ai = await conn.getAccountInfo(oraclePk);
decodePrice(slot, item[0], conn, ai, oraclePk);
}
}
// eslint-disable-next-line no-constant-condition
if (true) {
// https://docs.pyth.network/price-feeds/sponsored-feeds
for (const item of PYTH_SPONSORED_ORACLES) {
const oraclePk = new PublicKey(item[1]);
const slot = await conn.getSlot();
const ai = await conn.getAccountInfo(oraclePk);
decodePrice(slot, item[0], conn, ai, oraclePk);
}
}
} catch (error) {
console.log(error);

View File

@ -4,6 +4,8 @@ import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { MangoClient } from '../src/client';
import { MANGO_V4_ID } from '../src/constants';
import { ZERO_I80F48 } from '../src/numbers/I80F48';
import { toUiDecimalsForQuote } from '../src/utils';
const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR, MANGO_ACCOUNT, MINT, NATIVE_AMOUNT } =
process.env;
@ -34,21 +36,60 @@ async function buildClient(): Promise<MangoClient> {
async function main(): Promise<void> {
const client = await buildClient();
const mangoAccount = await client.getMangoAccount(
new PublicKey(MANGO_ACCOUNT!),
);
const group = await client.getGroup(new PublicKey(GROUP_PK));
const mintPk = new PublicKey(MINT!);
const mangoAccounts = await client.getAllMangoAccounts(group, true);
for (const bank of Array.from(group.banksMapByName.values()).flat()) {
if (bank.uiDeposits() * bank.uiPrice > 10) continue;
if (bank.nativeDeposits().eq(ZERO_I80F48())) continue;
if (bank.reduceOnly != 1) continue;
console.log(`${bank.name}, ${bank.uiDeposits()}`);
for (const mangoAccount of mangoAccounts) {
if (mangoAccount.getTokenBalance(bank).lt(ZERO_I80F48())) {
console.log(
`${bank.name}, ${toUiDecimalsForQuote(
mangoAccount.getEquity(group),
)} ${mangoAccount.publicKey}, ${mangoAccount.getTokenBalance(
bank,
)}, ${mangoAccount.getTokenBalance(bank).ceil().toNumber()}`,
);
const rs = await client.tokenDepositNative(
group,
mangoAccount,
mintPk,
new BN(NATIVE_AMOUNT!),
bank.mint,
new BN(mangoAccount.getTokenBalance(bank).ceil().toNumber()),
false,
true,
);
console.log(rs.signature);
}
if (mangoAccount.getTokenBalance(bank).gt(ZERO_I80F48())) {
console.log(
`${bank.name}, ${toUiDecimalsForQuote(
mangoAccount.getEquity(group),
)} ${mangoAccount.publicKey}, ${mangoAccount.getTokenBalance(
bank,
)}, ${mangoAccount.getTokenBalance(bank).ceil().toNumber()}, ${
mangoAccount.getToken(bank.tokenIndex)?.inUseCount
}`,
);
const rs = await client.tokenForceWithdraw(
group,
mangoAccount,
bank.tokenIndex,
);
console.log(rs.signature);
}
}
console.log('');
}
process.exit();
}
try {

View File

@ -45,6 +45,17 @@ async function forceWithdrawTokens(): Promise<void> {
).filter((a) => a.getTokenBalanceUi(forceWithdrawBank) > 0);
for (const mangoAccount of mangoAccountsWithDeposits) {
<<<<<<< HEAD
=======
const sig = await client.serum3LiqForceCancelOrders(group,mangoAccount)
const sig = await client.tokenForceWithdraw(
group,
mangoAccount,
TOKEN_INDEX,
);
>>>>>>> main
console.log(
`Withdrawing ${mangoAccount.getTokenBalanceUi(forceWithdrawBank)} for ${
mangoAccount.publicKey

View File

@ -32,3 +32,218 @@ export const MAINNET_PYTH_PROGRAM = new PublicKey(
export const DEVNET_PYTH_PROGRAM = new PublicKey(
'gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s',
);
export const SB_ON_DEMAND_TESTING_ORACLES = [
['DIGITSOL', '2A7aqNLy26ZBSMWP2Ekxv926hj16tCA47W1sHWVqaLii'],
['JLP', '65J9bVEMhNbtbsNgArNV1K4krzcsomjho4bgR51sZXoj'],
['INF', 'AZcoqpWhMJUaKEDUfKsfzCr3Y96gSQwv43KSQ6KpeyQ1'],
['GUAC', 'Ai2GsLRioGKwVgWX8dtbLF5rJJEZX17SteGEDqrpzBv3'],
['RAY', 'AJkAFiXdbMonys8rTXZBrRnuUiLcDFdkyoPuvrVKXhex'],
['JUP', '2F9M59yYc28WMrAymNWceaBEk8ZmDAjUAKULp8seAJF3'],
];
export const SB_ORACLES = [
['RENDER', '94rcvEktGTwCr2uZ6UGq7GPwwkb5BXWox942pGqJPMW3'],
['MNGO', '9AhK6J5bNWBkBEqC9ix5K4bVSgwh9uMEoJvq8Ad2mKZZ'],
['BLZE', 'p8WhggEpj4bTQJpGqPANiqG2CWUxooxWBWzi5qhrdzy'],
['DAI', 'GXRCfroqu9k4ZoS5MyjUSiuoRb1bhps7nacEQLkLBVgr'],
['CHAI', 'GXRCfroqu9k4ZoS5MyjUSiuoRb1bhps7nacEQLkLBVgr'],
];
export const SB_LST_ORACLES = [
['JSOL', '91yrNSV8mofYcP6NCsHNi2YgNxwukBenv5MCRFD92Rgp'],
['HUBSOL', '318uRUE2RuYpvv1VwxC4eJwViDrRrxUTTqoUBV1cgUYi'],
['DUALSOL', '6zBkSKhAqLT2SNRbzTbrom2siKhVZ6SLQcFPnvyexdTE'],
['DIGITSOL', 'Am5rswhcxQhqviDXuaiZnLvkpmB4iJEdxmhqMMZDV3KJ'],
['MANGOSOL', 'FLroEBBA4Fa8ENqfBmqyypq8U6ai2mD7c5k6Vfb2PWzv'],
['COMPASSSOL', '9gFehBozPdWafFfPiZRbub2yUmwYJrGMvguKHii7cMTA'],
];
export const PYTH_SPONSORED_ORACLES = [
['SOL/USD', '7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE'],
['JITOSOL/USD', 'AxaxyeDT8JnWERSaTKvFXvPKkEdxnamKSqpWbsSjYg1g'],
['MSOL/USD', '5CKzb9j4ChgLUt8Gfm5CNGLN6khXKiqMbnGAW4cgXgxK'],
['BSOL/USD', '5cN76Xm2Dtx9MnrQqBDeZZRsWruTTcw37UruznAdSvvE'],
['BONK/USD', 'DBE3N8uNjhKPRHfANdwGvCZghWXyLPdqdSbEW2XFwBiX'],
['W/USD', 'BEMsCSQEGi2kwPA4mKnGjxnreijhMki7L4eeb96ypzF9'],
['KMNO/USD', 'ArjngUHXrQPr1wH9Bqrji9hdDQirM6ijbzc1Jj1fXUk7'],
['MEW/USD', 'EF6U755BdHMXim8RBw6XSC6Yk6XaouTKpwcBZ7QkcanB'],
['TNSR/USD', '9TSGDwcPQX4JpAvZbu2Wp5b68wSYkQvHCvfeBjYcCyC'],
['USDC/USD', 'Dpw1EAVrSB1ibxiDQyTAW6Zip3J4Btk2x4SgApQCeFbX'],
['BTC/USD', '4cSM2e6rvbGQUFiJbqytoVMi5GgghSMr8LwVrT9VPSPo'],
['JTO/USD', '7ajR2zA4MGMMTqRAVjghTKqPPn4kbrj3pYkAVRVwTGzP'],
['USDT/USD', 'HT2PLQBcG5EiCcNSaMHAjSgd9F98ecpATbk4Sk5oYuM'],
['JUP/USD', '7dbob1psH1iZBS7qPsm3Kwbf5DzSXK8Jyg31CTgTnxH5'],
['ETH/USD', '42amVS4KgzR9rA28tkVYqVXjq9Qa8dcZQMbH5EYFX6XC'],
['PYTH/USD', '8vjchtMuJNY4oFQdTi8yCe6mhCaNBFaUbktT482TpLPS'],
['HNT/USD', '4DdmDswskDxXGpwHrXUfn2CNUm9rt21ac79GHNTN3J33'],
['RNDR/USD', 'GbgH1oen3Ne1RY4LwDgh8kEeA1KywHvs5x8zsx6uNV5M'],
['ORCA/USD', '4CBshVeNBEXz24GZpoj8SrqP5L7VGG3qjGd6tCST1pND'],
['SAMO/USD', '2eUVzcYccqXzsDU1iBuatUaDCbRKBjegEaPPeChzfocG'],
['WIF/USD', '6B23K3tkb51vLZA14jcEQVCA1pfHptzEHFA93V5dYwbT'],
['LST/USD', '7aT9A5knp62jVvnEW33xaWopaPHa3Y7ggULyYiUsDhu8'],
['INF/USD', 'Ceg5oePJv1a6RR541qKeQaTepvERA3i8SvyueX9tT8Sq'],
['PRCL/USD', '6a9HN13ZFf57WZd4msn85KWLe5iTayqS8Ee8gstQkxqm'],
['RAY/USD', 'Hhipna3EoWR7u8pDruUg8RxhP5F6XLh6SEHMVDmZhWi8'],
['FIDA/USD', '2cfmeuVBf7bvBJcjKBQgAwfvpUvdZV7K8NZxUEuccrub'],
['MNDE/USD', 'GHKcxocPyzSjy7tWApQjKRkDNuVXd4Kk624zhuaR7xhC'],
['MOBILE/USD', 'DQ4C1tzvu28cwo1roN1Wm6TW35sfJEjLh517k3ZeWevx'],
['IOT/USD', '8UYEn5Weq7toHwgcmctvcAxaNJo3SJxXEayM57rpoXr9'],
['GOFX/USD', '2WS7DByXgzmsGD1QfDyvY2pwAmxjsPDrF2DijwpRBxr7'],
['NEON/USD', 'F2VfCymdNQiCa8Vyg5E7BwEv9UPwfm8cVN6eqQLqXiGo'],
['AUD/USD', '6pPXqXcgFFoLEcXfedWJy3ypNZVJ1F3mgipaDFsvZ1co'],
['GBP/USD', 'G25Tm7UkVruTJ7mcbCxFm45XGWwsH72nJKNGcHEQw1tU'],
['EUR/USD', 'Fu76ChamBDjE8UuGLV6GP2AcPPSU6gjhkNhAyuoPm7ny'],
['XAG/USD', 'H9JxsWwtDZxjSL6m7cdCVsWibj3JBMD9sxqLjadoZnot'],
['XAU/USD', '2uPQGpm8X4ZkxMHxrAW1QuhXcse1AHEgPih6Xp9NuEWW'],
['INJ/USD', 'GwXYEfmPdgHcowF9GZwbb1WiTGTn1fuT3hbSLneoBKK6'],
['SLND/USD', '6vPfd6612huknxXaDapfj6cVmB8NvCwKm3BHKFxzo1EZ'],
['WEN/USD', 'CsG7wXoqZKNxx4UnFtvozfwXQ9RgpKe7zSJa4LWh5MT9'],
];
export const SB_FEEDS_TO_MIGRATE = [
{
name: 'STEP/USD',
pk: '2MW4RK9a7omGDswjLvAmWc75r8zHNdneVwtgqpU1nK3v',
newPk: '5anCm1isKCEyBaiLB4MXL4Q1XDAWgTyfT9i5knQsaZTJ',
pythFeed: 'usd',
},
{
name: 'POPCAT/USD',
pk: '2stQe1XLGkuTZ22gQrgZKsb93iG9mWXSLfANMPRjs5Ky',
newPk: 'G7yd9DdEDjb1ynTTmG2hZhPtenz5DVSzwvwtHf8T6JeW',
pythFeed: 'usd',
},
{
name: 'USDH/USD',
pk: 'B2iwUqbK6ksAsD21SPUUjjx3EwdswpVWFGkeWPHaYd81',
newPk: 'J2KP4GcXaC16fEB2vne1HsKxtNxsiAGS1geMUFuNpLuo',
pythFeed: 'usd',
},
{
name: 'NOS/USD',
pk: 'ED844qf2K6M3JFD9RJCqEVaJ9zP2i9B5Rag5YzVw8Tav',
newPk: 'DmAmYWwGQjHy6JY6EKW9fUNs2Bdaj1WNrjVgKEvuaNvL',
pythFeed: 'usd',
},
{
name: 'GUAC/USD',
pk: '2kbaLTLTovQxkVzmwTXt5ddJKGgmpEAfx9ZNxZMspy8s',
newPk: 'GBTqdMpJ3uJjzdhcCf9JYAwE2fXSSyPdJU1PL41PKZ1k',
pythFeed: 'usd',
},
{
name: 'CORN/USD',
pk: 'BBZWtK26bnwnC6gtyEy2Z5XdrqGJTj4aEevkphuzA5Q8',
newPk: 'CfXTvsF6E7ysLg6HnnmNpnaoYSa59rHuDcupqeKdy5aJ',
pythFeed: 'usd',
},
{
name: 'SLCL/USD',
pk: '5aX5yToaDTkWz6mWKTfC5M9HxwWDSrTopU3UHEVRwp6Y',
newPk: 'BcL5gWHvG5Kmw9okPcAq3ccFm1f3vBUeyvjXwzLLJcd4',
pythFeed: 'usd',
},
{
name: 'JLP/USD',
pk: 'pmHEXBam7kbmCCg5ED5V7RNMN8e34sKu338KeuFAGof',
newPk: 'ASAKdrSoMew3GerohdwFp3bT6HJPUVt3bZgN3JKFvinS',
pythFeed: 'usd',
},
{
name: 'SLERF/USD',
pk: '8LxP1juSh9RPMECQiTocqk8bZcrhhtqgUEk76y4AmE2K',
newPk: 'Cewh5ybWrXDxBJ2s7ZVmQsJRXR3DdKKik9P91ymT4MQe',
pythFeed: 'usd',
},
{
name: 'BOME/USD',
pk: 'JDj6n1iBeJUB54rNsmKw9ty2psAnkcXySLRshBWrYfGD',
newPk: 'DNChSQVXuefoZzeQURJ3JE7r8MsQ2aB8f1TSV75BEGmX',
pythFeed: 'usd',
},
{
name: 'WEN/USD',
pk: 'DzGXTYWCAsQhZbP3KGPeA8G8SA7ynTNntrtDWZ2kPE8n',
newPk: '4ctjNHu5xTrurB4wFCiZs8puC5UmQ4bFfAKVUuUG7E9Z',
pythFeed: 'usd',
},
{
name: 'MEW/USD',
pk: 'BogEXWj8YcrXV31D7FzzUmTCS1hFHfRGZN7rnVN1Vcqe',
newPk: '2GNGnpmku4Aw7ku3Xa3fZyPugcDg1GADSzu2C1pWXB7E',
pythFeed: 'usd',
},
{
name: 'MOTHER/USD',
pk: '273kfU17iwVVgYCRrRR9rFmT2R8FysSPQ2jETuix2gpd',
newPk: 'HcWVxt6fwp2i149GunKohiZCi9jz3tqXyD31drn9USoX',
pythFeed: 'usd',
},
{
name: 'USDY/USD',
pk: '5RKJ9unGQQhHezsNg7wshfJD4c5jJ64iXYu1nk6PJ5fb',
newPk: '234oAERsti3gMYH8DNXxawKm7jGLwqgSsGB5Cz72KeXU',
pythFeed: 'usd',
},
{
name: 'PUPS/USD',
pk: 'ApF6hz2W7FSKMgmmpWxLm6ijA2J5vU2XDBaBLvjbyMbm',
newPk: 'zH9ZpmU6xb6G2NzbujZthvUVdFxwAmbAgRrVX93gUX1',
pythFeed: 'usd',
},
{
name: 'GECKO/USD',
pk: 'ERWF6PnFCVPWeDM9VGCQDC7pASvVrCUwv9Tk3Mh3oXGG',
newPk: 'CseiaHZ8rT2MaD2RFb924huBpkQhd5Gvxd8egmbKBqeK',
pythFeed: 'usd',
},
{
name: 'KMNO/USD',
pk: 'H8oLEoDyvABEDmGmXQuuzvSPWAkr2f2GKytbXiGX9YUm',
newPk: 'ELMSj3w18giUcfU7XHDwxQn8A4At4Ao8aadopP2ZvWpn',
pythFeed: 'usd',
},
{
name: 'INF/USD',
pk: '6AQHz9mpGNjyVafcWdqzzgsJq14Cs8gG6MiQKmdAgCuP',
newPk: '6dM4Wppuz8GtpAqd5xgd1abtXCd1VBfqJAkkhTYW3JpZ',
pythFeed: 'usd',
},
{
name: 'GME/USD',
pk: 'B9BzQ6hBBFn3C6fsGsVwcFd1v5cdbAwi8bUNmL58Bx8z',
newPk: '3zz1k5dcKVSkiFh3DRaTMsZbAckEk1DNiJrWUJKJw2Nr',
pythFeed: 'usd',
},
{
name: 'BILLY/USD',
pk: 'DKt5kYg2wcY3SpbMZrYcJUg23mwEEQ2PsCioyPfcX633',
newPk: 'BvNyTAZp8P1KXXxb8U28Za8XAJGR4CGagexcPoYYr3BE',
pythFeed: 'usd',
},
{
name: 'LNGCAT/USD',
pk: 'H5DimRdrm4xjMMEzg574QKkfaHZcraGLqC85JJ4PBm58',
newPk: '4CgXzP6uCV829KtrvaXY6UuBJz6M4YjHy4YWzo4hanb9',
pythFeed: 'usd',
},
{
name: 'CROWN/USD',
pk: 'RMy7j7BUNxhE4Njgq69KC6ZLzZEpKWoKSp4Y5JQPQLE',
newPk: 'HJQfdAcZGgo9eJXkzPebcARe7Ptxv1G5xjcucZMvNSpt',
pythFeed: 'usd',
},
{
name: 'OPOS/USD',
pk: '3nM4m9FX1ENp3vfbJKMK6mELH7PSPQX5apzonHB9VZeL',
newPk: '59rJDd4xxZFsouZ73sTj3ysnNPCTmunTiThS21NHEazz',
pythFeed: 'usd',
},
{
name: 'KIN/USD',
pk: 'FS4pE37HCGtwjrf4g3G4YfdfRN64nTm1z8iFNHyjZHB5',
newPk: 'HHkJVKgbueG4eoeHf3WCXSuG3MVAqq2MwAaeiZBkTc1g',
pythFeed: 'usd',
},
];

View File

@ -1,3 +1,5 @@
import { SequenceType } from '@blockworks-foundation/mangolana/lib/globalTypes';
import { sendSignAndConfirmTransactions } from '@blockworks-foundation/mangolana/lib/transactions';
import {
getGovernanceProgramVersion,
getInstructionDataFromBase64,
@ -12,18 +14,11 @@ import {
withInsertTransaction,
withSignOffProposal,
} from '@solana/spl-governance';
import {
Connection,
PublicKey,
Transaction,
TransactionInstruction,
} from '@solana/web3.js';
import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { chunk } from 'lodash';
import { createComputeBudgetIx } from '../../src/utils/rpc';
import { updateVoterWeightRecord } from './updateVoteWeightRecord';
import { VsrClient } from './voteStakeRegistryClient';
import { createComputeBudgetIx } from '../../src/utils/rpc';
import { sendSignAndConfirmTransactions } from '@blockworks-foundation/mangolana/lib/transactions';
import { SequenceType } from '@blockworks-foundation/mangolana/lib/globalTypes';
export const MANGO_MINT = 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac';
export const MANGO_REALM_PK = new PublicKey(
@ -142,8 +137,32 @@ export const createProposal = async (
);
}
const txChunks = chunk([...instructions, ...insertInstructions], 2);
let txChunks = chunk([...instructions], 2);
await sendSignAndConfirmTransactions({
connection,
wallet,
transactionInstructions: txChunks.map((txChunk) => ({
instructionsSet: [
{
signers: [],
transactionInstruction: createComputeBudgetIx(80000),
},
...txChunk.map((tx) => ({
signers: [],
transactionInstruction: tx,
})),
],
sequenceType: SequenceType.Sequential,
})),
config: {
maxRetries: 5,
autoRetry: true,
maxTxesInBatch: 20,
logFlowInfo: true,
},
});
txChunks = chunk([...insertInstructions], 1);
await sendSignAndConfirmTransactions({
connection,
wallet,

View File

@ -0,0 +1,44 @@
import { PublicKey } from '@solana/web3.js';
import { MangoClient } from '../src/client';
async function main(): Promise<void> {
const client = await MangoClient.connectDefault(process.env.MB_CLUSTER_URL!);
// eslint-disable-next-line no-constant-condition
while (true) {
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
);
const perpMarket = Array.from(group.perpMarketsMapByName.values()).filter(
(pm) => pm.name == 'SOL-PERP',
)[0];
console.log(`Long funding ${perpMarket.longFunding.toNumber().toLocaleString()}`);
console.log(`Short funding ${perpMarket.shortFunding.toNumber().toLocaleString()}`);
const bids = await perpMarket.loadBids(client);
const asks = await perpMarket.loadAsks(client);
console.log(`FR ${perpMarket.getInstantaneousFundingRateUi(bids, asks)}`);
const mangoAccount = await client.getMangoAccount(
new PublicKey('BLgb4NFwhpurMrGX5LQfb8D8dBpGSGtBqqew2Em8uyRT'),
false,
);
const perpPosition = mangoAccount.getPerpPosition(perpMarket.perpMarketIndex);
console.log(
`Long settled funding ${perpPosition?.longSettledFunding
.toNumber()
.toLocaleString()}`,
);
console.log(
`Short settled funding ${perpPosition?.shortSettledFunding
.toNumber()
.toLocaleString()}`,
);
console.log(`Unsettled funding ui ${perpPosition?.getUnsettledFundingUi(perpMarket)}`);
console.log('');
await new Promise((r) => setTimeout(r, 5 * 1000));
}
}
main();

View File

@ -0,0 +1,43 @@
import { PublicKey } from '@solana/web3.js';
import { SB_ON_DEMAND_PID } from '@switchboard-xyz/on-demand';
import { isSwitchboardOracle } from '../src/accounts/oracle';
import { MangoClient } from '../src/client';
async function main(): Promise<void> {
const client = await MangoClient.connectDefault(process.env.MB_CLUSTER_URL!);
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
);
const allOracles = Array.from(group.banksMapByName.values())
.flat()
.map((b) => [b.name, b.oracle])
.concat(
Array.from(group.banksMapByName.values())
.flat()
.map((b) => [b.name, b.fallbackOracle])
.filter(
(item) =>
item[1] instanceof PublicKey && !item[1].equals(PublicKey.default),
),
);
const oraclePublicKeys = allOracles.map((item) => item[1] as PublicKey);
const ais = await client.program.provider.connection.getMultipleAccountsInfo(
oraclePublicKeys,
);
const result = ais
.map((ai, idx) => {
return [
isSwitchboardOracle(ai!) && !ai?.owner.equals(SB_ON_DEMAND_PID),
allOracles[idx],
];
})
.filter((item) => item[0])
.map((item) => item[1]);
console.log(result);
}
main();

View File

@ -1,7 +1,6 @@
import {
LISTING_PRESETS,
MidPriceImpact,
getMidPriceImpacts,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools';
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
import { BN } from '@project-serum/anchor';
@ -27,11 +26,7 @@ import { MangoAccount } from '../src/accounts/mangoAccount';
import { MangoClient } from '../src/client';
import { NullTokenEditParams } from '../src/clientIxParamBuilder';
import { MANGO_V4_MAIN_GROUP as MANGO_V4_PRIMARY_GROUP } from '../src/constants';
import {
LiqorPriceImpact,
buildGroupGrid,
getEquityForMangoAccounts,
} from '../src/risk';
import { getEquityForMangoAccounts } from '../src/risk';
import {
buildFetch,
toNativeI80F48ForQuote,
@ -58,6 +53,30 @@ const {
DRY_RUN,
} = process.env;
function wrapWithForwarder(
ix: TransactionInstruction,
signer: PublicKey,
timeout: BN,
): TransactionInstruction {
return new TransactionInstruction({
keys: [
{
pubkey: signer,
isSigner: true,
isWritable: false,
},
{
pubkey: ix.programId,
isSigner: false,
isWritable: false,
},
...ix.keys,
],
programId: new PublicKey('ixFPGCPYEp5GzhoahhHFVL8VVzkq1kc2eeFZh3qpYca'),
data: Buffer.concat([timeout.toArrayLike(Buffer, 'le', 8), ix.data]),
});
}
const getApiTokenName = (bankName: string): string => {
if (bankName === 'ETH (Portal)') {
return 'ETH';
@ -142,27 +161,27 @@ async function updateTokenParams(): Promise<void> {
const instructions: TransactionInstruction[] = [];
const allMangoAccounts = await client.getAllMangoAccounts(group, true);
// const allMangoAccounts = await client.getAllMangoAccounts(group, true);
const stepSize = 1;
// const stepSize = 1;
const ttlLiqorEquityUi = await getTotalLiqorEquity(
client,
group,
allMangoAccounts,
);
// const ttlLiqorEquityUi = await getTotalLiqorEquity(
// client,
// group,
// allMangoAccounts,
// );
const midPriceImpacts = getMidPriceImpacts(group.pis);
// const midPriceImpacts = getMidPriceImpacts(group.pis);
const pisForLiqor: LiqorPriceImpact[][] = [];
// eslint-disable-next-line no-constant-condition
if (false) {
const pisForLiqor: LiqorPriceImpact[][] = await buildGroupGrid(
group,
allMangoAccounts,
stepSize,
);
}
// const pisForLiqor: LiqorPriceImpact[][] = [];
// // eslint-disable-next-line no-constant-condition
// if (false) {
// const pisForLiqor: LiqorPriceImpact[][] = await buildGroupGrid(
// group,
// allMangoAccounts,
// stepSize,
// );
// }
// eslint-disable-next-line no-constant-condition
// if (false) {
@ -180,6 +199,8 @@ async function updateTokenParams(): Promise<void> {
// );
// }
console.log(Array.from(group.banksMapByTokenIndex.values()).length);
Array.from(group.banksMapByTokenIndex.values())
.map((banks) => banks[0])
.sort((a, b) => a.name.localeCompare(b.name))
@ -187,6 +208,7 @@ async function updateTokenParams(): Promise<void> {
const builder = Builder(NullTokenEditParams);
let change = false;
// try {
const tier = Object.values(LISTING_PRESETS).find((x) =>
x.initLiabWeight.toFixed(1) === '1.8'
? x.initLiabWeight.toFixed(1) ===
@ -196,27 +218,73 @@ async function updateTokenParams(): Promise<void> {
bank?.initLiabWeight.toNumber().toFixed(1),
);
if (!tier) {
console.log(`Cant estimate tier for ${bank.name}!`);
return;
}
// const maybeSbOracle = SB_FEEDS_TO_MIGRATE.filter(
// (x) => x.name.replace('/USD', '') === bank.name.toLocaleUpperCase(),
// );
// if (maybeSbOracle.length > 0) {
// console.log(` - ${bank.name} ${maybeSbOracle[0].name}`);
// builder.oracle(new PublicKey(maybeSbOracle[0].newPk));
// change = true;
// } else {
// return;
// }
// if (bank.oracleProvider != OracleProvider.Pyth) {
// console.log(`Skipping ${bank.name}, since not pyth`);
// return;
// }
// if (bank.reduceOnly == 1) {
// console.log(`Skipping ${bank.name}, since reduceOnly`);
// return;
// }
// const maybePythV2Feed = PYTH_SPONSORED_ORACLES.filter(
// (x) =>
// x[0].replace('/USD', '') ==
// (bank.name.includes('BTC')
// ? 'BTC'
// : bank.name.includes('ETH')
// ? 'ETH'
// : bank.name.toUpperCase()),
// );
// if (maybePythV2Feed.length > 0) {
// console.log(` - ${bank.name} ${bank.oracle} ${maybePythV2Feed[0][0]}`);
// builder.oracle(new PublicKey(maybePythV2Feed[0][1]));
// change = true;
// } else {
// console.log(`Skipping ${bank.name}, cant find pyth feed`);
// }
// if (
// bank.reduceOnly != 1 &&
// maybePythV2Feed.length == 0 &&
// bank.oracleProvider == OracleProvider.Pyth &&
// !['CHAI', 'DAI', 'BLZE', 'MNGO', 'RENDER'].some(
// (item) => item == bank.name,
// )
// ) {
// throw new Error(`No pyth feed for ${bank.name}`);
// }
// eslint-disable-next-line no-constant-condition
// if (true) {
// if (
// bank.uiBorrows() == 0 &&
// bank.reduceOnly == 2 &&
// bank.initAssetWeight.toNumber() == 0 &&
// bank.maintAssetWeight.toNumber() == 0
// ) {
// builder.disableAssetLiquidation(true);
// builder.oracleConfig({
// confFilter: 1000,
// maxStalenessSlots: -1,
// });
// change = true;
// }
// }
if (true) {
if (
bank.uiBorrows() == 0 &&
bank.reduceOnly == 2 &&
bank.initAssetWeight.toNumber() == 0 &&
bank.maintAssetWeight.toNumber() == 0
) {
builder.disableAssetLiquidation(true);
builder.oracleConfig({
confFilter: 1000,
maxStalenessSlots: -1,
});
change = true;
console.log(
` - ${bank.name}, ${(
bank.uiDeposits() * bank.uiPrice
).toLocaleString()} disabled asset liquidation`,
);
}
}
// // eslint-disable-next-line no-constant-condition
// if (true) {
@ -279,15 +347,13 @@ async function updateTokenParams(): Promise<void> {
// );
// eslint-disable-next-line no-constant-condition
if (!bank.areBorrowsReduceOnly()) {
if (false) {
// Net borrow limits
let netBorrowLimitPerWindowQuote = Math.max(
toUiDecimalsForQuote(tier!.netBorrowLimitPerWindowQuote),
const netBorrowLimitPerWindowQuote = Math.max(
10_000,
Math.min(bank.uiDeposits() * bank.uiPrice, 300_000) / 3 +
Math.max(0, bank.uiDeposits() * bank.uiPrice - 300_000) / 5,
);
netBorrowLimitPerWindowQuote =
Math.round(netBorrowLimitPerWindowQuote / 10_000) * 10_000;
builder.netBorrowLimitPerWindowQuote(
toNativeI80F48ForQuote(netBorrowLimitPerWindowQuote).toNumber(),
);
@ -297,21 +363,17 @@ async function updateTokenParams(): Promise<void> {
toUiDecimalsForQuote(bank.netBorrowLimitPerWindowQuote)
) {
console.log(
`${bank.name}, ${bank.uiDeposits() * bank.uiPrice}$ (${
Math.min(bank.uiDeposits() * bank.uiPrice, 300_000) / 3
}, ${
Math.max(0, bank.uiDeposits() * bank.uiPrice - 300_000) / 5
}), ${
tier?.netBorrowLimitPerWindowQuote
} , new - ${netBorrowLimitPerWindowQuote}, old - ${toUiDecimalsForQuote(
`${
bank.name
} new - ${netBorrowLimitPerWindowQuote.toLocaleString()}, old - ${toUiDecimalsForQuote(
bank.netBorrowLimitPerWindowQuote,
)}`,
).toLocaleString()}`,
);
}
}
// Deposit limits
// eslint-disable-next-line no-constant-condition
// // Deposit limits
// // eslint-disable-next-line no-constant-condition
// if (false) {
// if (bank.maintAssetWeight.toNumber() > 0) {
// {
@ -375,8 +437,14 @@ async function updateTokenParams(): Promise<void> {
// }
const params = builder.build();
if (change) {
// console.log(
// `${bank.name}, ${params.disableAssetLiquidation} ${params.oracleConfig?.maxStalenessSlots} ${params.oracleConfig?.confFilter}`,
// );
// console.log(`${bank.name}, ${bank.oracle} ${params.oracle}`);
}
const ix = await client.program.methods
let ix = await client.program.methods
.tokenEdit(
params.oracle,
params.oracleConfig,
@ -441,6 +509,12 @@ async function updateTokenParams(): Promise<void> {
])
.instruction();
ix = wrapWithForwarder(
ix,
new PublicKey('8SSLjXBEVk9nesbhi9UMCA32uijbVBUqWoKPPQPTekzt'),
new BN(new Date().getTime() / 1000 + 60 * 60 * 24 * 2 * 7), // 2 weeks
);
const tx = new Transaction({ feePayer: wallet.publicKey }).add(ix);
const simulated = await client.connection.simulateTransaction(tx);
@ -452,6 +526,10 @@ async function updateTokenParams(): Promise<void> {
if (change) {
instructions.push(ix);
}
// } catch (error) {
// // console.log(error.stack);
// console.log(`....Skipping ${bank.name}, ${error}`);
// }
});
const tokenOwnerRecordPk = await getTokenOwnerRecordAddress(
@ -480,7 +558,7 @@ async function updateTokenParams(): Promise<void> {
tokenOwnerRecord,
PROPOSAL_TITLE
? PROPOSAL_TITLE
: 'Update net borrow limits for tokens in mango-v4',
: 'Switch remaining switchboard oracles mango-v4',
PROPOSAL_LINK ?? '',
Object.values(proposals).length,
instructions,

View File

@ -1,6 +1,5 @@
import { BorshAccountsCoder } from '@coral-xyz/anchor';
import { Market, Orderbook } from '@project-serum/serum';
import { parsePriceData } from '@pythnetwork/client';
import { TOKEN_PROGRAM_ID, unpackAccount } from '@solana/spl-token';
import {
AccountInfo,
@ -26,6 +25,7 @@ import {
OracleProvider,
isPythOracle,
isSwitchboardOracle,
parsePythOracle,
parseSwitchboardOracle,
} from './oracle';
import { BookSide, PerpMarket, PerpMarketIndex } from './perp';
@ -493,15 +493,14 @@ export class Group {
provider = OracleProvider.Stub;
deviation = stubOracle.deviation;
} else if (isPythOracle(ai)) {
const priceData = parsePriceData(ai.data);
uiPrice = priceData.previousPrice;
const priceData = parsePythOracle(ai, client.program.provider.connection);
uiPrice = priceData.price;
price = this.toNativePrice(uiPrice, baseDecimals);
lastUpdatedSlot = parseInt(priceData.lastSlot.toString());
lastUpdatedSlot = priceData.lastUpdatedSlot;
deviation =
priceData.previousConfidence !== undefined
? this.toNativePrice(priceData.previousConfidence, baseDecimals)
priceData.uiDeviation !== undefined
? this.toNativePrice(priceData.uiDeviation, baseDecimals)
: undefined;
provider = OracleProvider.Pyth;
} else if (isSwitchboardOracle(ai)) {
const priceData = await parseSwitchboardOracle(

View File

@ -1,5 +1,5 @@
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
import { Magic as PythMagic } from '@pythnetwork/client';
import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor';
import { parsePriceData, Magic as PythMagic } from '@pythnetwork/client';
import { AccountInfo, Connection, Keypair, PublicKey } from '@solana/web3.js';
import { SB_ON_DEMAND_PID } from '@switchboard-xyz/on-demand';
import SwitchboardProgram from '@switchboard-xyz/sbv2-lite';
@ -7,7 +7,13 @@ import Big from 'big.js';
import BN from 'bn.js';
import { Program as Anchor30Program } from 'switchboard-anchor';
import {
DEFAULT_RECEIVER_PROGRAM_ID,
PythSolanaReceiverProgram,
} from '@pythnetwork/pyth-solana-receiver';
import { IDL } from '@pythnetwork/pyth-solana-receiver/lib/idl/pyth_solana_receiver';
import { I80F48, I80F48Dto } from '../numbers/I80F48';
import { toUiDecimals } from '../utils';
const SBV1_DEVNET_PID = new PublicKey(
'7azgmy1pFXHikv36q1zZASvFq5vFa39TT9NweVugKKTU',
@ -38,6 +44,7 @@ export const SOL_MINT_MAINNET = new PublicKey(
let sbv2DevnetProgram;
let sbv2MainnetProgram;
let sbOnDemandProgram;
let pythSolanaReceiverProgram;
export enum OracleProvider {
Pyth,
@ -263,6 +270,9 @@ export function isSwitchboardOracle(accountInfo: AccountInfo<Buffer>): boolean {
}
export function isPythOracle(accountInfo: AccountInfo<Buffer>): boolean {
if (accountInfo.owner.equals(DEFAULT_RECEIVER_PROGRAM_ID)) {
return true;
}
return accountInfo.data.readUInt32LE(0) === PythMagic;
}
@ -296,6 +306,60 @@ export function isClmmOracle(accountInfo: AccountInfo<Buffer>): boolean {
return isOrcaOracle(accountInfo) || isRaydiumOracle(accountInfo);
}
export function parsePythOracle(
accountInfo: AccountInfo<Buffer>,
connection: Connection,
): {
price: number;
lastUpdatedSlot: number;
uiDeviation: number;
} {
if (accountInfo.owner.equals(DEFAULT_RECEIVER_PROGRAM_ID)) {
if (!pythSolanaReceiverProgram) {
const options = AnchorProvider.defaultOptions();
const provider = new AnchorProvider(
connection,
new Wallet(new Keypair()),
options,
);
pythSolanaReceiverProgram = new Program<PythSolanaReceiverProgram>(
IDL as PythSolanaReceiverProgram,
DEFAULT_RECEIVER_PROGRAM_ID,
provider,
);
}
const decoded = pythSolanaReceiverProgram.coder.accounts.decode(
'priceUpdateV2',
accountInfo.data,
);
return {
price: toUiDecimals(
decoded.priceMessage.price.toNumber(),
-decoded.priceMessage.exponent,
),
publishedTime: decoded.priceMessage.publishTime.toNumber(),
lastUpdatedSlot: decoded.postedSlot.toNumber(),
uiDeviation: toUiDecimals(
decoded.priceMessage.conf.toNumber(),
-decoded.priceMessage.exponent,
),
} as any;
}
if (accountInfo.data.readUInt32LE(0) === PythMagic) {
const priceData = parsePriceData(accountInfo.data);
return {
price: priceData.previousPrice,
lastUpdatedSlot: parseInt(priceData.lastSlot.toString()),
uiDeviation: priceData.previousConfidence,
};
}
throw new Error('Unknown Pyth oracle!');
}
export function isOracleStaleOrUnconfident(
nowSlot: number,
maxStalenessSlots: number,

View File

@ -1,5 +1,5 @@
export type MangoV4 = {
"version": "0.24.1",
"version": "0.24.2",
"name": "mango_v4",
"instructions": [
{
@ -11166,6 +11166,9 @@ export type MangoV4 = {
},
{
"name": "SwitchboardOnDemand"
},
{
"name": "PythV2"
}
]
}
@ -14485,7 +14488,7 @@ export type MangoV4 = {
};
export const IDL: MangoV4 = {
"version": "0.24.1",
"version": "0.24.2",
"name": "mango_v4",
"instructions": [
{
@ -25652,6 +25655,9 @@ export const IDL: MangoV4 = {
},
{
"name": "SwitchboardOnDemand"
},
{
"name": "PythV2"
}
]
}

View File

@ -8,15 +8,7 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"strictNullChecks": true,
"target": "esnext",
// "paths": {
// "C-v1": [
// "node_modules/C@1.0.0"
// ],
// "C-v2": [
// "node_modules/C@2.0.0"
// ]
// }
"target": "esnext"
},
"ts-node": {
// these options are overrides used only by ts-node
@ -25,7 +17,6 @@
"module": "commonjs"
}
},
"include": [
"ts/client/src"
]
"include": ["ts/client/src"],
"exclude": ["ts/client/scripts"]
}

1074
yarn.lock

File diff suppressed because it is too large Load Diff