Преглед изворни кода

[solana] A working solana receiver program (#632)

* A working version of solana receiver

* Cleanup

* Minor

* Commit for triggering tilt

* Add program key starting with pyth

* Remove duplicated hard-coded wormhole address

* Add check for VAA magic number and emmitter is Pythnet or Solana

* Cleaner command for building the cli package

* Fix bug and use BatchPriceAttestation for deserialization

* minor
Yunhao Zhang пре 2 година
родитељ
комит
b6fcb03d6b

+ 8 - 5
target_chains/solana/Anchor.toml

@@ -1,15 +1,18 @@
 [features]
 seeds = false
 skip-lint = false
-[programs.localnet]
-solana_receiver = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
+[programs.devnet]
+pyth_solana_receiver = "pythKkWXoywbvTQVcWrNDz5ENvWteF7tem7xzW52NBK"
 
 [registry]
 url = "https://api.apr.dev"
 
 [provider]
-cluster = "Localnet"
-wallet = "/home/yunhao/.config/solana/id.json"
+cluster = "https://api.devnet.solana.com"
+wallet = "~/.config/solana/id.json"
 
 [scripts]
-test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+deploy = "anchor deploy --program-keypair program_address.json --program-name solana-receiver"
+cli_build = "cargo build --package pyth-solana-receiver-cli"
+cli_test = "cargo run --package pyth-solana-receiver-cli post-and-receive-vaa -v AQAAAAABABPz9W9ARa/V06ZBp68lkoGJ3tlnJRZ2/jYU4Wi1jdQAdQj3ZkPLKIMtzN3EqaCAjKrLG/sKqADTbPoaBtn9zKUBZAYEhwAAAAAAAfNGGVrALzfWDU24/6bvdMsb41UAR1Q6Sp7prPTXhpewAAAAAApC0/gBUDJXSAADAAEAAQIAAwCdfVoraihHxJUfK+NKfkJ4r4DsPSm8uMcxEWv1D/av5l7/DsJkQsV9dFZpW4Q2lOc3mxXPGyULJ+DkfmV/GVWq/wAAAAAAG8iMAAAAAAAAARb////7AAAAAAAbz04AAAAAAAABfQEAAAACAAAACwAAAABkBgSHAAAAAGQGBIcAAAAAZAYEhwAAAAAAG8hzAAAAAAAAAS8AAAAAZAYEhD3tPwvP5dgslVQroHu37+dlyxeoTqcDWIDDMUz5Y0KBMhuk1gj6dbp21tc9qnFavL3rnboCJX8FobWReLSfWZsAAAAAACBQdgAAAAAAAANS////+wAAAAAAID+JAAAAAAAAA00BAAAAAgAAAAsAAAAAZAYEhwAAAABkBgSHAAAAAGQGBIYAAAAAACBQKwAAAAAAAAOdAAAAAGQGBIRPu3srgFy1FVEoeBBWS6f74PhYsJvdbggVRmGyUzFy1DChkVj1pUwK34+3VgYnND8iobyFK4nVa+GszcXb+W0OAAAAAAAcQ44AAAAAAAAAhP////0AAAAAABxBSQAAAAAAAACXAQAAAAIAAAATAAAAAGQGBIcAAAAAZAYEhwAAAABkBgSGAAAAAAAcQ28AAAAAAAAAZgAAAABkBgSE"

+ 101 - 0
target_chains/solana/Cargo.lock

@@ -1104,6 +1104,12 @@ dependencies = [
  "syn 0.15.44",
 ]
 
+[[package]]
+name = "dyn-clone"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60"
+
 [[package]]
 name = "eager"
 version = "0.1.0"
@@ -1486,6 +1492,9 @@ name = "hex"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "hex-literal"
@@ -2337,11 +2346,58 @@ dependencies = [
  "yansi",
 ]
 
+[[package]]
+name = "pyth-sdk"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ff07d7ef3bd98214f9b4fe6a611a69e36b5aad74b18cdbad5150193c1f204"
+dependencies = [
+ "borsh",
+ "borsh-derive",
+ "schemars",
+ "serde",
+]
+
+[[package]]
+name = "pyth-sdk"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5c805ba3dfb5b7ed6a8ffa62ec38391f485a79c7cf6b3b11d3bd44fb0325824"
+dependencies = [
+ "borsh",
+ "borsh-derive",
+ "hex",
+ "schemars",
+ "serde",
+]
+
+[[package]]
+name = "pyth-sdk-solana"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27a648739aa69cab94edd900a0d7ca37d8a789e9c88741b23deec11fab418d16"
+dependencies = [
+ "borsh",
+ "borsh-derive",
+ "bytemuck",
+ "num-derive",
+ "num-traits",
+ "pyth-sdk 0.1.0",
+ "serde",
+ "solana-program",
+ "thiserror",
+]
+
 [[package]]
 name = "pyth-solana-receiver"
 version = "0.1.0"
 dependencies = [
  "anchor-lang",
+ "pyth-sdk 0.5.0",
+ "pyth-sdk-solana",
+ "pyth-wormhole-attester-sdk",
+ "wormhole-core",
+ "wormhole-solana",
 ]
 
 [[package]]
@@ -2352,6 +2408,7 @@ dependencies = [
  "anyhow",
  "base64 0.13.1",
  "clap 3.2.23",
+ "pyth-solana-receiver",
  "shellexpand",
  "solana-client",
  "solana-sdk",
@@ -2359,6 +2416,15 @@ dependencies = [
  "wormhole-solana",
 ]
 
+[[package]]
+name = "pyth-wormhole-attester-sdk"
+version = "0.1.2"
+dependencies = [
+ "hex",
+ "pyth-sdk 0.5.0",
+ "serde",
+]
+
 [[package]]
 name = "qstring"
 version = "0.7.2"
@@ -2760,6 +2826,30 @@ dependencies = [
  "windows-sys",
 ]
 
+[[package]]
+name = "schemars"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307"
+dependencies = [
+ "dyn-clone",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9"
+dependencies = [
+ "proc-macro2 1.0.50",
+ "quote 1.0.23",
+ "serde_derive_internals",
+ "syn 1.0.107",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -2840,6 +2930,17 @@ dependencies = [
  "syn 1.0.107",
 ]
 
+[[package]]
+name = "serde_derive_internals"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
+dependencies = [
+ "proc-macro2 1.0.50",
+ "quote 1.0.23",
+ "syn 1.0.107",
+]
+
 [[package]]
 name = "serde_json"
 version = "1.0.91"

+ 0 - 4
target_chains/solana/Makefile

@@ -1,4 +0,0 @@
-ccli:
-	cd cli; cargo build
-post_vaa:
-	cd cli; cargo run post-price-vaa -v AQAAAAABAGL2NH+QGX4mcTw+F2uFuH8lC62485K1o/vxftbGKL1gauMUW6Lzb18JJLF4SJtkc9H5MnqC+Mpai+bDoj2Ztw8BY9p6/AAAAAAAAfNGGVrALzfWDU24/6bvdMsb41UAR1Q6Sp7prPTXhpewAAAAAAeb7UQBUDJXSAADAAAAAQIABQCVQxzC/Q70r0vHyF//ri9j1Rsm0WIXloLRSa5hmxIhwAv8MJRn3vpLGYxrW9WcCNtLnfsn3bzDLzFWDyF7T/j8KwAAACRAHYigAAAAAAPYMSD////4AAAAJCpaeTgAAAAAAqjStgEAAAABAAAAFQAAAABj2nr8AAAAAGPaevwAAAAAY9p6/AAAACRAHYigAAAAAAPYMSD0Kq+ITHsUVIlBcL4Krx2zm0t406VqJ/1JvYs57ywz12UQcfjHqyMhtr3TvHm5SlCEGpKm4GX547i5kmqPtaXRAAAAJO/rVQAAAAAABQG9AP////gAAAAk30Wg6AAAAAAFg7x3AQAAAAEAAAALAAAAAGPaevwAAAAAY9p6/AAAAABj2nr8AAAAJO/rVQAAAAAABQG9ABgB6wOAOvAkRSPuKobD8nsSar6JBNtLRagq21/iFwi0yoC6bcMuCNBvGqiGAR7tHXfHe+nrdhzBDXK30KL9V6YAAAAk8MYnegAAAAAD1cL6////+AAAACTf8vQAAAAAAANX/dkBAAAAAgAAAB4AAAAAY9p6/AAAAABj2nr8AAAAAGPaevwAAAAk8MYnegAAAAAD1cL6fd8Ngq9THwrxCdXpzp7Ce6nwDp7oq3HJEq//oW1xWDa3q9Jadt2v/fhHIk8DGYzLknI/kLJCnPM/Duy5bjUqhgAAACRKX0MAAAAAAIiMq2D////4AAAAJGpZPYAAAAAAf92Z9AEAAAACAAAADgAAAABj2nr8AAAAAGPaevwAAAAAY9p6/AAAACRKX0MAAAAAAIP9X0DVpcLzDga9bzjgHCxMjN18p8HBLUenM25Fn8bbQXG65mD9YbLZDrpH8oFQWoiGm2YTPZ3FjyA7AZ9apH8bOTQ+AAAAAAAAAAAAAAAAAAAAAP////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAGPaevwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==

+ 29 - 3
target_chains/solana/README.md

@@ -1,5 +1,31 @@
-# pythnet-solana-receiver
+# Solana program for receiving price VAA from Pythnet
 
-To compile the cli, run `make ccli`.
+The program under `cli` receives a VAA string from the shell, verifies the VAA with wormhole, posts the VAA on solana and then invokes the receiver program under `programs`.
+The receiver program verifies that the VAA comes from wormhole (through the `owner` function in `state.rs`) and deserializes the price information (in `decode_posted_vaa` function of `lib.rs`).
 
-To test post_vaa of the cli, run `make post_vaa`.
+```shell
+# Generate the program key
+# and use the key to replace the following two places
+#     "pyth_solana_receiver" in Anchor.toml
+#     "declare_id!()" in programs/solana-receiver/src/lib.rs
+> solana-keygen new -o program_address.json
+
+# Build and deploy the receiver program
+> anchor build
+> anchor run deploy
+
+# Build and test the cli program
+> anchor run cli_build
+> anchor run cli_test
+# Example output
+...
+[1/5] Decode the VAA
+[2/5] Get wormhole guardian set configuration
+[3/5] Invoke wormhole on solana to verify the VAA
+Transaction successful : 3VbrqQBCf1RsNLxrcvxN3aTb5fZRht4n8XDUVPM8NKniRmo84NZQUu5iFw5groAQgQYox3YCqaMjKc2WTpPU1yqV
+[4/5] Post the VAA data onto a solana account
+Transaction successful : 3L1vxzSHQv6B6TwtoMv2Y6m7vFGz3hzqApGHEhHSLA9Jn5dNKeWRWKv29UDPDc3vsgt1mYueamUPPt6bHGGEkbxh
+[5/5] Receive and deserialize the VAA on solana
+Receiver program ID is 5dXnHcDXdXaiEp9QgknCDPsEhJStSqZqJ4ATirWfEqeY
+Transaction successful : u5y9Hqc18so3BnjSUvZkLZR4mvA8zkiBgzGKHSEYyWkHQhH3uQatM7xWf4kdrhjZFVGbfBLdR8RJJUmuf28ePtG
+```

+ 1 - 0
target_chains/solana/cli/Cargo.toml

@@ -11,5 +11,6 @@ solana-sdk = "1.10.31"
 solana-client = "1.10.31"
 anchor-client = "0.26.0"
 clap = {version ="3.2.22", features = ["derive"]}
+pyth-solana-receiver = {path = "../programs/solana-receiver"}
 wormhole-core = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}
 wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}

+ 3 - 13
target_chains/solana/cli/src/cli.rs

@@ -1,4 +1,3 @@
-//! CLI options
 use {
     clap::{
         Parser,
@@ -18,26 +17,17 @@ pub struct Cli {
 
 #[derive(Subcommand, Debug)]
 pub enum Action {
-    #[clap(about = "Verify and post the price VAA on solana")]
-    PostPriceVAA {
+    #[clap(about = "Verify, post and receive the price VAA on solana")]
+    PostAndReceiveVAA {
         #[clap(short = 'v', long,
                help = "Price VAA from Pythnet")]
         vaa:     String,
         #[clap(
             short = 'k', long,
             default_value = "~/.config/solana/id.json",
-            help = "Keypair of the transaction's funder"
+            help = "Keypair of the payer of transactions"
         )]
         keypair: String,
     },
 
-    #[clap(about = "Invoke the on-chain contract decoding the VAA")]
-    InvokePriceReceiver {
-        #[clap(
-            short = 'k', long,
-            default_value = "~/.config/solana/id.json",
-            help = "Keypair of the transaction's funder"
-        )]
-        keypair: String,
-    },
 }

+ 42 - 18
target_chains/solana/cli/src/main.rs

@@ -7,10 +7,8 @@ use {
     },
     clap::Parser,
     anyhow::Result,
-    std::str::FromStr,
 
     solana_sdk::{
-        pubkey::Pubkey,
         signature::{
             read_keypair_file,
             Keypair,
@@ -22,36 +20,47 @@ use {
 
     wormhole::VAA,
     wormhole_solana::{
-        Account,
-        GuardianSet,
-        Config as WormholeConfig,
         instructions::{
             post_vaa,
             verify_signatures_txs,
             PostVAAData,
         },
+        Account,
+        GuardianSet,
+        Config as WormholeConfig,
+        VAA as WormholeSolanaVAA,
+    },
+
+    pyth_solana_receiver::{
+        ID,
+        state::AnchorVaa,
+        accounts::DecodePostedVaa,
+    },
+
+    anchor_client::anchor_lang::{
+        Owner,
+        ToAccountMetas,
+        InstructionData,
+        AnchorDeserialize,
     },
 
     solana_client::rpc_client::RpcClient,
-    anchor_client::anchor_lang::AnchorDeserialize,
 };
 
 fn main() -> Result<()> {
     let cli = Cli::parse();
 
     match cli.action {
-        Action::PostPriceVAA { vaa, keypair } => {
-            println!("PostPriceVAA is invoked with vaa\"{}\"", vaa);
-            // Hard-coded strings
+        Action::PostAndReceiveVAA { vaa, keypair } => {
+            let wormhole = AnchorVaa::owner();
             let rpc_client = RpcClient::new("https://api.devnet.solana.com");
-            // Is RpcClient::new_with_commitment necessary?
-            let wormhole = Pubkey::from_str("3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5").unwrap();
 
-            println!("Decode the VAA");
+            println!("[1/5] Decode the VAA");
             let vaa_bytes: Vec<u8> = base64::decode(vaa)?;
             let vaa = VAA::from_bytes(vaa_bytes.clone())?;
+            let posted_vaa_key = WormholeSolanaVAA::key(&wormhole, vaa.digest().unwrap().hash);
 
-            println!("Get wormhole guardian set configuration");
+            println!("[2/5] Get wormhole guardian set configuration");
             let wormhole_config = WormholeConfig::key(&wormhole, ());
             let wormhole_config_data =
                 WormholeConfig::try_from_slice(&rpc_client.get_account_data(&wormhole_config)?)?;
@@ -60,7 +69,7 @@ fn main() -> Result<()> {
             let guardian_set_data =
                 GuardianSet::try_from_slice(&rpc_client.get_account_data(&guardian_set)?)?;
 
-            println!("Invoke wormhole on solana to verify the VAA");
+            println!("[3/5] Invoke wormhole on solana to verify the VAA");
             let payer =
                 read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");
             let signature_set_keypair = Keypair::new();
@@ -77,7 +86,7 @@ fn main() -> Result<()> {
                 process_transaction(&rpc_client, tx, &vec![&payer, &signature_set_keypair])?;
             }
 
-            println!("Upload the VAA data to a solana account");
+            println!("[4/5] Post the VAA data onto a solana account");
             let post_vaa_data = PostVAAData {
                 version:            vaa.version,
                 guardian_set_index: vaa.guardian_set_index,
@@ -100,10 +109,23 @@ fn main() -> Result<()> {
                 )?],
                 &vec![&payer],
             )?;
-        }
 
-        Action::InvokePriceReceiver { keypair } => {
-            println!("TBD, keypair={}", keypair);
+            println!("[5/5] Receive and deserialize the VAA on solana");
+            let account_metas = DecodePostedVaa::populate(&payer.pubkey(), &posted_vaa_key)
+                .to_account_metas(None);
+
+            println!("Receiver program ID is {}", ID);
+            let invoke_receiver_instruction = Instruction {
+                program_id: ID,
+                accounts:   account_metas,
+                data:       pyth_solana_receiver::instruction::DecodePostedVaa.data(),
+            };
+
+            process_transaction(
+                &rpc_client,
+                vec![invoke_receiver_instruction],
+                &vec![&payer],
+            )?;
         }
     }
 
@@ -118,8 +140,10 @@ pub fn process_transaction(
     let mut transaction =
         Transaction::new_with_payer(instructions.as_slice(), Some(&signers[0].pubkey()));
     transaction.sign(signers, rpc_client.get_latest_blockhash()?);
+
     let transaction_signature =
         rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
     println!("Transaction successful : {transaction_signature:?}");
+
     Ok(())
 }

+ 6 - 0
target_chains/solana/program_address.json

@@ -0,0 +1,6 @@
+[
+  60, 149, 65, 82, 222, 44, 5, 183, 190, 133, 174, 157, 116, 169, 223, 187, 176,
+  116, 180, 249, 122, 201, 107, 232, 238, 112, 238, 241, 8, 143, 97, 41, 12, 74,
+  160, 14, 126, 203, 129, 160, 107, 74, 220, 154, 139, 58, 58, 125, 87, 41, 190,
+  231, 142, 176, 98, 77, 107, 78, 193, 20, 114, 34, 71, 146
+]

+ 7 - 0
target_chains/solana/programs/solana-receiver/Cargo.toml

@@ -17,3 +17,10 @@ default = []
 
 [dependencies]
 anchor-lang = "0.26.0"
+wormhole-core = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}
+wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}
+pyth-wormhole-attester-sdk = { path = "../../../../wormhole_attester/sdk/rust" }
+
+[dev-dependencies]
+pyth-sdk = "0.5.0"
+pyth-sdk-solana = "0.1.0"

+ 11 - 0
target_chains/solana/programs/solana-receiver/src/error.rs

@@ -0,0 +1,11 @@
+use anchor_lang::prelude::*;
+
+#[error_code]
+pub enum ReceiverError {
+    #[msg("The emitter of the VAA is not Solana or Pythnet.")]
+    EmitterChainNotSolanaOrPythnet,
+    #[msg("The posted VAA has wrong magic number.")]
+    PostedVaaHeaderWrongMagicNumber,
+    #[msg("An error occured when deserializeing the VAA.")]
+    DeserializeVAAFailed,
+}

+ 87 - 5
target_chains/solana/programs/solana-receiver/src/lib.rs

@@ -1,15 +1,97 @@
-use anchor_lang::prelude::*;
+pub mod error;
+pub mod state;
 
-declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+use {
+    wormhole::Chain::{
+        self,
+        Solana,
+        Pythnet,
+    },
+    state::AnchorVaa,
+    anchor_lang::prelude::*,
+    pyth_wormhole_attester_sdk::BatchPriceAttestation,
+};
+
+use crate::error::ReceiverError::*;
+
+declare_id!("pythKkWXoywbvTQVcWrNDz5ENvWteF7tem7xzW52NBK");
 
 #[program]
-pub mod solana_receiver {
+pub mod pyth_solana_receiver {
     use super::*;
 
-    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
+    pub fn decode_posted_vaa(ctx: Context<DecodePostedVaa>) -> Result<()> {
+        let posted_vaa = &ctx.accounts.posted_vaa.payload;
+        let batch: BatchPriceAttestation =
+            BatchPriceAttestation::deserialize(posted_vaa.as_slice())
+            .map_err(|_| DeserializeVAAFailed)?;
+
+        msg!("There are {} attestations in this batch.", batch.price_attestations.len());
+
+        for attestation in batch.price_attestations {
+            msg!("product_id: {}", attestation.product_id);
+            msg!("price_id: {}", attestation.price_id);
+            msg!("price: {}", attestation.price);
+            msg!("conf: {}", attestation.conf);
+            msg!("ema_price: {}", attestation.ema_price);
+            msg!("ema_conf: {}", attestation.ema_conf);
+            msg!("num_publishers: {}", attestation.num_publishers);
+            msg!("publish_time: {}", attestation.publish_time);
+            msg!("attestation_time: {}", attestation.attestation_time);
+        }
+
         Ok(())
     }
 }
 
 #[derive(Accounts)]
-pub struct Initialize {}
+pub struct DecodePostedVaa<'info> {
+    #[account(mut)]
+    pub payer:          Signer<'info>,
+    #[account(constraint = (Chain::from(posted_vaa.emitter_chain) == Solana || Chain::from(posted_vaa.emitter_chain) == Pythnet) @ EmitterChainNotSolanaOrPythnet, constraint = (&posted_vaa.magic == b"vaa" || &posted_vaa.magic == b"msg" || &posted_vaa.magic == b"msu") @PostedVaaHeaderWrongMagicNumber)]
+    pub posted_vaa:     Account<'info, AnchorVaa>,
+}
+
+impl crate::accounts::DecodePostedVaa {
+    pub fn populate(
+        payer: &Pubkey,
+        posted_vaa: &Pubkey,
+    ) -> Self {
+        crate::accounts::DecodePostedVaa {
+            payer: *payer,
+            posted_vaa: *posted_vaa,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use pyth_sdk::Identifier;
+    use pyth_wormhole_attester_sdk::PriceStatus;
+    use pyth_wormhole_attester_sdk::PriceAttestation;
+
+    #[test]
+    fn mock_attestation() {
+        // TODO: create a VAA with this attestation as payload
+        // and then invoke DecodePostedVaa
+
+        let _attestation = PriceAttestation {
+            product_id:                 Identifier::new([18u8; 32]),
+            price_id:                   Identifier::new([150u8; 32]),
+            price:                      0x2bad2feed7,
+            conf:                       101,
+            ema_price:                  -42,
+            ema_conf:                   42,
+            expo:                       -3,
+            status:                     PriceStatus::Trading,
+            num_publishers:             123212u32,
+            max_num_publishers:         321232u32,
+            attestation_time:           (0xdeadbeeffadeu64) as i64,
+            publish_time:               0xdadebeefi64,
+            prev_publish_time:          0xdeadbabei64,
+            prev_price:                 0xdeadfacebeefi64,
+            prev_conf:                  0xbadbadbeefu64,
+            last_attested_publish_time: (0xdeadbeeffadedeafu64) as i64,
+        };
+    }
+}

+ 51 - 0
target_chains/solana/programs/solana-receiver/src/state.rs

@@ -0,0 +1,51 @@
+use {
+    std::{
+        io::Write,
+        ops::Deref,
+        str::FromStr,
+    },
+    wormhole_solana::VAA,
+    anchor_lang::prelude::*,
+};
+
+// The current chain's wormhole bridge owns the VAA accounts
+impl Owner for AnchorVaa {
+    fn owner() -> Pubkey {
+        // wormhole address on solana devnet
+        Pubkey::from_str("3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5").unwrap()
+    }
+}
+
+impl AccountDeserialize for AnchorVaa {
+    // Manual implementation because this account does not have an anchor discriminator
+    fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
+        Self::try_deserialize_unchecked(buf)
+    }
+
+    // Manual implementation because this account does not have an anchor discriminator
+    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
+        AnchorDeserialize::deserialize(buf)
+            .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
+    }
+}
+
+impl AccountSerialize for AnchorVaa {
+    // Make this fail, this is readonly VAA it should never be serialized by this program
+    fn try_serialize<W: Write>(&self, _writer: &mut W) -> Result<()> {
+        Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into())
+    }
+}
+
+impl Deref for AnchorVaa {
+    type Target = VAA;
+
+    fn deref(&self) -> &Self::Target {
+        &self.vaa
+    }
+}
+
+#[derive(Clone, AnchorDeserialize, AnchorSerialize)]
+pub struct AnchorVaa {
+    pub magic: [u8; 3],
+    pub vaa:   VAA,
+}