Forráskód Böngészése

feat(pyth-solana-receiver): add `post_twap_update` instruction & types (#2172)

* feat: impl post_twap_update and necessary types

* refactor: move TwapPrice to receiver sdk
Tejas Badadare 11 hónapja
szülő
commit
59f3f6f37c

+ 3 - 1
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 {

+ 155 - 155
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",
 ]

+ 10 - 5
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]

+ 8 - 0
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,

+ 306 - 40
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<PostTwapUpdate>,
+        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(),
+            &params.start_merkle_price_update,
+            &params.end_merkle_price_update,
+        )?;
+
+        Ok(())
+    }
+
     pub fn reclaim_rent(_ctx: Context<ReclaimRent>) -> 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<Keccak160> = 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::<byteorder::BE, Message>(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<Message> {
+    let wormhole_message = WormholeMessage::try_from_bytes(vaa_payload)
+        .map_err(|_| ReceiverError::InvalidWormholeMessage)?;
+    let root: MerkleRoot<Keccak160> = 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::<byteorder::BE, Message>(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());
+    }
+}

+ 60 - 1
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<DataSource>) -> Instruction {
         let governance_accounts = accounts::Governance::populate(payer).to_account_metas(None);

+ 207 - 0
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::<TwapUpdate>(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::<TwapUpdate>(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;
+}

+ 7 - 0
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,
+}

+ 39 - 0
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.