diff --git a/pythnet/pythnet_sdk/src/messages.rs b/pythnet/pythnet_sdk/src/messages.rs index d151ee32cc..5008ea4f5d 100644 --- a/pythnet/pythnet_sdk/src/messages.rs +++ b/pythnet/pythnet_sdk/src/messages.rs @@ -138,7 +138,9 @@ impl Arbitrary for PriceFeedMessage { } } -/// Message format for sending Twap data via the accumulator program +/// Message format for sending cumulative price data via the accumulator program. +/// These messages are used to calculate TWAPs for a given time window. +/// The calculated TWAPs are stored as TwapPrices in TwapUpdate accounts. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub struct TwapMessage { diff --git a/target_chains/solana/Cargo.lock b/target_chains/solana/Cargo.lock index 20f00f14bb..6e80ccac2d 100644 --- a/target_chains/solana/Cargo.lock +++ b/target_chains/solana/Cargo.lock @@ -125,8 +125,8 @@ checksum = "faa5be5b72abea167f87c868379ba3c2be356bfca9e6f474fd055fa0f7eeb4f2" dependencies = [ "anchor-syn", "anyhow", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "regex", "syn 1.0.107", ] @@ -140,8 +140,8 @@ dependencies = [ "anchor-syn", "anyhow", "bs58 0.5.0", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "rustversion", "syn 1.0.107", ] @@ -153,7 +153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59948e7f9ef8144c2aefb3f32a40c5fce2798baeec765ba038389e82301017ef" dependencies = [ "anchor-syn", - "proc-macro2 1.0.79", + "proc-macro2 1.0.92", "syn 1.0.107", ] @@ -164,8 +164,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc753c9d1c7981cb8948cf7e162fb0f64558999c0413058e2d43df1df5448086" dependencies = [ "anchor-syn", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -177,8 +177,8 @@ checksum = "f38b4e172ba1b52078f53fdc9f11e3dc0668ad27997838a0aad2d148afac8c97" dependencies = [ "anchor-syn", "anyhow", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -190,8 +190,8 @@ checksum = "4eebd21543606ab61e2d83d9da37d24d3886a49f390f9c43a1964735e8c0f0d5" dependencies = [ "anchor-syn", "anyhow", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -222,8 +222,8 @@ checksum = "ec4720d899b3686396cced9508f23dab420f1308344456ec78ef76f98fda42af" dependencies = [ "anchor-syn", "anyhow", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -233,8 +233,8 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f495e85480bd96ddeb77b71d499247c7d4e8b501e75ecb234e9ef7ae7bd6552a" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -271,8 +271,8 @@ dependencies = [ "anyhow", "bs58 0.5.0", "heck 0.3.3", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "serde", "serde_json", "sha2 0.10.6", @@ -364,7 +364,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ - "quote 1.0.33", + "quote 1.0.37", "syn 1.0.107", ] @@ -376,8 +376,8 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint 0.4.3", "num-traits", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -412,8 +412,8 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -473,8 +473,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", "synstructure", ] @@ -485,8 +485,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -536,9 +536,9 @@ version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -695,7 +695,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.79", + "proc-macro2 1.0.92", "syn 1.0.107", ] @@ -708,7 +708,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.79", + "proc-macro2 1.0.92", "syn 1.0.107", ] @@ -718,8 +718,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -729,8 +729,8 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -740,8 +740,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -751,8 +751,8 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -845,8 +845,8 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -858,9 +858,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bzip2" @@ -981,8 +981,8 @@ checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" dependencies = [ "heck 0.4.0", "proc-macro-error", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -1243,8 +1243,8 @@ dependencies = [ "cc", "codespan-reporting", "once_cell", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "scratch", "syn 1.0.107", ] @@ -1261,8 +1261,8 @@ version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -1284,10 +1284,10 @@ checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "strsim 0.10.0", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -1297,8 +1297,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", - "quote 1.0.33", - "syn 2.0.39", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1353,8 +1353,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -1425,8 +1425,8 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -1507,8 +1507,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0188e3c3ba8df5753894d54461f0e39bc91741dc5b22e1c46999ec2c71f4e4" dependencies = [ "enum-ordinalize", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -1548,9 +1548,9 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1561,8 +1561,8 @@ checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a" dependencies = [ "num-bigint 0.4.3", "num-traits", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "rustc_version", "syn 1.0.107", ] @@ -1720,9 +1720,9 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -2491,8 +2491,8 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -2580,8 +2580,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -2591,9 +2591,9 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -2673,9 +2673,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate 1.3.0", - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -2685,9 +2685,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" dependencies = [ "proc-macro-crate 1.3.0", - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -2716,9 +2716,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -2775,8 +2775,8 @@ checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" dependencies = [ "Inflector", "proc-macro-error", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -2866,8 +2866,8 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -2966,8 +2966,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", "version_check", ] @@ -2978,8 +2978,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "version_check", ] @@ -2994,9 +2994,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -3065,7 +3065,7 @@ dependencies = [ [[package]] name = "pyth-solana-receiver" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anchor-lang", "byteorder", @@ -3208,11 +3208,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.92", ] [[package]] @@ -3649,8 +3649,8 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "serde_derive_internals", "syn 1.0.107", ] @@ -3682,8 +3682,8 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -3750,9 +3750,9 @@ version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -3761,8 +3761,8 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -3806,9 +3806,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ "darling", - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -4228,10 +4228,10 @@ version = "1.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "992b866b9f0510fd3c290afe6a37109ae8d15b74fa24e3fb6d164be2971ee94f" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "rustc_version", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -4714,10 +4714,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fe4363d2503a75325ec94aa18b063574edb3454d38840e01c5af477b3b0689d" dependencies = [ "bs58 0.4.0", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "rustversion", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -5035,9 +5035,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fadbefec4f3c678215ca72bd71862697bb06b41fd77c0088902dd3203354387b" dependencies = [ - "quote 1.0.33", + "quote 1.0.37", "spl-discriminator-syn", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -5046,10 +5046,10 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e5f2044ca42c8938d54d1255ce599c79a1ffd86b677dfab695caa20f9ffc3f2" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "sha2 0.10.6", - "syn 2.0.39", + "syn 2.0.90", "thiserror", ] @@ -5094,10 +5094,10 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5269c8e868da17b6552ef35a51355a017bd8e0eae269c201fef830d35fa52c" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "sha2 0.10.6", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -5228,8 +5228,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.0", - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "rustversion", "syn 1.0.107", ] @@ -5263,19 +5263,19 @@ version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.39" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "unicode-ident", ] @@ -5285,8 +5285,8 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", "unicode-xid 0.2.4", ] @@ -5353,8 +5353,8 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee42b4e559f17bce0385ebf511a7beb67d5cc33c12c96b7f4e9789919d9c10f" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -5410,9 +5410,9 @@ version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -5512,9 +5512,9 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -5661,8 +5661,8 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", ] @@ -5924,9 +5924,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -5948,7 +5948,7 @@ version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ - "quote 1.0.33", + "quote 1.0.37", "wasm-bindgen-macro-support", ] @@ -5958,9 +5958,9 @@ version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6335,9 +6335,9 @@ version = "0.7.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", - "syn 2.0.39", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -6355,8 +6355,8 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ - "proc-macro2 1.0.79", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.107", "synstructure", ] diff --git a/target_chains/solana/programs/pyth-solana-receiver/Cargo.toml b/target_chains/solana/programs/pyth-solana-receiver/Cargo.toml index 10e8aa8836..376248d1e5 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/Cargo.toml +++ b/target_chains/solana/programs/pyth-solana-receiver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-solana-receiver" -version = "0.1.0" +version = "0.2.0" description = "Created with Anchor" edition = "2021" @@ -17,12 +17,17 @@ test-bpf = [] [dependencies] anchor-lang = { workspace = true } -pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk", features = ["solana-program"] } +pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk", features = [ + "solana-program", +] } solana-program = { workspace = true } byteorder = "1.4.3" -wormhole-core-bridge-solana = {workspace = true} -wormhole-raw-vaas = {version = "0.1.3", features = ["ruint", "on-chain"], default-features = false } -pyth-solana-receiver-sdk = { path = "../../pyth_solana_receiver_sdk"} +wormhole-core-bridge-solana = { workspace = true } +wormhole-raw-vaas = { version = "0.1.3", features = [ + "ruint", + "on-chain", +], default-features = false } +pyth-solana-receiver-sdk = { path = "../../pyth_solana_receiver_sdk" } rand = "0.8.5" [dev-dependencies] diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs index b792db8c67..5e06a44ad0 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs @@ -15,6 +15,14 @@ pub enum ReceiverError { InvalidDataSource, #[msg("Funds are insufficient to pay the receiving fee")] InsufficientFunds, + #[msg("Cannot calculate TWAP, end slot must be greater than start slot")] + InvalidTwapSlots, + #[msg("Start message is not the first update for its timestamp")] + InvalidTwapStartMessage, + #[msg("End message is not the first update for its timestamp")] + InvalidTwapEndMessage, + #[msg("Overflow in TWAP calculation")] + TwapCalculationOverflow, // Price account permissions #[msg("This signer can't write to price update account")] WrongWriteAuthority, diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index 3c7822bf43..32a0f9887e 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs @@ -8,13 +8,13 @@ use { pyth_solana_receiver_sdk::{ config::{Config, DataSource}, pda::{CONFIG_SEED, TREASURY_SEED}, - price_update::{PriceUpdateV2, VerificationLevel}, - PostUpdateAtomicParams, PostUpdateParams, + price_update::{PriceUpdateV2, TwapUpdate, VerificationLevel}, + PostTwapUpdateParams, PostUpdateAtomicParams, PostUpdateParams, }, pythnet_sdk::{ accumulators::merkle::MerkleRoot, hashers::keccak256_160::Keccak160, - messages::Message, + messages::{Message, TwapMessage}, wire::{ from_slice, v1::{WormholeMessage, WormholePayload}, @@ -232,6 +232,52 @@ pub mod pyth_solana_receiver { Ok(()) } + /// Post a TWAP (time weighted average price) update for a given time window. + pub fn post_twap_update( + ctx: Context, + params: PostTwapUpdateParams, + ) -> Result<()> { + let config = &ctx.accounts.config; + let payer: &Signer<'_> = &ctx.accounts.payer; + let write_authority: &Signer<'_> = &ctx.accounts.write_authority; + + // IMPORTANT: These lines check that the encoded VAAs have ProcessingStatus::Verified. + // These checks are critical otherwise the program could be tricked into accepting unverified VAAs. + let start_encoded_vaa = VaaAccount::load(&ctx.accounts.start_encoded_vaa)?; + let end_encoded_vaa = VaaAccount::load(&ctx.accounts.end_encoded_vaa)?; + + let treasury: &AccountInfo<'_> = &ctx.accounts.treasury; + let twap_update_account: &mut Account<'_, TwapUpdate> = + &mut ctx.accounts.twap_update_account; + + let start_vaa_components = VaaComponents { + verification_level: VerificationLevel::Full, + emitter_address: start_encoded_vaa.try_emitter_address()?, + emitter_chain: start_encoded_vaa.try_emitter_chain()?, + }; + let end_vaa_components = VaaComponents { + verification_level: VerificationLevel::Full, + emitter_address: end_encoded_vaa.try_emitter_address()?, + emitter_chain: end_encoded_vaa.try_emitter_chain()?, + }; + + post_twap_update_from_vaas( + config, + payer, + write_authority, + treasury, + twap_update_account, + &start_vaa_components, + &end_vaa_components, + start_encoded_vaa.try_payload()?.as_ref(), + end_encoded_vaa.try_payload()?.as_ref(), + ¶ms.start_merkle_price_update, + ¶ms.end_merkle_price_update, + )?; + + Ok(()) + } + pub fn reclaim_rent(_ctx: Context) -> Result<()> { Ok(()) } @@ -290,6 +336,30 @@ pub struct PostUpdate<'info> { pub write_authority: Signer<'info>, } +#[derive(Accounts)] +#[instruction(params: PostTwapUpdateParams)] +pub struct PostTwapUpdate<'info> { + #[account(mut)] + pub payer: Signer<'info>, + /// CHECK: We aren't deserializing the VAA here but later with VaaAccount::load, which is the recommended way + #[account(owner = config.wormhole @ ReceiverError::WrongVaaOwner)] + pub start_encoded_vaa: AccountInfo<'info>, + /// CHECK: We aren't deserializing the VAA here but later with VaaAccount::load, which is the recommended way + #[account(owner = config.wormhole @ ReceiverError::WrongVaaOwner)] + pub end_encoded_vaa: AccountInfo<'info>, + #[account(seeds = [CONFIG_SEED.as_ref()], bump)] + pub config: Account<'info, Config>, + /// CHECK: This is just a PDA controlled by the program. There is currently no way to withdraw funds from it. + #[account(mut, seeds = [TREASURY_SEED.as_ref(), &[params.treasury_id]], bump)] + pub treasury: AccountInfo<'info>, + /// The constraint is such that either the price_update_account is uninitialized or the write_authority is the write_authority. + /// Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized + #[account(init_if_needed, constraint = twap_update_account.write_authority == Pubkey::default() || twap_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority , payer =payer, space = TwapUpdate::LEN)] + pub twap_update_account: Account<'info, TwapUpdate>, + pub system_program: Program<'info, System>, + pub write_authority: Signer<'info>, +} + #[derive(Accounts)] #[instruction(params: PostUpdateAtomicParams)] pub struct PostUpdateAtomic<'info> { @@ -368,61 +438,159 @@ fn post_price_update_from_vaa<'info>( vaa_payload: &[u8], price_update: &MerklePriceUpdate, ) -> Result<()> { + pay_single_update_fee(config, treasury, payer)?; + verify_vaa_data_source(config, vaa_components)?; + let message = verify_merkle_proof(vaa_payload, price_update)?; + match message { + Message::PriceFeedMessage(price_feed_message) => { + price_update_account.write_authority = write_authority.key(); + price_update_account.verification_level = vaa_components.verification_level; + price_update_account.price_message = price_feed_message; + price_update_account.posted_slot = Clock::get()?.slot; + } + Message::TwapMessage(_) | Message::PublisherStakeCapsMessage(_) => { + return err!(ReceiverError::UnsupportedMessageType); + } + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn post_twap_update_from_vaas<'info>( + config: &Account<'info, Config>, + payer: &Signer<'info>, + write_authority: &Signer<'info>, + treasury: &AccountInfo<'info>, + twap_update_account: &mut Account<'_, TwapUpdate>, + start_vaa_components: &VaaComponents, + end_vaa_components: &VaaComponents, + start_vaa_payload: &[u8], + end_vaa_payload: &[u8], + start_price_update: &MerklePriceUpdate, + end_price_update: &MerklePriceUpdate, +) -> Result<()> { + pay_single_update_fee(config, treasury, payer)?; + + // Verify data sources for both VAAs + for vaa_components in [start_vaa_components, end_vaa_components] { + verify_vaa_data_source(config, vaa_components)?; + } + + // Verify both merkle proofs and extract their messages + let start_message = verify_merkle_proof(start_vaa_payload, start_price_update)?; + let end_message = verify_merkle_proof(end_vaa_payload, end_price_update)?; + + // Calculate the TWAP and store it in the output account + match (start_message, end_message) { + (Message::TwapMessage(start_msg), Message::TwapMessage(end_msg)) => { + let (price, conf, down_slots_ratio) = calculate_twap(&start_msg, &end_msg)?; + + twap_update_account.write_authority = write_authority.key(); + twap_update_account.verification_level = start_vaa_components.verification_level; + + twap_update_account.twap.feed_id = start_msg.feed_id; + twap_update_account.twap.start_time = start_msg.publish_time; + twap_update_account.twap.end_time = end_msg.publish_time; + twap_update_account.twap.price = price; + twap_update_account.twap.conf = conf; + twap_update_account.twap.exponent = start_msg.exponent; + twap_update_account.twap.down_slots_ratio = down_slots_ratio; + + twap_update_account.posted_slot = Clock::get()?.slot; + } + _ => { + return err!(ReceiverError::UnsupportedMessageType); + } + } + + Ok(()) +} + +fn calculate_twap(start_msg: &TwapMessage, end_msg: &TwapMessage) -> Result<(i64, u64, u32)> { + // Validate slots + require!( + end_msg.publish_slot > start_msg.publish_slot, + ReceiverError::InvalidTwapSlots + ); + + // Validate first messages in timestamp + require!( + start_msg.prev_publish_time < start_msg.publish_time, + ReceiverError::InvalidTwapStartMessage + ); + require!( + end_msg.prev_publish_time < end_msg.publish_time, + ReceiverError::InvalidTwapEndMessage + ); + let slot_diff = end_msg + .publish_slot + .checked_sub(start_msg.publish_slot) + .ok_or(ReceiverError::TwapCalculationOverflow)?; + + let price_diff = end_msg + .cumulative_price + .checked_sub(start_msg.cumulative_price) + .ok_or(ReceiverError::TwapCalculationOverflow)?; + + let conf_diff = end_msg + .cumulative_conf + .checked_sub(start_msg.cumulative_conf) + .ok_or(ReceiverError::TwapCalculationOverflow)?; + + // Calculate time averaged price and confidence + let price = i64::try_from(price_diff / i128::from(slot_diff)) + .map_err(|_| ReceiverError::TwapCalculationOverflow)?; + let conf = u64::try_from(conf_diff / u128::from(slot_diff)) + .map_err(|_| ReceiverError::TwapCalculationOverflow)?; + + // Calculate down_slots_ratio as an integer between 0 and 1_000_000 + // A value of 1_000_000 means all slots were missed and 0 means no slots were missed. + let total_slots = end_msg + .publish_slot + .checked_sub(start_msg.publish_slot) + .ok_or(ReceiverError::TwapCalculationOverflow)?; + let total_down_slots = end_msg + .num_down_slots + .checked_sub(start_msg.num_down_slots) + .ok_or(ReceiverError::TwapCalculationOverflow)?; + let down_slots_ratio = total_down_slots + .checked_mul(1_000_000) + .ok_or(ReceiverError::TwapCalculationOverflow)? + .checked_div(total_slots) + .ok_or(ReceiverError::TwapCalculationOverflow)?; + // down_slots_ratio is a number in [0, 1_000_000], so we only need 32 unsigned bits + let down_slots_ratio = + u32::try_from(down_slots_ratio).map_err(|_| ReceiverError::TwapCalculationOverflow)?; + Ok((price, conf, down_slots_ratio)) +} + +fn pay_single_update_fee<'info>( + config: &Account<'info, Config>, + treasury: &AccountInfo<'info>, + payer: &Signer<'info>, +) -> Result<()> { + // Handle treasury payment let amount_to_pay = if treasury.lamports() == 0 { Rent::get()? .minimum_balance(0) .max(config.single_update_fee_in_lamports) } else { config.single_update_fee_in_lamports - }; // First person to use the treasury account has to pay rent + }; + if payer.lamports() < Rent::get()? .minimum_balance(payer.data_len()) .saturating_add(amount_to_pay) { return err!(ReceiverError::InsufficientFunds); - }; + } let transfer_instruction = system_instruction::transfer(payer.key, treasury.key, amount_to_pay); anchor_lang::solana_program::program::invoke( &transfer_instruction, &[payer.to_account_info(), treasury.to_account_info()], )?; - - let valid_data_source = config.valid_data_sources.iter().any(|x| { - *x == DataSource { - chain: vaa_components.emitter_chain, - emitter: Pubkey::from(vaa_components.emitter_address), - } - }); - if !valid_data_source { - return err!(ReceiverError::InvalidDataSource); - } - - let wormhole_message = WormholeMessage::try_from_bytes(vaa_payload) - .map_err(|_| ReceiverError::InvalidWormholeMessage)?; - let root: MerkleRoot = MerkleRoot::new(match wormhole_message.payload { - WormholePayload::Merkle(merkle_root) => merkle_root.root, - }); - - if !root.check(price_update.proof.clone(), price_update.message.as_ref()) { - return err!(ReceiverError::InvalidPriceUpdate); - } - - let message = from_slice::(price_update.message.as_ref()) - .map_err(|_| ReceiverError::DeserializeMessageFailed)?; - - match message { - Message::PriceFeedMessage(price_feed_message) => { - price_update_account.write_authority = write_authority.key(); - price_update_account.verification_level = vaa_components.verification_level; - price_update_account.price_message = price_feed_message; - price_update_account.posted_slot = Clock::get()?.slot; - } - Message::TwapMessage(_) | Message::PublisherStakeCapsMessage(_) => { - return err!(ReceiverError::UnsupportedMessageType); - } - } Ok(()) } @@ -459,3 +627,101 @@ fn verify_guardian_signature( // Done. Ok(()) } + +fn verify_merkle_proof(vaa_payload: &[u8], price_update: &MerklePriceUpdate) -> Result { + let wormhole_message = WormholeMessage::try_from_bytes(vaa_payload) + .map_err(|_| ReceiverError::InvalidWormholeMessage)?; + let root: MerkleRoot = MerkleRoot::new(match wormhole_message.payload { + WormholePayload::Merkle(merkle_root) => merkle_root.root, + }); + + if !root.check(price_update.proof.clone(), price_update.message.as_ref()) { + return err!(ReceiverError::InvalidPriceUpdate); + } + + from_slice::(price_update.message.as_ref()) + .map_err(|_| error!(ReceiverError::DeserializeMessageFailed)) +} +fn verify_vaa_data_source( + config: &Account<'_, Config>, + vaa_components: &VaaComponents, +) -> Result<()> { + let valid_data_source = config.valid_data_sources.iter().any(|x| { + *x == DataSource { + chain: vaa_components.emitter_chain, + emitter: Pubkey::from(vaa_components.emitter_address), + } + }); + if !valid_data_source { + return err!(ReceiverError::InvalidDataSource); + } + Ok(()) +} + +#[cfg(test)] +/// Unit tests for the core TWAP calculation logic in `calculate_twap` +/// This test module is here because `calculate_twap` is private and can't +/// be imported into `tests/test_post_twap_updates`. +mod calculate_twap_unit_tests { + use super::*; + + fn create_basic_twap_message( + cumulative_price: i128, + publish_time: i64, + prev_publish_time: i64, + publish_slot: u64, + ) -> TwapMessage { + TwapMessage { + feed_id: [0; 32], + cumulative_price, + cumulative_conf: 100, + num_down_slots: 0, + exponent: 8, + publish_time, + prev_publish_time, + publish_slot, + } + } + + #[test] + fn test_valid_twap() { + let start = create_basic_twap_message(100, 100, 90, 1000); + let end = create_basic_twap_message(300, 200, 180, 1100); + + let price = calculate_twap(&start, &end).unwrap(); + assert_eq!(price.0, 2); // (300-100)/(1100-1000) = 2 + } + + #[test] + fn test_invalid_slot_order() { + let start = create_basic_twap_message(100, 100, 90, 1100); + let end = create_basic_twap_message(300, 200, 180, 1000); + + let err = calculate_twap(&start, &end).unwrap_err(); + assert_eq!(err, ReceiverError::InvalidTwapSlots.into()); + } + + #[test] + fn test_invalid_timestamps() { + let start = create_basic_twap_message(100, 100, 110, 1000); + let end = create_basic_twap_message(300, 200, 180, 1100); + + let err = calculate_twap(&start, &end).unwrap_err(); + assert_eq!(err, ReceiverError::InvalidTwapStartMessage.into()); + + let start = create_basic_twap_message(100, 100, 90, 1000); + let end = create_basic_twap_message(300, 200, 200, 1100); + + let err = calculate_twap(&start, &end).unwrap_err(); + assert_eq!(err, ReceiverError::InvalidTwapEndMessage.into()); + } + + #[test] + fn test_overflow() { + let start = create_basic_twap_message(i128::MIN, 100, 90, 1000); + let end = create_basic_twap_message(i128::MAX, 200, 180, 1100); + + let err = calculate_twap(&start, &end).unwrap_err(); + assert_eq!(err, ReceiverError::TwapCalculationOverflow.into()); + } +} diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs index 3ccf7262b6..6a8de22aec 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs +++ b/target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs @@ -4,7 +4,7 @@ use { pyth_solana_receiver_sdk::{ config::{Config, DataSource}, pda::{get_config_address, get_treasury_address}, - PostUpdateAtomicParams, PostUpdateParams, + PostTwapUpdateParams, PostUpdateAtomicParams, PostUpdateParams, }, pythnet_sdk::wire::v1::{AccumulatorUpdateData, MerklePriceUpdate, Proof}, rand::Rng, @@ -80,6 +80,30 @@ impl accounts::PostUpdate { } } +impl accounts::PostTwapUpdate { + pub fn populate( + payer: Pubkey, + write_authority: Pubkey, + start_encoded_vaa: Pubkey, + end_encoded_vaa: Pubkey, + twap_update_account: Pubkey, + treasury_id: u8, + ) -> Self { + let config = get_config_address(); + let treasury = get_treasury_address(treasury_id); + accounts::PostTwapUpdate { + payer, + start_encoded_vaa, + end_encoded_vaa, + config, + treasury, + twap_update_account, + system_program: system_program::ID, + write_authority, + } + } +} + impl accounts::Governance { pub fn populate(payer: Pubkey) -> Self { let config = get_config_address(); @@ -180,7 +204,42 @@ impl instruction::PostUpdateAtomic { } } } +impl instruction::PostTwapUpdate { + #[allow(clippy::too_many_arguments)] + pub fn populate( + payer: Pubkey, + write_authority: Pubkey, + start_encoded_vaa: Pubkey, + end_encoded_vaa: Pubkey, + twap_update_account: Pubkey, + start_merkle_price_update: MerklePriceUpdate, + end_merkle_price_update: MerklePriceUpdate, + treasury_id: u8, + ) -> Instruction { + let post_twap_accounts = accounts::PostTwapUpdate::populate( + payer, + write_authority, + start_encoded_vaa, + end_encoded_vaa, + twap_update_account, + treasury_id, + ) + .to_account_metas(None); + Instruction { + program_id: ID, + accounts: post_twap_accounts, + data: instruction::PostTwapUpdate { + params: PostTwapUpdateParams { + start_merkle_price_update, + end_merkle_price_update, + treasury_id, + }, + } + .data(), + } + } +} impl instruction::SetDataSources { pub fn populate(payer: Pubkey, data_sources: Vec) -> Instruction { let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None); diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_twap_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_twap_updates.rs new file mode 100644 index 0000000000..162e06cd33 --- /dev/null +++ b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_twap_updates.rs @@ -0,0 +1,207 @@ +use { + common_test_utils::{ + assert_treasury_balance, setup_pyth_receiver, ProgramTestFixtures, WrongSetupOption, + }, + pyth_solana_receiver::{ + instruction::PostTwapUpdate, + sdk::{deserialize_accumulator_update_data, DEFAULT_TREASURY_ID}, + }, + pyth_solana_receiver_sdk::price_update::{TwapUpdate, VerificationLevel}, + pythnet_sdk::{ + messages::{Message, TwapMessage}, + test_utils::create_accumulator_message, + }, + solana_sdk::{rent::Rent, signature::Keypair, signer::Signer}, +}; + +/// Test the happy path for posting 2 twap updates. +/// Verification errors are being tested by the other files in this module. +#[tokio::test] +async fn test_post_twap_updates() { + // ARRANGE // + + // Create start and end cumulative price updates for feed 1 and feed 2 + // feed 1 start message of cumul_price=100 at slot=100 + let feed_1_start_msg = Message::TwapMessage(TwapMessage { + feed_id: [1; 32], + cumulative_price: 100, + cumulative_conf: 100, + num_down_slots: 10, + exponent: -8, + publish_time: 100, + prev_publish_time: 99, + publish_slot: 100, + }); + // feed 1 end message of cumul_price=500 at slot=500 + let feed_1_end_msg = Message::TwapMessage(TwapMessage { + feed_id: [1; 32], + cumulative_price: 500, + cumulative_conf: 500, + num_down_slots: 110, // 100 new down slots out of 400 total slots = 25% down slots + exponent: -8, + publish_time: 500, + prev_publish_time: 499, + publish_slot: 500, + }); + + // feed 2 start message of cumul_price=200 at slot=100 + let feed_2_start_msg = Message::TwapMessage(TwapMessage { + feed_id: [2; 32], + cumulative_price: 200, + cumulative_conf: 200, + num_down_slots: 20, + exponent: -6, + publish_time: 100, + prev_publish_time: 99, + publish_slot: 100, + }); + // feed 2 end message of cumul_price=3200 at slot=500 + let feed_2_end_msg = Message::TwapMessage(TwapMessage { + feed_id: [2; 32], + cumulative_price: 3200, + cumulative_conf: 3200, + num_down_slots: 320, // 300 new down slots out of 400 total slots = 75% down slots + exponent: -6, + publish_time: 500, + prev_publish_time: 499, + publish_slot: 500, + }); + + // Combine the updates into accumulator messages + let start_accumulator_message = create_accumulator_message( + &[&feed_1_start_msg, &feed_2_start_msg], + &[&feed_1_start_msg, &feed_2_start_msg], + false, + false, + None, + ); + let end_accumulator_message = create_accumulator_message( + &[&feed_1_end_msg, &feed_2_end_msg], + &[&feed_1_end_msg, &feed_2_end_msg], + false, + false, + None, + ); + // Extract the VAAs and merkle proofs from the accumulator updates + let (start_vaa, start_merkle_price_updates) = + deserialize_accumulator_update_data(start_accumulator_message).unwrap(); + let (end_vaa, end_merkle_price_updates) = + deserialize_accumulator_update_data(end_accumulator_message).unwrap(); + + // Set up receiver program simulation + let ProgramTestFixtures { + mut program_simulator, + encoded_vaa_addresses, + governance_authority: _, + } = setup_pyth_receiver( + vec![ + serde_wormhole::from_slice(&start_vaa).unwrap(), + serde_wormhole::from_slice(&end_vaa).unwrap(), + ], + WrongSetupOption::None, + ) + .await; + + // Check that we have zero fee payment balance before starting + assert_treasury_balance(&mut program_simulator, 0, DEFAULT_TREASURY_ID).await; + let poster = program_simulator.get_funded_keypair().await.unwrap(); + + // The program will post the TWAPs to these accounts + let twap_update_keypair_1 = Keypair::new(); + let twap_update_keypair_2 = Keypair::new(); + + // ACT // + + // Post the TWAP updates + // Post feed 1 TWAP + program_simulator + .process_ix_with_default_compute_limit( + PostTwapUpdate::populate( + poster.pubkey(), + poster.pubkey(), // Using poster as write authority + encoded_vaa_addresses[0], // start_encoded_vaa + encoded_vaa_addresses[1], // end_encoded_vaa + twap_update_keypair_1.pubkey(), + start_merkle_price_updates[0].clone(), + end_merkle_price_updates[0].clone(), + DEFAULT_TREASURY_ID, + ), + &vec![&poster, &twap_update_keypair_1], + None, + ) + .await + .unwrap(); + + // Post feed 2 TWAP + program_simulator + .process_ix_with_default_compute_limit( + PostTwapUpdate::populate( + poster.pubkey(), + poster.pubkey(), // Using poster as write authority + encoded_vaa_addresses[0], // start_encoded_vaa + encoded_vaa_addresses[1], // end_encoded_vaa + twap_update_keypair_2.pubkey(), + start_merkle_price_updates[1].clone(), + end_merkle_price_updates[1].clone(), + DEFAULT_TREASURY_ID, + ), + &vec![&poster, &twap_update_keypair_2], + None, + ) + .await + .unwrap(); + + // ASSERT // + + // Check feed 1 TWAP + let twap_update_account_1 = program_simulator + .get_anchor_account_data::(twap_update_keypair_1.pubkey()) + .await + .unwrap(); + + // Assert that the TWAP account was created correctly + assert_eq!(twap_update_account_1.write_authority, poster.pubkey()); + assert_eq!( + twap_update_account_1.verification_level, + VerificationLevel::Full + ); + + // Assert all TWAP fields are correctly calculated for feed 1 + assert_eq!(twap_update_account_1.twap.feed_id, [1; 32]); + assert_eq!(twap_update_account_1.twap.start_time, 100); + assert_eq!(twap_update_account_1.twap.end_time, 500); + assert_eq!(twap_update_account_1.twap.price, 1); // (500-100)/(500-100) = 1 + assert_eq!(twap_update_account_1.twap.conf, 1); + assert_eq!(twap_update_account_1.twap.exponent, -8); + assert_eq!(twap_update_account_1.twap.down_slots_ratio, 250_000); // 25% down slots = 250,000 + + // Check feed 2 TWAP + let twap_update_account_2 = program_simulator + .get_anchor_account_data::(twap_update_keypair_2.pubkey()) + .await + .unwrap(); + + // Assert that the TWAP account was created correctly + assert_eq!(twap_update_account_2.write_authority, poster.pubkey()); + assert_eq!( + twap_update_account_2.verification_level, + VerificationLevel::Full + ); + + // Assert all TWAP fields are correctly calculated for feed 2 + assert_eq!(twap_update_account_2.twap.feed_id, [2; 32]); + assert_eq!(twap_update_account_2.twap.start_time, 100); + assert_eq!(twap_update_account_2.twap.end_time, 500); + assert_eq!(twap_update_account_2.twap.price, 7); // (3200-200)/(500-100)=7.5 --> 7 + assert_eq!(twap_update_account_2.twap.conf, 7); + assert_eq!(twap_update_account_2.twap.exponent, -6); + assert_eq!(twap_update_account_2.twap.down_slots_ratio, 750_000); // 75% down slots = 750,000 + + // Assert that rent for the treasury was paid (first ix) + an update fee was paid (second ix) + assert_treasury_balance( + &mut program_simulator, + Rent::default().minimum_balance(0) + 1, + DEFAULT_TREASURY_ID, + ) + .await; +} diff --git a/target_chains/solana/pyth_solana_receiver_sdk/src/lib.rs b/target_chains/solana/pyth_solana_receiver_sdk/src/lib.rs index 4e786d440d..355c2ededc 100644 --- a/target_chains/solana/pyth_solana_receiver_sdk/src/lib.rs +++ b/target_chains/solana/pyth_solana_receiver_sdk/src/lib.rs @@ -31,3 +31,10 @@ pub struct PostUpdateAtomicParams { pub merkle_price_update: MerklePriceUpdate, pub treasury_id: u8, } + +#[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] +pub struct PostTwapUpdateParams { + pub start_merkle_price_update: MerklePriceUpdate, + pub end_merkle_price_update: MerklePriceUpdate, + pub treasury_id: u8, +} diff --git a/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs b/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs index 877333eecd..db34864fcd 100644 --- a/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs +++ b/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs @@ -59,6 +59,45 @@ pub struct PriceUpdateV2 { impl PriceUpdateV2 { pub const LEN: usize = 8 + 32 + 2 + 32 + 8 + 8 + 4 + 8 + 8 + 8 + 8 + 8; } +/// A time weighted average price account. This account is used by the Pyth Receiver program to store a verified TWAP update from a Pyth price feed. +/// It contains: +/// - `write_authority`: The write authority for this account. This authority can close this account to reclaim rent or update the account to contain a different TWAP update. +/// - `verification_level`: The [`VerificationLevel`] of this price update. This represents how many Wormhole guardian signatures have been verified for this TWAP update. +/// - `twap`: The actual TWAP update. +/// - `posted_slot`: The slot at which this TWAP update was posted. +#[account] +#[derive(BorshSchema)] +pub struct TwapUpdate { + pub write_authority: Pubkey, + pub verification_level: VerificationLevel, + pub twap: TwapPrice, + pub posted_slot: u64, +} + +impl TwapUpdate { + pub const LEN: usize = ( + 8 // account discriminator (anchor) + + 32 // write_authority + + 2 // verification_level + + (32 + 8 + 8 + 8 + 8 + 4 + 4) // twap + + 8 + // posted_slot + ); +} +/// The time weighted average price & conf for a feed over the window [start_time, end_time]. +/// This type is used to persist the calculated TWAP in TwapUpdate accounts on Solana. +#[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, PartialEq, BorshSchema, Debug)] +pub struct TwapPrice { + pub feed_id: FeedId, + pub start_time: i64, + pub end_time: i64, + pub price: i64, + pub conf: u64, + pub exponent: i32, + /// Ratio out of 1_000_000, where a value of 1_000_000 represents + /// all slots were missed and 0 represents no slots were missed. + pub down_slots_ratio: u32, +} /// A Pyth price. /// The actual price is `(price ± conf)* 10^exponent`. `publish_time` may be used to check the recency of the price.