diff --git a/Cargo.lock b/Cargo.lock index 933b7125..3690382b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,7 +150,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.20", + "time 0.3.7", ] [[package]] @@ -272,7 +272,7 @@ version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "clap 3.2.23", @@ -295,6 +295,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "bytemuck", +] + [[package]] name = "bitmaps" version = "2.1.0" @@ -581,7 +590,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim 0.8.0", "textwrap 0.11.0", "unicode-width", @@ -595,9 +604,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_lex", "indexmap", + "once_cell", "strsim 0.10.0", "termcolor", "textwrap 0.16.0", @@ -1013,6 +1023,12 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "eager" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe71d579d1812060163dff96056261deb5bf6729b100fa2e36a68b9649ba3d3" + [[package]] name = "ed25519" version = "1.5.3" @@ -1083,18 +1099,18 @@ dependencies = [ [[package]] name = "enum-iterator" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +checksum = "2953d1df47ac0eb70086ccabf0275aa8da8591a28bd358ee2b52bd9f9e3ff9e9" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +checksum = "8958699f9359f0b04e691a13850d48b7de329138023876d07cbd024c2c820598" dependencies = [ "proc-macro2 1.0.56", "quote 1.0.26", @@ -1377,8 +1393,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1389,9 +1407,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "goblin" -version = "0.4.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32401e89c6446dcd28185931a01b1093726d0356820ac744023e6850689bf926" +checksum = "a7666983ed0dd8d21a6f6576ee00053ca0926fb281a5522577a4dbd0f1b54143" dependencies = [ "log", "plain", @@ -1419,9 +1437,9 @@ dependencies = [ [[package]] name = "hash32" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" dependencies = [ "byteorder", ] @@ -2034,12 +2052,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags", - "cc", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.6.5", @@ -2194,6 +2211,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -2229,13 +2255,15 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf9b1c4e9a6c4de793c632496fa490bdc0e1eea73f0c91394f7b6990935d22" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" dependencies = [ "async-trait", "crossbeam-channel", - "futures", + "futures-channel", + "futures-executor", + "futures-util", "js-sys", "lazy_static", "percent-encoding", @@ -2252,20 +2280,19 @@ checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "ouroboros" -version = "0.14.2" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71643f290d126e18ac2598876d01e1d57aed164afc78fdb6e2a0c6589a1f6662" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" dependencies = [ "aliasable", "ouroboros_macro", - "stable_deref_trait", ] [[package]] name = "ouroboros_macro" -version = "0.14.2" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9a247206016d424fe8497bc611e510887af5c261fbbf977877c4bb55ca4d82" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" dependencies = [ "Inflector", "proc-macro-error", @@ -2333,9 +2360,9 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.6", ] @@ -2506,10 +2533,11 @@ dependencies = [ [[package]] name = "pyth-oracle" -version = "2.26.0" +version = "2.32.0" dependencies = [ "bincode", "bindgen", + "bitflags 2.6.0", "bytemuck", "byteorder", "csv", @@ -2528,7 +2556,10 @@ dependencies = [ "strum", "test-generator", "thiserror", + "time 0.3.7", + "time-macros", "tokio", + "tracing-subscriber", ] [[package]] @@ -2663,7 +2694,6 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", - "rand_pcg", ] [[package]] @@ -2724,15 +2754,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rand_xoshiro" version = "0.6.0" @@ -2772,7 +2793,7 @@ checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem", "ring", - "time 0.3.20", + "time 0.3.7", "yasna", ] @@ -2782,7 +2803,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2791,7 +2812,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2926,7 +2947,7 @@ version = "0.37.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -3020,22 +3041,22 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "scroll" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" dependencies = [ "scroll_derive", ] [[package]] name = "scroll_derive" -version = "0.10.5" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2 1.0.56", "quote 1.0.26", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -3054,7 +3075,7 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -3280,9 +3301,9 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bf996a6f6063f8dc883b27c1b166de10461b3653d3213165174682855b7f1c" +checksum = "fd1d1d16c04e7867408f2e0383a863e272dba2eda2c4b7095c70aa1a7ad4ff36" dependencies = [ "Inflector", "base64 0.13.1", @@ -3293,20 +3314,21 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "solana-address-lookup-table-program", "solana-config-program", "solana-sdk", "solana-vote-program", "spl-token", - "spl-token-2022", + "spl-token-2022 0.6.0", "thiserror", "zstd", ] [[package]] name = "solana-address-lookup-table-program" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d6428d35413a20aaf4342f9874df16b54e548b0c50aab807cc98275eada8cf" +checksum = "de353d486f6e4a20cd163fc0c8391d01ff52e0ce504dbce7a45433362218b6d7" dependencies = [ "bincode", "bytemuck", @@ -3325,9 +3347,9 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22905a42c1668ab479ac02213565f157b8313b23bcc3b3adefef33fc5703e0ab" +checksum = "96c4d210c247714a742fa27629c30c63eefccb3fa7565168649dd58c275cb0cc" dependencies = [ "borsh", "futures", @@ -3342,9 +3364,9 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc46a0ee1a44de50833125c46c1d94a56805a046668e6ea39308e62c27be307" +checksum = "bce2b9e85d389592a02fd061966b548488f2cadc03efca029551365d2d92fa14" dependencies = [ "serde", "solana-sdk", @@ -3353,9 +3375,9 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29591409f677da82d3ff26a39bbce697ea75d0886402f4169a970ad3f057148f" +checksum = "80855bd840b4255224b84641ef89762ec886968af43f617e1a22c20d906ce1c0" dependencies = [ "bincode", "crossbeam-channel", @@ -3373,9 +3395,9 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a303734501f4cad1ea0ef86910bd63d6560473511035941f034e0edf75b4912b" +checksum = "685347b658b1dfb5adf9f42007c4608a4d1954b8b9e36dd1035fd8fcb0918c8b" dependencies = [ "bincode", "byteorder", @@ -3392,9 +3414,9 @@ dependencies = [ [[package]] name = "solana-bucket-map" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cbb57ac4d0aa833140dd244e053ec86db9f0a2357d5482f20cd0c5a85d17be2" +checksum = "ae7219df17935400ead19e838b0a3c583dd7735745c52fca77f7966d39d41100" dependencies = [ "log", "memmap2", @@ -3407,9 +3429,9 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b0c923e89c64ed398964ec2a3ddecacb5a712664d19812ebbf65eefd599145" +checksum = "30c261007a3f9424c7793d9a23e478c615f31ebc24e3ba5e52904575db36b865" dependencies = [ "chrono", "clap 2.34.0", @@ -3425,9 +3447,9 @@ dependencies = [ [[package]] name = "solana-cli-config" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a11154a239590693f0d6b0b4d69193dc98ca21b3e8cc4d1d8d9b326311f520a" +checksum = "86c13afaa04bef4814c6eac7b48c47118d31ecce3dc03a609e0405a42fbddc12" dependencies = [ "dirs-next", "lazy_static", @@ -3441,9 +3463,9 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aba6eeb10249c986b335dd1c684f7353c3e48eeec3259550fcd3b79685013f2" +checksum = "0e803c468b3609af59298721f3a66ad145fa68416bfa420775f190ed9cfc6355" dependencies = [ "async-mutex", "async-trait", @@ -3462,7 +3484,6 @@ dependencies = [ "jsonrpc-core", "lazy_static", "log", - "lru", "quinn", "quinn-proto", "rand 0.7.3", @@ -3485,7 +3506,7 @@ dependencies = [ "solana-transaction-status", "solana-version", "solana-vote-program", - "spl-token-2022", + "spl-token-2022 0.6.0", "thiserror", "tokio", "tokio-stream", @@ -3496,9 +3517,9 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d97b7459e4b3af93401416e301f2cc62a52e8b85db7ec3c12afc82ae2bf32" +checksum = "47c9af546a45bb12d281bc714a688a104d3d9bfe4a0472bd249a5ebacb658f3d" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -3506,9 +3527,9 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20f530aab2e7933539698196e9c2c41521355cdd72ee270e9ec4b3154556dc9e" +checksum = "877de8a7d14e47e948f969277396b759e5361de662fa404980df9fd69d562964" dependencies = [ "bincode", "chrono", @@ -3520,9 +3541,9 @@ dependencies = [ [[package]] name = "solana-faucet" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9aae7a9b16c3e81b6532c3a5d886c58ee0cb518ee6b417f28ea0cfee972ac60" +checksum = "1d202bbd0e7cbea5e291692efbc7b633b4d6d0f2de5aa0e466d6fa7bf40fd98b" dependencies = [ "bincode", "byteorder", @@ -3544,31 +3565,43 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c80650015acc4634f2bbb9a6c1a76c740a6f12854539103f3c13345b1e285b86" +checksum = "f53e63c8f2aac07bc21167e7ede9b9d010ae25523fff5c01b171d9bab9a5a394" dependencies = [ + "ahash", + "blake3", + "block-buffer 0.9.0", "bs58", "bv", + "byteorder", + "cc", + "either", "generic-array", + "getrandom 0.1.16", + "hashbrown 0.12.3", "im", "lazy_static", "log", "memmap2", + "once_cell", + "rand_core 0.6.4", "rustc_version", "serde", "serde_bytes", "serde_derive", + "serde_json", "sha2 0.10.6", "solana-frozen-abi-macro", + "subtle", "thiserror", ] [[package]] name = "solana-frozen-abi-macro" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439f7e3b931ff5ec531164f01c3eb8897c0ae72f7d537294e67b81029fa8e89f" +checksum = "daeaaa2713c06a2fe4bcdcfe7e1af55ee8a89c4d6693860b4041997af667207a" dependencies = [ "proc-macro2 1.0.56", "quote 1.0.26", @@ -3578,9 +3611,9 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27888ef86f0e2ea9b55be9e94f08e785c4d810b89d9d6597c9f590fdf605cd7a" +checksum = "2b502866be84a799633c0744e1d72b819a256337149e9fb6c7eee4db84ec63f5" dependencies = [ "env_logger 0.9.3", "lazy_static", @@ -3589,9 +3622,9 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66772934ed60505434586cf6e1b7133c266a36501c201aca1d26fff3b54e84ef" +checksum = "098178fabb0f0be17ed45ca52aea2754e49d4c41a443be5f98e1bce99b5c12bb" dependencies = [ "log", "solana-sdk", @@ -3599,9 +3632,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef37aa2167fffa33c6c97f9aa804ba155b16cc6ae4aac89bf1a5aca00939bce5" +checksum = "01dcd00c029063c09f15a1e2632570f5a549052cb3647db3bb06c2062e8351c4" dependencies = [ "crossbeam-channel", "gethostname", @@ -3613,12 +3646,12 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3093c7701cc7d3603a32aec204e5a2e4935f71b37eabd7302d5b67bdb4ea0a02" +checksum = "088b41440725aab4858d7e614fe4bb2c930ea60bba494485ed7640ed2b908724" dependencies = [ "bincode", - "clap 2.34.0", + "clap 3.2.23", "crossbeam-channel", "log", "nix", @@ -3635,9 +3668,9 @@ dependencies = [ [[package]] name = "solana-perf" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f5821cb3d6b17319663d5344639a8ea5eba049fcf144b839ecb4673ad2d62" +checksum = "7e9460658e4e92257ead496f8f3e36d8ec6778e09b476b7be6a518121bf0bd74" dependencies = [ "ahash", "bincode", @@ -3662,54 +3695,62 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "792814be1ae8ea58a43ae3cd51578517ec0d9c42c6241c9c0dc71a83d7e0e314" +checksum = "d66c02ad6002fbe7903ec96edd16352fe7964d3ee43b02053112f5304529849f" dependencies = [ "base64 0.13.1", "bincode", - "bitflags", + "bitflags 1.3.2", "blake3", "borsh", "borsh-derive", "bs58", "bv", "bytemuck", + "cc", "console_error_panic_hook", "console_log", "curve25519-dalek", - "getrandom 0.1.16", + "getrandom 0.2.9", "itertools", "js-sys", "lazy_static", + "libc", "libsecp256k1", "log", + "memoffset 0.6.5", "num-derive", "num-traits", "parking_lot 0.12.1", "rand 0.7.3", + "rand_chacha 0.2.2", "rustc_version", "rustversion", "serde", "serde_bytes", "serde_derive", + "serde_json", "sha2 0.10.6", "sha3 0.10.7", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-sdk-macro", "thiserror", + "tiny-bip39", "wasm-bindgen", + "zeroize", ] [[package]] name = "solana-program-runtime" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef724d482da6354e6f2519ae17847ad383ba1e55b525899503ab13e2c97bd740" +checksum = "7d57bc75db0cbfb8e0620cef48b4de3388ed1ea5fbdcc68769da0c2b1ccf69bc" dependencies = [ "base64 0.13.1", "bincode", + "eager", "enum-iterator", "itertools", "libc", @@ -3717,21 +3758,24 @@ dependencies = [ "log", "num-derive", "num-traits", + "rand 0.7.3", "rustc_version", "serde", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-measure", + "solana-metrics", "solana-sdk", "thiserror", ] [[package]] name = "solana-program-test" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05308753a4a6ee579b97ca913c945b5db54784e99aa65fe13934b9c73428635" +checksum = "c0c4b86a72a48ebb66b2182a692b449d8590cab3eb9626a1967cc704aeb488c4" dependencies = [ + "assert_matches", "async-trait", "base64 0.13.1", "bincode", @@ -3752,9 +3796,9 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b5e1e75dbbde7ac578bb8e712de8d0408e299a220b50c3316b08654f5fb628" +checksum = "b2093a3edf87c3753e9548ac00d01a03da479f7fd7859f2d375528d543ecd101" dependencies = [ "lazy_static", "num_cpus", @@ -3762,9 +3806,9 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11989d097d9d47ee433a85714ff8a9ca940847b441dffdc78f3d0e17147da208" +checksum = "1844aed08fd8fc006732cc84fef8f4e0188d52188d7cdc859a5893553063b197" dependencies = [ "console", "dialoguer", @@ -3781,9 +3825,9 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b738f3a58d8720735a349d555afc30be757cc3cb4e2747220e2fff03af524bb" +checksum = "3460b7a188de4b92ce5379e82ff370139cc0174c210df849e4126e701ff6885c" dependencies = [ "arrayref", "bincode", @@ -3802,11 +3846,13 @@ dependencies = [ "itertools", "lazy_static", "log", + "lru", "lz4", "memmap2", "num-derive", "num-traits", "num_cpus", + "once_cell", "ouroboros", "rand 0.7.3", "rayon", @@ -3840,14 +3886,14 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e54361f5faf0696f6f41a8f57e49ec9d99e80c333e2f5466f8501bc67313a0f" +checksum = "60cbad77fa09d23fa5e05029dec6c88e4b784be76cf6ae390f82cc04b8089e73" dependencies = [ "assert_matches", "base64 0.13.1", "bincode", - "bitflags", + "bitflags 1.3.2", "borsh", "bs58", "bytemuck", @@ -3867,7 +3913,7 @@ dependencies = [ "memmap2", "num-derive", "num-traits", - "pbkdf2 0.10.1", + "pbkdf2 0.11.0", "qstring", "rand 0.7.3", "rand_chacha 0.2.2", @@ -3891,9 +3937,9 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9ca4f24a272f09377a0b594e6cfa809247c8f8e6ab188928fa0072b6803543" +checksum = "b73f54502e7d537472bf393ffce0c252c55b534f16797029a1614d79ec0209c9" dependencies = [ "bs58", "proc-macro2 1.0.56", @@ -3904,9 +3950,9 @@ dependencies = [ [[package]] name = "solana-send-transaction-service" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c2d73b9568e50bd371cd3e50f125955db79a6c5e5f743bb90436d524a761fa" +checksum = "7b4db241054803501f57820c467b712556722ff2248e58e22b5728f773bd4fdd" dependencies = [ "crossbeam-channel", "log", @@ -3919,9 +3965,9 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5d713deaa887c0a5d042ad1ffa4e9bfc8bbc190a38b1b3fd24e83b05983c95" +checksum = "780cfa177de42c88a523acd3368cf16852bc1b0c4a9e7f3c893382dd4c9c10cb" dependencies = [ "bincode", "log", @@ -3942,9 +3988,9 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fdc52c3945a4d4fd9012e6c0c3d1a95f00ec51555e09ce6fef4806d7259c9ba" +checksum = "1b7834c7b6281d1e9f6d8207d544da0095b7e562a95b99ecbb7461408cec56eb" dependencies = [ "crossbeam-channel", "futures-util", @@ -3971,9 +4017,9 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9287c2199bd71b577641f00d1e4023e4908f59050a83fb14a1fd19327537d289" +checksum = "feacea79beaefa2aec4ea02bd56573845bcf2a480858f7d666077138928fc259" dependencies = [ "Inflector", "base64 0.13.1", @@ -3986,6 +4032,7 @@ dependencies = [ "serde_derive", "serde_json", "solana-account-decoder", + "solana-address-lookup-table-program", "solana-measure", "solana-metrics", "solana-sdk", @@ -3993,15 +4040,15 @@ dependencies = [ "spl-associated-token-account", "spl-memo", "spl-token", - "spl-token-2022", + "spl-token-2022 0.6.0", "thiserror", ] [[package]] name = "solana-version" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0312c45b6b94e220c5cc9ec1de4fa0fedf21c33ccb17d932ff6191ac237f405" +checksum = "2301b32765f9c37786b1d76b46c0d21be9475ad39d2f0e0494cb9cfe06182149" dependencies = [ "log", "rustc_version", @@ -4015,9 +4062,9 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93db350f670ad166107e4a241a1e9f1deff65fc17402900b443275e2fd8834c" +checksum = "fa94c3c98a33f9825c83ea3d68db39fcbfa94b66772d9a8eb9e16e711966b453" dependencies = [ "bincode", "log", @@ -4036,9 +4083,9 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbb26d981d61ce9b9b75ac5f5b04dcdeb1211cf209ae3a3bb4842c3c84894e3e" +checksum = "c7ebd4f7b2ed789d3c122b40e6590c0fcdb34d1029a6eb7ebb463e96beb5db35" dependencies = [ "bytemuck", "getrandom 0.1.16", @@ -4051,9 +4098,9 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.13.3" +version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8a148ec781994129fea0ede0628131a8f78bc1aaa28fd7d14d72aacdd065c1" +checksum = "b28c5ec36aa1393174f7ea18c0cb809af82c10977bc5b2e1240a6b4b048b8f24" dependencies = [ "aes-gcm-siv", "arrayref", @@ -4064,6 +4111,7 @@ dependencies = [ "cipher 0.4.4", "curve25519-dalek", "getrandom 0.1.16", + "itertools", "lazy_static", "merlin", "num-derive", @@ -4081,9 +4129,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.2.24" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e138f6d6d4eb6a65f8e9f01ca620bc9907d79648d5038a69dd3f07b6ed3f1f" +checksum = "80a28c5dfe7e8af38daa39d6561c8e8b9ed7a2f900951ebe7362ad6348d36c73" dependencies = [ "byteorder", "combine", @@ -4091,11 +4139,10 @@ dependencies = [ "hash32", "libc", "log", - "rand 0.7.3", + "rand 0.8.5", "rustc-demangle", "scroll", "thiserror", - "time 0.1.45", ] [[package]] @@ -4116,9 +4163,9 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a33ecc83137583902c3e13c02f34151c8b2f2b74120f9c2b3ff841953e083d" +checksum = "fbc000f0fdf1f12f99d77d398137c1751345b18c88258ce0f99b7872cf6c9bd6" dependencies = [ "assert_matches", "borsh", @@ -4126,7 +4173,7 @@ dependencies = [ "num-traits", "solana-program", "spl-token", - "spl-token-2022", + "spl-token-2022 0.5.0", "thiserror", ] @@ -4156,9 +4203,9 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a97cbf60b91b610c846ccf8eecca96d92a24a19ffbf9fe06cd0c84e76ec45e" +checksum = "0edb869dbe159b018f17fb9bfa67118c30f232d7f54a73742bc96794dff77ed8" dependencies = [ "arrayref", "bytemuck", @@ -4173,10 +4220,22 @@ dependencies = [ ] [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "spl-token-2022" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "67fcd758e8d22c5fce17315015f5ff319604d1a6e57a73c72795639dba898890" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "solana-zk-token-sdk", + "spl-memo", + "spl-token", + "thiserror", +] [[package]] name = "static_assertions" @@ -4288,9 +4347,9 @@ dependencies = [ [[package]] name = "tarpc" -version = "0.27.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85d0a9369a919ba0db919b142a2b704cd207dfc676f7a43c2d105d0bc225487" +checksum = "1c38a012bed6fb9681d3bf71ffaa4f88f3b4b9ed3198cda6e4c8462d24d4bb80" dependencies = [ "anyhow", "fnv", @@ -4413,30 +4472,21 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ "itoa", - "serde", - "time-core", + "libc", + "num_threads", "time-macros", ] -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" -dependencies = [ - "time-core", -] +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" [[package]] name = "tiny-bip39" @@ -4652,12 +4702,24 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-opentelemetry" -version = "0.15.0" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "599f388ecb26b28d9c1b2e4437ae019a7b336018b45ed911458cd9ebf91129f6" +checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f" dependencies = [ + "once_cell", "opentelemetry", "tracing", "tracing-core", @@ -4666,13 +4728,16 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.25" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +checksum = "5cf865b5ddc38e503a29c41c4843e616a73028ae18c637bc3eb2afaef4909c84" dependencies = [ + "ansi_term", "sharded-slab", + "smallvec", "thread_local", "tracing-core", + "tracing-log", ] [[package]] @@ -5188,7 +5253,7 @@ dependencies = [ "oid-registry", "rusticata-macros", "thiserror", - "time 0.3.20", + "time 0.3.7", ] [[package]] @@ -5215,7 +5280,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.20", + "time 0.3.7", ] [[package]] diff --git a/program/rust/Cargo.toml b/program/rust/Cargo.toml index df753755..bbb1eb35 100644 --- a/program/rust/Cargo.toml +++ b/program/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-oracle" -version = "2.26.0" +version = "2.32.0" edition = "2021" license = "Apache 2.0" publish = false @@ -9,7 +9,7 @@ publish = false bindgen = "0.60.1" [dependencies] -solana-program = "=1.13.3" +solana-program = "=1.14.17" bytemuck = "1.11.0" thiserror = "1.0" num-derive = "0.3" @@ -18,10 +18,12 @@ byteorder = "1.4.3" serde = { version = "1.0", features = ["derive"], optional = true } strum = { version = "0.24.1", features = ["derive"], optional = true } pythnet-sdk = { git = "https://github.com/pyth-network/pyth-crosschain", rev="60144002053a93f424be70decd8a8ccb8d618d81"} +solana-sdk = { version = "=1.14.17", optional = true } +bitflags = { version = "2.6.0", features = ["bytemuck"] } [dev-dependencies] -solana-program-test = "=1.13.3" -solana-sdk = "=1.13.3" +solana-program-test = "=1.14.17" +solana-sdk = "=1.14.17" tokio = "1.14.1" hex = "0.3.1" quickcheck = "1" @@ -34,10 +36,15 @@ serde_json = "1.0" test-generator = "0.3.1" csv = "1.1" +# Downgrade to be compatible with Rust 1.60 +tracing-subscriber = "=0.3.0" +time-macros = "=0.2.3" +time = "=0.3.7" + [features] check = [] # Skips make build in build.rs, use with cargo-clippy and cargo-check debug = [] -library = [] +library = ["solana-sdk"] [lib] crate-type = ["cdylib", "lib"] diff --git a/program/rust/src/accounts.rs b/program/rust/src/accounts.rs index 5958fc88..8bc83ce5 100644 --- a/program/rust/src/accounts.rs +++ b/program/rust/src/accounts.rs @@ -34,7 +34,6 @@ use { std::borrow::BorrowMut, }; - mod mapping; mod permission; mod price; @@ -44,6 +43,8 @@ mod product; #[cfg(feature = "strum")] pub use price::MessageType; #[cfg(test)] +pub use price::PriceCumulative; +#[cfg(test)] pub use product::{ account_has_key_values, create_pc_str_t, @@ -53,14 +54,13 @@ pub use { permission::PermissionAccount, price::{ PriceAccount, + PriceAccountFlags, PriceComponent, - PriceCumulative, PriceEma, PriceInfo, PythOracleSerialize, }, product::{ - read_pc_str_t, update_product_metadata, ProductAccount, }, diff --git a/program/rust/src/accounts/price.rs b/program/rust/src/accounts/price.rs index 82f2d62c..6958970f 100644 --- a/program/rust/src/accounts/price.rs +++ b/program/rust/src/accounts/price.rs @@ -25,14 +25,12 @@ mod price_pythnet { use { super::*, - crate::{ - c_oracle_header::{ - PC_MAX_SEND_LATENCY, - PC_NUM_COMP_PYTHNET, - PC_STATUS_TRADING, - }, - error::OracleError, + crate::c_oracle_header::{ + PC_MAX_SEND_LATENCY, + PC_NUM_COMP_PYTHNET, + PC_STATUS_TRADING, }, + bitflags::bitflags, }; /// Pythnet-only extended price account format. This extension is @@ -65,8 +63,9 @@ mod price_pythnet { pub message_sent_: u8, /// Configurable max latency in slots between send and receive pub max_latency_: u8, + /// Various flags + pub flags: PriceAccountFlags, /// Unused placeholder for alignment - pub unused_2_: i8, pub unused_3_: i32, /// Corresponding product account pub product_account: Pubkey, @@ -91,6 +90,18 @@ mod price_pythnet { pub price_cumulative: PriceCumulative, } + bitflags! { + #[repr(C)] + #[derive(Copy, Clone, Pod, Zeroable)] + pub struct PriceAccountFlags: u8 { + /// If set, the program doesn't do accumulation, but validator does. + const ACCUMULATOR_V2 = 0b1; + /// If unset, the program will remove old messages from its message buffer account + /// and set this flag. + const MESSAGE_BUFFER_CLEARED = 0b10; + } + } + impl PriceAccountPythnet { pub fn as_price_feed_message(&self, key: &Pubkey) -> PriceFeedMessage { let (price, conf, publish_time) = if self.agg_.status_ == PC_STATUS_TRADING { @@ -111,7 +122,7 @@ mod price_pythnet { } } /// This function gets triggered when there's a succesful aggregation and updates the cumulative sums - pub fn update_price_cumulative(&mut self) -> Result<(), OracleError> { + pub fn update_price_cumulative(&mut self) { if self.agg_.status_ == PC_STATUS_TRADING { self.price_cumulative.update( self.agg_.price_, @@ -119,9 +130,6 @@ mod price_pythnet { self.agg_.pub_slot_.saturating_sub(self.prev_slot_), self.max_latency_, ); // pub_slot should always be >= prev_slot, but we protect ourselves against underflow just in case - Ok(()) - } else { - Err(OracleError::NeedsSuccesfulAggregation) } } diff --git a/program/rust/src/lib.rs b/program/rust/src/lib.rs index b0e3b877..eb1881ef 100644 --- a/program/rust/src/lib.rs +++ b/program/rust/src/lib.rs @@ -10,6 +10,12 @@ mod instruction; mod processor; mod utils; +#[cfg(any(test, feature = "library"))] +pub mod validator; + +#[cfg(feature = "library")] +pub use solana_program; + #[cfg(test)] mod tests; @@ -31,11 +37,13 @@ pub use accounts::{ MappingAccount, PermissionAccount, PriceAccount, + PriceAccountFlags, PriceComponent, PriceEma, PriceInfo, ProductAccount, PythAccount, + PythOracleSerialize, }; use { crate::error::OracleError, diff --git a/program/rust/src/processor.rs b/program/rust/src/processor.rs index b8902165..b4104fc1 100644 --- a/program/rust/src/processor.rs +++ b/program/rust/src/processor.rs @@ -27,6 +27,11 @@ mod upd_permissions; mod upd_price; mod upd_product; +#[cfg(test)] +pub use add_publisher::{ + DISABLE_ACCUMULATOR_V2, + ENABLE_ACCUMULATOR_V2, +}; pub use { add_price::add_price, add_product::add_product, diff --git a/program/rust/src/processor/add_publisher.rs b/program/rust/src/processor/add_publisher.rs index 841de3b9..13c29ea9 100644 --- a/program/rust/src/processor/add_publisher.rs +++ b/program/rust/src/processor/add_publisher.rs @@ -2,6 +2,7 @@ use { crate::{ accounts::{ PriceAccount, + PriceAccountFlags, PriceComponent, PythAccount, }, @@ -33,6 +34,13 @@ use { std::mem::size_of, }; +pub const ENABLE_ACCUMULATOR_V2: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +]; +pub const DISABLE_ACCUMULATOR_V2: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, +]; + /// Add publisher to symbol account // account[0] funding account [signer writable] // account[1] price account [signer writable] @@ -62,14 +70,19 @@ pub fn add_publisher( &cmd_args.header, )?; - let mut price_data = load_checked::(price_account, cmd_args.header.version)?; - // Use the call with the default pubkey (000..) as a trigger to sort the publishers as a - // migration step from unsorted list to sorted list. - if cmd_args.publisher == Pubkey::default() { - let num_comps = try_convert::(price_data.num_)?; - sort_price_comps(&mut price_data.comp_, num_comps)?; + if cmd_args.publisher == Pubkey::from(ENABLE_ACCUMULATOR_V2) { + // Hack: we use add_publisher instruction to configure the `ACCUMULATOR_V2` flag. Using a new + // instruction would be cleaner but it would require more work in the tooling. + // These special cases can be removed along with the v1 aggregation code once the transition + // is complete. + price_data.flags.insert(PriceAccountFlags::ACCUMULATOR_V2); + return Ok(()); + } else if cmd_args.publisher == Pubkey::from(DISABLE_ACCUMULATOR_V2) { + price_data + .flags + .remove(PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED); return Ok(()); } @@ -224,7 +237,6 @@ mod test { let mut rust_std_sorted_comps = comps.get(..num_comps).unwrap().to_vec(); rust_std_sorted_comps.sort_by_key(|x| x.pub_); - assert_eq!(sort_price_comps(&mut comps, num_comps), Ok(())); assert_eq!(comps.get(..num_comps).unwrap(), rust_std_sorted_comps); } diff --git a/program/rust/src/processor/upd_price.rs b/program/rust/src/processor/upd_price.rs index 278673b4..b521277c 100644 --- a/program/rust/src/processor/upd_price.rs +++ b/program/rust/src/processor/upd_price.rs @@ -2,6 +2,7 @@ use { crate::{ accounts::{ PriceAccount, + PriceAccountFlags, PriceComponent, PriceInfo, PythOracleSerialize, @@ -131,6 +132,7 @@ pub fn upd_price( let publisher_index: usize; let latest_aggregate_price: PriceInfo; + let flags: PriceAccountFlags; // The price_data borrow happens in a scope because it must be // dropped before we borrow again as raw data pointer for the C @@ -159,39 +161,43 @@ pub fn upd_price( && cmd_args.publishing_slot <= clock.slot), ProgramError::InvalidArgument, )?; - } - // Try to update the aggregate - #[allow(unused_variables)] - if clock.slot > latest_aggregate_price.pub_slot_ { - let updated = unsafe { - // NOTE: c_upd_aggregate must use a raw pointer to price - // data. Solana's `.borrow_*` methods require exclusive - // access, i.e. no other borrow can exist for the account. - c_upd_aggregate( - price_account.try_borrow_mut_data()?.as_mut_ptr(), - clock.slot, - clock.unix_timestamp, - ) - }; + flags = price_data.flags; + } - // If the aggregate was successfully updated, calculate the difference and update TWAP. - if updated { - let agg_diff = (clock.slot as i64) - - load_checked::(price_account, cmd_args.header.version)?.prev_slot_ - as i64; - // Encapsulate TWAP update logic in a function to minimize unsafe block scope. - unsafe { - c_upd_twap(price_account.try_borrow_mut_data()?.as_mut_ptr(), agg_diff); + if !flags.contains(PriceAccountFlags::ACCUMULATOR_V2) { + // Try to update the aggregate + #[allow(unused_variables)] + if clock.slot > latest_aggregate_price.pub_slot_ { + let updated = unsafe { + // NOTE: c_upd_aggregate must use a raw pointer to price + // data. Solana's `.borrow_*` methods require exclusive + // access, i.e. no other borrow can exist for the account. + c_upd_aggregate( + price_account.try_borrow_mut_data()?.as_mut_ptr(), + clock.slot, + clock.unix_timestamp, + ) + }; + + // If the aggregate was successfully updated, calculate the difference and update TWAP. + if updated { + let agg_diff = (clock.slot as i64) + - load_checked::(price_account, cmd_args.header.version)? + .prev_slot_ as i64; + // Encapsulate TWAP update logic in a function to minimize unsafe block scope. + unsafe { + c_upd_twap(price_account.try_borrow_mut_data()?.as_mut_ptr(), agg_diff); + } + let mut price_data = + load_checked::(price_account, cmd_args.header.version)?; + // We want to send a message every time the aggregate price updates. However, during the migration, + // not every publisher will necessarily provide the accumulator accounts. The message_sent_ flag + // ensures that after every aggregate update, the next publisher who provides the accumulator accounts + // will send the message. + price_data.message_sent_ = 0; + price_data.update_price_cumulative(); } - let mut price_data = - load_checked::(price_account, cmd_args.header.version)?; - // We want to send a message every time the aggregate price updates. However, during the migration, - // not every publisher will necessarily provide the accumulator accounts. The message_sent_ flag - // ensures that after every aggregate update, the next publisher who provides the accumulator accounts - // will send the message. - price_data.message_sent_ = 0; - price_data.update_price_cumulative()?; } } @@ -199,64 +205,77 @@ pub fn upd_price( let mut price_data = load_checked::(price_account, cmd_args.header.version)?; // Feature-gated accumulator-specific code, used only on pythnet/pythtest - { + let need_message_buffer_update = if flags.contains(PriceAccountFlags::ACCUMULATOR_V2) { + // We need to clear old messages. + !flags.contains(PriceAccountFlags::MESSAGE_BUFFER_CLEARED) + } else { + // V1 + price_data.message_sent_ == 0 + }; + + if need_message_buffer_update { if let Some(accumulator_accounts) = maybe_accumulator_accounts { - if price_data.message_sent_ == 0 { - // Check that the oracle PDA is correctly configured for the program we are calling. - let oracle_auth_seeds: &[&[u8]] = &[ - UPD_PRICE_WRITE_SEED.as_bytes(), - &accumulator_accounts.program_id.key.to_bytes(), - ]; - let (expected_oracle_auth_pda, bump) = - Pubkey::find_program_address(oracle_auth_seeds, program_id); - pyth_assert( - expected_oracle_auth_pda == *accumulator_accounts.oracle_auth_pda.key, - OracleError::InvalidPda.into(), - )?; - - let account_metas = vec![ - AccountMeta { - pubkey: *accumulator_accounts.whitelist.key, - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: *accumulator_accounts.oracle_auth_pda.key, - is_signer: true, - is_writable: false, - }, - AccountMeta { - pubkey: *accumulator_accounts.message_buffer_data.key, - is_signer: false, - is_writable: true, - }, - ]; - - let message = vec![ + // Check that the oracle PDA is correctly configured for the program we are calling. + let oracle_auth_seeds: &[&[u8]] = &[ + UPD_PRICE_WRITE_SEED.as_bytes(), + &accumulator_accounts.program_id.key.to_bytes(), + ]; + let (expected_oracle_auth_pda, bump) = + Pubkey::find_program_address(oracle_auth_seeds, program_id); + pyth_assert( + expected_oracle_auth_pda == *accumulator_accounts.oracle_auth_pda.key, + OracleError::InvalidPda.into(), + )?; + + let account_metas = vec![ + AccountMeta { + pubkey: *accumulator_accounts.whitelist.key, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: *accumulator_accounts.oracle_auth_pda.key, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: *accumulator_accounts.message_buffer_data.key, + is_signer: false, + is_writable: true, + }, + ]; + + let message = if flags.contains(PriceAccountFlags::ACCUMULATOR_V2) { + vec![] + } else { + vec![ price_data .as_price_feed_message(price_account.key) .to_bytes(), price_data.as_twap_message(price_account.key).to_bytes(), - ]; - - // Append a TWAP message if available - - // anchor discriminator for "global:put_all" - let discriminator: [u8; 8] = [212, 225, 193, 91, 151, 238, 20, 93]; - let create_inputs_ix = Instruction::new_with_borsh( - *accumulator_accounts.program_id.key, - &(discriminator, price_account.key.to_bytes(), message), - account_metas, - ); - - let auth_seeds_with_bump: &[&[u8]] = &[ - UPD_PRICE_WRITE_SEED.as_bytes(), - &accumulator_accounts.program_id.key.to_bytes(), - &[bump], - ]; - - invoke_signed(&create_inputs_ix, accounts, &[auth_seeds_with_bump])?; - price_data.message_sent_ = 1; + ] + }; + + // anchor discriminator for "global:put_all" + let discriminator: [u8; 8] = [212, 225, 193, 91, 151, 238, 20, 93]; + let create_inputs_ix = Instruction::new_with_borsh( + *accumulator_accounts.program_id.key, + &(discriminator, price_account.key.to_bytes(), message), + account_metas, + ); + + let auth_seeds_with_bump: &[&[u8]] = &[ + UPD_PRICE_WRITE_SEED.as_bytes(), + &accumulator_accounts.program_id.key.to_bytes(), + &[bump], + ]; + + invoke_signed(&create_inputs_ix, accounts, &[auth_seeds_with_bump])?; + price_data.message_sent_ = 1; + if flags.contains(PriceAccountFlags::ACCUMULATOR_V2) { + price_data + .flags + .insert(PriceAccountFlags::MESSAGE_BUFFER_CLEARED); } } } @@ -287,7 +306,7 @@ pub fn upd_price( /// to get the result faster if the list is sorted. If the list is not sorted, it falls back to /// a linear search. #[inline(always)] -fn find_publisher_index(comps: &[PriceComponent], key: &Pubkey) -> Option { +pub fn find_publisher_index(comps: &[PriceComponent], key: &Pubkey) -> Option { // Verify that publisher is authorized by initially binary searching // for the publisher's component in the price account. The binary // search might not work if the publisher list is not sorted; therefore diff --git a/program/rust/src/tests/mod.rs b/program/rust/src/tests/mod.rs index 8208d153..33178ccd 100644 --- a/program/rust/src/tests/mod.rs +++ b/program/rust/src/tests/mod.rs @@ -2,6 +2,7 @@ mod pyth_simulator; mod test_add_price; mod test_add_product; mod test_add_publisher; +mod test_aggregate_v2; mod test_aggregation; mod test_c_code; mod test_check_valid_signable_account_or_permissioned_funding_account; @@ -23,6 +24,7 @@ mod test_upd_aggregate; mod test_upd_permissions; mod test_upd_price; mod test_upd_price_no_fail_on_error; +mod test_upd_price_with_validator; mod test_upd_product; mod test_utils; diff --git a/program/rust/src/tests/test_add_publisher.rs b/program/rust/src/tests/test_add_publisher.rs index 41dcfe67..57f2a346 100644 --- a/program/rust/src/tests/test_add_publisher.rs +++ b/program/rust/src/tests/test_add_publisher.rs @@ -157,37 +157,4 @@ fn test_add_publisher() { assert!(price_data.comp_[i as usize].pub_ > price_data.comp_[(i - 1) as usize].pub_); } } - - // Test sorting by reordering the publishers to be in reverse order - // and then adding the default (000...) publisher to trigger the sorting. - { - let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - price_data - .comp_ - .get_mut(..PC_NUM_COMP as usize) - .unwrap() - .reverse(); - } - - cmd.publisher = Pubkey::default(); - instruction_data = bytes_of::(&cmd); - assert!(process_instruction( - &program_id, - &[ - funding_account.clone(), - price_account.clone(), - permissions_account.clone(), - ], - instruction_data - ) - .is_ok()); - - // Make sure that publishers get sorted after adding the default publisher - { - let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); - assert!(price_data.num_ == PC_NUM_COMP); - for i in 1..PC_NUM_COMP { - assert!(price_data.comp_[i as usize].pub_ > price_data.comp_[(i - 1) as usize].pub_); - } - } } diff --git a/program/rust/src/tests/test_aggregate_v2.rs b/program/rust/src/tests/test_aggregate_v2.rs new file mode 100644 index 00000000..1a8c8e11 --- /dev/null +++ b/program/rust/src/tests/test_aggregate_v2.rs @@ -0,0 +1,174 @@ +use { + crate::{ + accounts::{ + PermissionAccount, + PriceAccount, + PriceAccountFlags, + PythAccount, + }, + c_oracle_header::{ + PC_STATUS_TRADING, + PC_VERSION, + }, + deserialize::{ + load_checked, + load_mut, + }, + instruction::{ + AddPublisherArgs, + OracleCommand, + UpdPriceArgs, + }, + processor::{ + process_instruction, + DISABLE_ACCUMULATOR_V2, + ENABLE_ACCUMULATOR_V2, + }, + tests::test_utils::{ + update_clock_slot, + AccountSetup, + }, + }, + bytemuck::bytes_of, + solana_program::pubkey::Pubkey, + std::mem::size_of, +}; + +struct Accounts { + program_id: Pubkey, + publisher_account: AccountSetup, + funding_account: AccountSetup, + price_account: AccountSetup, + permissions_account: AccountSetup, + clock_account: AccountSetup, +} + +impl Accounts { + fn new() -> Self { + let program_id = Pubkey::new_unique(); + let publisher_account = AccountSetup::new_funding(); + let clock_account = AccountSetup::new_clock(); + let mut funding_account = AccountSetup::new_funding(); + let mut permissions_account = AccountSetup::new_permission(&program_id); + let mut price_account = AccountSetup::new::(&program_id); + + PriceAccount::initialize(&price_account.as_account_info(), PC_VERSION).unwrap(); + + { + let permissions_account_info = permissions_account.as_account_info(); + let mut permissions_account_data = + PermissionAccount::initialize(&permissions_account_info, PC_VERSION).unwrap(); + permissions_account_data.master_authority = *funding_account.as_account_info().key; + permissions_account_data.data_curation_authority = + *funding_account.as_account_info().key; + permissions_account_data.security_authority = *funding_account.as_account_info().key; + } + + Self { + program_id, + publisher_account, + funding_account, + price_account, + permissions_account, + clock_account, + } + } +} + +fn add_publisher(accounts: &mut Accounts, publisher: Option) { + let args = AddPublisherArgs { + header: OracleCommand::AddPublisher.into(), + publisher: publisher.unwrap_or(*accounts.publisher_account.as_account_info().key), + }; + + assert!(process_instruction( + &accounts.program_id, + &[ + accounts.funding_account.as_account_info(), + accounts.price_account.as_account_info(), + accounts.permissions_account.as_account_info(), + ], + bytes_of::(&args) + ) + .is_ok()); +} + +fn update_price(accounts: &mut Accounts, price: i64, conf: u64, slot: u64) { + let instruction_data = &mut [0u8; size_of::()]; + let mut cmd = load_mut::(instruction_data).unwrap(); + cmd.header = OracleCommand::UpdPrice.into(); + cmd.status = PC_STATUS_TRADING; + cmd.price = price; + cmd.confidence = conf; + cmd.publishing_slot = slot; + cmd.unused_ = 0; + + let mut clock = accounts.clock_account.as_account_info(); + clock.is_signer = false; + clock.is_writable = false; + + process_instruction( + &accounts.program_id, + &[ + accounts.publisher_account.as_account_info(), + accounts.price_account.as_account_info(), + clock, + ], + instruction_data, + ) + .unwrap(); +} + +#[test] +fn test_aggregate_v2_toggle() { + let accounts = &mut Accounts::new(); + + // Add an initial Publisher to test with. + add_publisher(accounts, None); + + // Update the price, no aggregation will happen on the first slot. + { + update_clock_slot(&mut accounts.clock_account.as_account_info(), 1); + update_price(accounts, 42, 2, 1); + let info = accounts.price_account.as_account_info(); + let price_data = load_checked::(&info, PC_VERSION).unwrap(); + assert_eq!(price_data.last_slot_, 0); + assert!(!price_data.flags.contains(PriceAccountFlags::ACCUMULATOR_V2)); + } + + // Update again, component is now TRADING so aggregation should trigger. + { + update_clock_slot(&mut accounts.clock_account.as_account_info(), 2); + update_price(accounts, 42, 2, 2); + let info = accounts.price_account.as_account_info(); + let price_data = load_checked::(&info, PC_VERSION).unwrap(); + assert_eq!(price_data.last_slot_, 2); + assert!(!price_data.flags.contains(PriceAccountFlags::ACCUMULATOR_V2)); + } + + // Enable v2 Aggregation + add_publisher(accounts, Some(ENABLE_ACCUMULATOR_V2.into())); + + // Update again, with accumulator bit set, aggregation should not have + // happened, as its now the validators job. + { + update_clock_slot(&mut accounts.clock_account.as_account_info(), 3); + update_price(accounts, 42, 2, 3); + let info = accounts.price_account.as_account_info(); + let price_data = load_checked::(&info, PC_VERSION).unwrap(); + assert_eq!(price_data.last_slot_, 2); + assert!(price_data.flags.contains(PriceAccountFlags::ACCUMULATOR_V2)); + } + + add_publisher(accounts, Some(DISABLE_ACCUMULATOR_V2.into())); + + // Confirm disabling v2 Aggregation re-enables the aggregation flow. + { + update_clock_slot(&mut accounts.clock_account.as_account_info(), 4); + update_price(accounts, 42, 2, 4); + let info = accounts.price_account.as_account_info(); + let price_data = load_checked::(&info, PC_VERSION).unwrap(); + assert_eq!(price_data.last_slot_, 4); + assert!(!price_data.flags.contains(PriceAccountFlags::ACCUMULATOR_V2)); + } +} diff --git a/program/rust/src/tests/test_sizes.rs b/program/rust/src/tests/test_sizes.rs index 9ec1e37b..7a1d900d 100644 --- a/program/rust/src/tests/test_sizes.rs +++ b/program/rust/src/tests/test_sizes.rs @@ -112,7 +112,7 @@ fn test_offsets() { #[test] fn test_pubkey() { let default_pubkey = Pubkey::default(); - let zero_pubkey = Pubkey::new(&[0u8; 32]); + let zero_pubkey = Pubkey::from([0u8; 32]); let unique_pubkey = Pubkey::new_unique(); assert_eq!(default_pubkey, zero_pubkey); diff --git a/program/rust/src/tests/test_twap.rs b/program/rust/src/tests/test_twap.rs index ff3b7379..db554234 100644 --- a/program/rust/src/tests/test_twap.rs +++ b/program/rust/src/tests/test_twap.rs @@ -12,7 +12,6 @@ use { PC_STATUS_UNKNOWN, }, deserialize::load_account_as_mut, - error::OracleError, }, quickcheck::Arbitrary, quickcheck_macros::quickcheck, @@ -262,7 +261,7 @@ fn test_twap_with_price_account() { unused: 0, }; price_data.prev_slot_ = 3; - price_data.update_price_cumulative().unwrap(); + price_data.update_price_cumulative(); assert_eq!(price_data.price_cumulative.price, 1 - 2 * 10); assert_eq!(price_data.price_cumulative.conf, 2 + 2 * 5); @@ -291,10 +290,6 @@ fn test_twap_with_price_account() { pub_slot_: 6, }; price_data.prev_slot_ = 5; - assert_eq!( - price_data.update_price_cumulative(), - Err(OracleError::NeedsSuccesfulAggregation) - ); assert_eq!(price_data.price_cumulative.price, 1 - 2 * 10); assert_eq!(price_data.price_cumulative.conf, 2 + 2 * 5); @@ -302,7 +297,7 @@ fn test_twap_with_price_account() { // Back to normal behavior price_data.agg_.status_ = PC_STATUS_TRADING; - price_data.update_price_cumulative().unwrap(); + price_data.update_price_cumulative(); assert_eq!(price_data.price_cumulative.price, 1 - 2 * 10 + 1); assert_eq!(price_data.price_cumulative.conf, 2 + 2 * 5 + 2); diff --git a/program/rust/src/tests/test_upd_price_with_validator.rs b/program/rust/src/tests/test_upd_price_with_validator.rs new file mode 100644 index 00000000..65dc2465 --- /dev/null +++ b/program/rust/src/tests/test_upd_price_with_validator.rs @@ -0,0 +1,439 @@ +use { + crate::{ + accounts::{ + PriceAccount, + PriceAccountFlags, + PythAccount, + PythOracleSerialize, + }, + c_oracle_header::{ + PC_STATUS_IGNORED, + PC_STATUS_TRADING, + PC_STATUS_UNKNOWN, + PC_VERSION, + }, + deserialize::{ + load_checked, + load_mut, + }, + instruction::{ + OracleCommand, + UpdPriceArgs, + }, + processor::process_instruction, + tests::test_utils::{ + update_clock_slot, + AccountSetup, + }, + validator, + }, + pythnet_sdk::messages::{ + PriceFeedMessage, + TwapMessage, + }, + solana_program::{ + program_error::ProgramError, + pubkey::Pubkey, + }, + std::mem::size_of, +}; + +#[test] +fn test_upd_price_with_validator() { + // Similar to `test_upd_price` but uses validator aggregation instead of + // in-program aggregation. + let mut instruction_data = [0u8; size_of::()]; + populate_instruction(&mut instruction_data, 42, 2, 1); + + let program_id = Pubkey::new_unique(); + + let mut funding_setup = AccountSetup::new_funding(); + let funding_account = funding_setup.as_account_info(); + + let mut price_setup = AccountSetup::new::(&program_id); + let mut price_account = price_setup.as_account_info(); + price_account.is_signer = false; + PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); + + { + let mut price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + price_data.num_ = 1; + price_data.comp_[0].pub_ = *funding_account.key; + price_data.flags.insert(PriceAccountFlags::ACCUMULATOR_V2); + price_data + .flags + .insert(PriceAccountFlags::MESSAGE_BUFFER_CLEARED); + } + + let mut clock_setup = AccountSetup::new_clock(); + let mut clock_account = clock_setup.as_account_info(); + clock_account.is_signer = false; + clock_account.is_writable = false; + + update_clock_slot(&mut clock_account, 1); + + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ) + .is_ok()); + + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 42); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 1); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 0); + assert_eq!(price_data.agg_.pub_slot_, 0); + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + } + + // a publisher's component pub_slot_ has to be strictly increasing -- get rejected + populate_instruction(&mut instruction_data, 43, 2, 1); + + assert_eq!( + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 42); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 1); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 0); + assert_eq!(price_data.agg_.pub_slot_, 0); + assert_eq!(price_data.agg_.price_, 0); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + } + + // We aggregate the price at the end of each slot now. + let messages1 = + validator::aggregate_price(1, 101, price_account.key, *price_account.data.borrow_mut()) + .unwrap(); + let expected_messages1 = [ + PriceFeedMessage { + id: price_account.key.to_bytes(), + price: 42, + conf: 2, + exponent: 0, + publish_time: 101, + prev_publish_time: 0, + ema_price: 42, + ema_conf: 2, + } + .to_bytes(), + TwapMessage { + id: price_account.key.to_bytes(), + cumulative_price: 42, + cumulative_conf: 2, + num_down_slots: 0, + exponent: 0, + publish_time: 101, + prev_publish_time: 0, + publish_slot: 1, + } + .to_bytes(), + ]; + assert_eq!(messages1, expected_messages1); + + update_clock_slot(&mut clock_account, 2); + let messages2 = + validator::aggregate_price(2, 102, price_account.key, *price_account.data.borrow_mut()) + .unwrap(); + + let expected_messages2 = [ + PriceFeedMessage { + id: price_account.key.to_bytes(), + price: 42, + conf: 2, + exponent: 0, + publish_time: 102, + prev_publish_time: 101, + ema_price: 41, + ema_conf: 2, + } + .to_bytes(), + TwapMessage { + id: price_account.key.to_bytes(), + cumulative_price: 84, + cumulative_conf: 4, + num_down_slots: 0, + exponent: 0, + publish_time: 102, + prev_publish_time: 101, + publish_slot: 2, + } + .to_bytes(), + ]; + assert_eq!(messages2, expected_messages2); + + update_clock_slot(&mut clock_account, 3); + // add next price in new slot triggering snapshot and aggregate calc + populate_instruction(&mut instruction_data, 81, 2, 2); + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 81); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 2); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + // Slot values are 1 less than in `test_upd_price` because + // we aggregate at the end of the block now. + assert_eq!(price_data.valid_slot_, 1); + assert_eq!(price_data.agg_.pub_slot_, 2); + assert_eq!(price_data.agg_.price_, 42); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + } + + // next price doesn't change but slot does + populate_instruction(&mut instruction_data, 81, 2, 3); + validator::aggregate_price(3, 103, price_account.key, *price_account.data.borrow_mut()) + .unwrap(); + update_clock_slot(&mut clock_account, 4); + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 81); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 3); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 2); + assert_eq!(price_data.agg_.pub_slot_, 3); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + } + + // next price doesn't change and neither does aggregate but slot does + populate_instruction(&mut instruction_data, 81, 2, 4); + validator::aggregate_price(4, 104, price_account.key, *price_account.data.borrow_mut()) + .unwrap(); + update_clock_slot(&mut clock_account, 5); + + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 81); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 4); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 3); + assert_eq!(price_data.agg_.pub_slot_, 4); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + } + + // try to publish back-in-time + populate_instruction(&mut instruction_data, 81, 2, 1); + update_clock_slot(&mut clock_account, 5); + + assert_eq!( + process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ), + Err(ProgramError::InvalidArgument) + ); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 81); + assert_eq!(price_data.comp_[0].latest_.conf_, 2); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 4); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 3); + assert_eq!(price_data.agg_.pub_slot_, 4); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + } + + populate_instruction(&mut instruction_data, 50, 20, 5); + validator::aggregate_price(5, 105, price_account.key, *price_account.data.borrow_mut()) + .unwrap(); + update_clock_slot(&mut clock_account, 6); + + // Publishing a wide CI results in a status of unknown. + + // check that someone doesn't accidentally break the test. + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + } + + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 50); + assert_eq!(price_data.comp_[0].latest_.conf_, 20); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 5); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_IGNORED); + assert_eq!(price_data.valid_slot_, 4); + assert_eq!(price_data.agg_.pub_slot_, 5); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + } + + // Crank one more time and aggregate should be unknown + populate_instruction(&mut instruction_data, 50, 20, 6); + + validator::aggregate_price(6, 106, price_account.key, *price_account.data.borrow_mut()) + .unwrap(); + update_clock_slot(&mut clock_account, 7); + + + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, 50); + assert_eq!(price_data.comp_[0].latest_.conf_, 20); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 6); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_IGNORED); + assert_eq!(price_data.valid_slot_, 5); + assert_eq!(price_data.agg_.pub_slot_, 6); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + } + + // Negative prices are accepted + populate_instruction(&mut instruction_data, -100, 1, 7); + validator::aggregate_price(7, 107, price_account.key, *price_account.data.borrow_mut()) + .unwrap(); + update_clock_slot(&mut clock_account, 8); + + + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, -100); + assert_eq!(price_data.comp_[0].latest_.conf_, 1); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 7); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 6); + assert_eq!(price_data.agg_.pub_slot_, 7); + assert_eq!(price_data.agg_.price_, 81); + assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN); + } + + // Crank again for aggregate + populate_instruction(&mut instruction_data, -100, 1, 8); + validator::aggregate_price(8, 108, price_account.key, *price_account.data.borrow_mut()) + .unwrap(); + update_clock_slot(&mut clock_account, 9); + + + assert!(process_instruction( + &program_id, + &[ + funding_account.clone(), + price_account.clone(), + clock_account.clone() + ], + &instruction_data + ) + .is_ok()); + + { + let price_data = load_checked::(&price_account, PC_VERSION).unwrap(); + assert_eq!(price_data.comp_[0].latest_.price_, -100); + assert_eq!(price_data.comp_[0].latest_.conf_, 1); + assert_eq!(price_data.comp_[0].latest_.pub_slot_, 8); + assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING); + assert_eq!(price_data.valid_slot_, 7); + assert_eq!(price_data.agg_.pub_slot_, 8); + assert_eq!(price_data.agg_.price_, -100); + assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING); + } +} + +// Create an upd_price instruction with the provided parameters +fn populate_instruction(instruction_data: &mut [u8], price: i64, conf: u64, pub_slot: u64) { + let mut cmd = load_mut::(instruction_data).unwrap(); + cmd.header = OracleCommand::UpdPrice.into(); + cmd.status = PC_STATUS_TRADING; + cmd.price = price; + cmd.confidence = conf; + cmd.publishing_slot = pub_slot; + cmd.unused_ = 0; +} diff --git a/program/rust/src/validator.rs b/program/rust/src/validator.rs new file mode 100644 index 00000000..48761b7b --- /dev/null +++ b/program/rust/src/validator.rs @@ -0,0 +1,130 @@ +use { + crate::{ + accounts::{ + AccountHeader, + PriceAccount, + PriceAccountFlags, + PythAccount, + PythOracleSerialize, + }, + c_oracle_header::PC_MAGIC, + error::OracleError, + processor::{ + c_upd_aggregate, + c_upd_twap, + }, + utils::pyth_assert, + }, + solana_sdk::pubkey::Pubkey, + std::mem::size_of, +}; + +// Attempts to validate and access the contents of an account as a PriceAccount. +fn validate_price_account( + price_account_info: &mut [u8], +) -> Result<&mut PriceAccount, AggregationError> { + // TODO: don't return error on non-price account? + pyth_assert( + price_account_info.len() >= PriceAccount::MINIMUM_SIZE, + OracleError::AccountTooSmall.into(), + ) + .map_err(|_| AggregationError::NotPriceFeedAccount)?; + + { + let account_header = bytemuck::from_bytes::( + &price_account_info[0..size_of::()], + ); + + pyth_assert( + account_header.magic_number == PC_MAGIC + && account_header.account_type == PriceAccount::ACCOUNT_TYPE, + OracleError::InvalidAccountHeader.into(), + ) + .map_err(|_| AggregationError::NotPriceFeedAccount)?; + } + + let data = bytemuck::from_bytes_mut::( + &mut price_account_info[0..size_of::()], + ); + if !data.flags.contains(PriceAccountFlags::ACCUMULATOR_V2) { + return Err(AggregationError::V1AggregationMode); + } + if !data + .flags + .contains(PriceAccountFlags::MESSAGE_BUFFER_CLEARED) + { + // We make sure that we don't generate v2 messages while v1 messages are still + // in the message buffer. + return Err(AggregationError::V1AggregationMode); + } + + Ok(data) +} + +fn update_aggregate(slot: u64, timestamp: i64, price_account: &mut PriceAccount) { + // NOTE: c_upd_aggregate must use a raw pointer to price data. We already + // have the exclusive mut reference so we can simply cast before calling + // the function. + let updated = unsafe { + c_upd_aggregate( + price_account as *mut PriceAccount as *mut u8, + slot, + timestamp, + ) + }; + + // If the aggregate was successfully updated, calculate the difference and update TWAP. + if updated { + let agg_diff = (slot as i64) - price_account.prev_slot_ as i64; + + // See comment on unsafe `c_upd_aggregate` call above for details. + unsafe { + c_upd_twap(price_account as *mut PriceAccount as *mut u8, agg_diff); + } + + // We want to send a message every time the aggregate price updates. However, during the migration, + // not every publisher will necessarily provide the accumulator accounts. The message_sent_ flag + // ensures that after every aggregate update, the next publisher who provides the accumulator accounts + // will send the message. + price_account.message_sent_ = 0; + price_account.update_price_cumulative(); + } +} + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum AggregationError { + #[error("NotPriceFeedAccount")] + NotPriceFeedAccount, + #[error("V1AggregationMode")] + V1AggregationMode, + #[error("AlreadyAggregated")] + AlreadyAggregated, +} + +/// Attempts to read a price account and create a new price aggregate if v2 +/// aggregation is enabled on this price account. Modifies `price_account_data` accordingly. +/// Returns messages that should be included in the merkle tree, unless v1 aggregation +/// is still in use. +/// Note that the `messages` may be returned even if aggregation fails for some reason. +pub fn aggregate_price( + slot: u64, + timestamp: i64, + price_account_pubkey: &Pubkey, + price_account_data: &mut [u8], +) -> Result<[Vec; 2], AggregationError> { + let price_account = validate_price_account(price_account_data)?; + if price_account.agg_.pub_slot_ == slot { + // Avoid v2 aggregation if v1 aggregation has happened in the same slot + // (this should normally happen only in the slot that contains the v1->v2 transition). + return Err(AggregationError::AlreadyAggregated); + } + update_aggregate(slot, timestamp, price_account); + Ok([ + price_account + .as_price_feed_message(price_account_pubkey) + .to_bytes(), + price_account + .as_twap_message(price_account_pubkey) + .to_bytes(), + ]) +}