Merge branch 'main' into deploy
This commit is contained in:
commit
2b02489aac
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -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.
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.24.1",
|
||||
"version": "0.24.2",
|
||||
"name": "mango_v4",
|
||||
"instructions": [
|
||||
{
|
||||
|
@ -11166,6 +11166,9 @@
|
|||
},
|
||||
{
|
||||
"name": "SwitchboardOnDemand"
|
||||
},
|
||||
{
|
||||
"name": "PythV2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
Binary file not shown.
|
@ -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![]));
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")?
|
||||
};
|
||||
|
||||
|
|
|
@ -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)),
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
|
|
|
@ -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")?;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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, "e_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();
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
|
@ -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();
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
}
|
Loading…
Reference in New Issue