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

[solana] Solana tests 2 (#1255)

* Checkpoint

* Checkpoint

* Cleanup

* Checkpoint, debug

* Go

* update comment

* Add ci

* Update solana workflow

* Rename create_guardians

* Signatures

* Add some randomness

* Add randomness

* More randomness

* Do it

* Fix some clippies

* Change trim signatures
guibescos пре 1 година
родитељ
комит
b0c6497fca

+ 33 - 0
.github/workflows/ci-solana-contract.yml

@@ -0,0 +1,33 @@
+name: Test Solana Contract
+
+on:
+  pull_request:
+    paths:
+      - target_chains/solana/**
+      - pythnet/pythnet_sdk/**
+  push:
+    branches:
+      - main
+    paths:
+      - target_chains/solana/**
+      - pythnet/pythnet_sdk/**
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: target_chains/solana
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install Solana
+        run: |
+          sh -c "$(curl -sSfL https://release.solana.com/v1.16.20/install)"
+          echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
+      - name: Build
+        run: cargo-build-sbf
+      - name: Run tests
+        run: cargo-test-sbf

+ 3 - 1
pythnet/pythnet_sdk/Cargo.toml

@@ -11,7 +11,7 @@ crate-type = ["lib"]
 name = "pythnet_sdk"
 
 [features]
-test-utils = ["dep:wormhole-sdk", "dep:serde_wormhole"]
+test-utils = ["dep:wormhole-sdk", "dep:serde_wormhole", "dep:libsecp256k1", "dep:rand"]
 
 [dependencies]
 bincode = "1.3.1"
@@ -28,6 +28,8 @@ slow_primes = "0.1.14"
 thiserror = "1.0.40"
 serde_wormhole = { git = "https://github.com/wormhole-foundation/wormhole", optional = true, tag="rust-sdk-2024-01-25"}
 wormhole-sdk = { git = "https://github.com/wormhole-foundation/wormhole", optional = true, tag="rust-sdk-2024-01-25"}
+libsecp256k1 = {version ="0.7.1", optional = true}
+rand = {version = "0.8.5", optional = true}
 
 [dev-dependencies]
 base64 = "0.21.0"

+ 78 - 4
pythnet/pythnet_sdk/src/test_utils/mod.rs

@@ -23,8 +23,22 @@ use {
         },
     },
     byteorder::BigEndian,
+    libsecp256k1::{
+        Message as libsecp256k1Message,
+        RecoveryId,
+        SecretKey,
+        Signature,
+    },
+    rand::{
+        seq::SliceRandom,
+        thread_rng,
+    },
     serde_wormhole::RawMessage,
     wormhole_sdk::{
+        vaa::{
+            Body,
+            Header,
+        },
         Address,
         Chain,
         Vaa,
@@ -67,6 +81,18 @@ pub const DEFAULT_VALID_TIME_PERIOD: u64 = 180;
 
 const DEFAULT_SEQUENCE: u64 = 2;
 
+const NUM_GUARDIANS: u8 = 19; // Matches wormhole mainnet
+const DEFAULT_NUM_SIGNATURES: usize = 13; // Matches wormhole mainnet
+
+pub fn dummy_guardians() -> Vec<SecretKey> {
+    let mut result: Vec<SecretKey> = vec![];
+    for i in 0..NUM_GUARDIANS {
+        let mut secret_key_bytes = [0u8; 32];
+        secret_key_bytes[0] = i + 1;
+        result.push(SecretKey::parse(&secret_key_bytes).unwrap());
+    }
+    result
+}
 
 pub fn create_dummy_price_feed_message(value: i64) -> Message {
     let mut dummy_id = [0; 32];
@@ -155,12 +181,60 @@ pub fn create_vaa_from_payload(
     emitter_chain: Chain,
     sequence: u64,
 ) -> Vaa<Box<RawMessage>> {
-    let vaa: Vaa<Box<RawMessage>> = Vaa {
-        emitter_chain: emitter_chain,
-        emitter_address: emitter_address,
+    let guardians = dummy_guardians();
+
+    let body: Body<Box<RawMessage>> = Body {
+        emitter_chain,
+        emitter_address,
         sequence,
         payload: <Box<RawMessage>>::from(payload.to_vec()),
         ..Default::default()
     };
-    vaa
+
+    let digest = libsecp256k1Message::parse_slice(&body.digest().unwrap().secp256k_hash).unwrap();
+
+    let signatures: Vec<(Signature, RecoveryId)> = guardians
+        .iter()
+        .map(|x| libsecp256k1::sign(&digest, &x))
+        .collect();
+    let wormhole_signatures: Vec<wormhole_sdk::vaa::Signature> = signatures
+        .iter()
+        .enumerate()
+        .map(|(i, (x, y))| {
+            let mut signature = [0u8; 65];
+            signature[..64].copy_from_slice(&x.serialize());
+            signature[64] = y.serialize();
+            wormhole_sdk::vaa::Signature {
+                index: i as u8,
+                signature,
+            }
+        })
+        .collect();
+
+    let mut wormhole_signatures_subset: Vec<wormhole_sdk::vaa::Signature> = wormhole_signatures
+        .choose_multiple(&mut thread_rng(), DEFAULT_NUM_SIGNATURES)
+        .cloned()
+        .collect();
+
+    wormhole_signatures_subset.sort_by(|a, b| a.index.cmp(&b.index));
+
+    let header = Header {
+        version: 1,
+        signatures: wormhole_signatures_subset,
+        ..Default::default()
+    };
+
+
+    (header, body).into()
+}
+
+pub fn trim_vaa_signatures(vaa: Vaa<&RawMessage>, n: u8) -> Vaa<&RawMessage> {
+    let mut vaa_copy = vaa.clone();
+    vaa_copy.signatures = vaa
+        .signatures
+        .choose_multiple(&mut thread_rng(), n.into())
+        .cloned()
+        .collect();
+    vaa_copy.signatures.sort_by(|a, b| a.index.cmp(&b.index));
+    vaa_copy
 }

+ 115 - 1
target_chains/cosmwasm/Cargo.lock

@@ -34,6 +34,12 @@ version = "1.0.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
 
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
 [[package]]
 name = "autocfg"
 version = "1.1.0"
@@ -585,6 +591,16 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
 [[package]]
 name = "curve25519-dalek"
 version = "3.2.0"
@@ -981,6 +997,16 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "hmac"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+]
+
 [[package]]
 name = "hmac"
 version = "0.12.1"
@@ -990,6 +1016,17 @@ dependencies = [
  "digest 0.10.6",
 ]
 
+[[package]]
+name = "hmac-drbg"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1"
+dependencies = [
+ "digest 0.9.0",
+ "generic-array",
+ "hmac 0.8.1",
+]
+
 [[package]]
 name = "ident_case"
 version = "1.0.1"
@@ -1106,6 +1143,54 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "libsecp256k1"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1"
+dependencies = [
+ "arrayref",
+ "base64",
+ "digest 0.9.0",
+ "hmac-drbg",
+ "libsecp256k1-core",
+ "libsecp256k1-gen-ecmult",
+ "libsecp256k1-gen-genmult",
+ "rand",
+ "serde",
+ "sha2 0.9.9",
+ "typenum",
+]
+
+[[package]]
+name = "libsecp256k1-core"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451"
+dependencies = [
+ "crunchy 0.2.2",
+ "digest 0.9.0",
+ "subtle",
+]
+
+[[package]]
+name = "libsecp256k1-gen-ecmult"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809"
+dependencies = [
+ "libsecp256k1-core",
+]
+
+[[package]]
+name = "libsecp256k1-gen-genmult"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c"
+dependencies = [
+ "libsecp256k1-core",
+]
+
 [[package]]
 name = "linux-raw-sys"
 version = "0.3.1"
@@ -1368,6 +1453,12 @@ dependencies = [
  "spki",
 ]
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "proc-macro-crate"
 version = "0.1.5"
@@ -1557,6 +1648,8 @@ dependencies = [
  "byteorder",
  "fast-math",
  "hex",
+ "libsecp256k1",
+ "rand",
  "rustc_version",
  "serde",
  "serde_wormhole",
@@ -1575,6 +1668,27 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.5.1"
@@ -1666,7 +1780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
 dependencies = [
  "crypto-bigint",
- "hmac",
+ "hmac 0.12.1",
  "zeroize",
 ]
 

+ 70 - 0
target_chains/near/receiver/Cargo.lock

@@ -1097,6 +1097,27 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "hmac"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "hmac-drbg"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1"
+dependencies = [
+ "digest 0.9.0",
+ "generic-array",
+ "hmac",
+]
+
 [[package]]
 name = "home"
 version = "0.5.4"
@@ -1310,6 +1331,54 @@ version = "0.2.139"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
 
+[[package]]
+name = "libsecp256k1"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1"
+dependencies = [
+ "arrayref",
+ "base64 0.13.1",
+ "digest 0.9.0",
+ "hmac-drbg",
+ "libsecp256k1-core",
+ "libsecp256k1-gen-ecmult",
+ "libsecp256k1-gen-genmult",
+ "rand 0.8.5",
+ "serde",
+ "sha2 0.9.9",
+ "typenum",
+]
+
+[[package]]
+name = "libsecp256k1-core"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451"
+dependencies = [
+ "crunchy",
+ "digest 0.9.0",
+ "subtle",
+]
+
+[[package]]
+name = "libsecp256k1-gen-ecmult"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809"
+dependencies = [
+ "libsecp256k1-core",
+]
+
+[[package]]
+name = "libsecp256k1-gen-genmult"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c"
+dependencies = [
+ "libsecp256k1-core",
+]
+
 [[package]]
 name = "libz-sys"
 version = "1.1.8"
@@ -2237,6 +2306,7 @@ dependencies = [
  "byteorder",
  "fast-math",
  "hex 0.4.3",
+ "libsecp256k1",
  "rustc_version",
  "serde",
  "serde_wormhole",

+ 4 - 0
target_chains/solana/Cargo.lock

@@ -3075,11 +3075,15 @@ dependencies = [
  "byteorder",
  "fast-math",
  "hex",
+ "libsecp256k1 0.7.1",
+ "rand 0.8.5",
  "rustc_version",
  "serde",
+ "serde_wormhole",
  "sha3 0.10.6",
  "slow_primes",
  "thiserror",
+ "wormhole-sdk",
 ]
 
 [[package]]

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

@@ -16,14 +16,11 @@ use {
         Cli,
     },
     pyth_solana_receiver::{
+        sdk::deserialize_accumulator_update_data,
         state::config::DataSource,
         PostUpdatesAtomicParams,
     },
-    pythnet_sdk::wire::v1::{
-        AccumulatorUpdateData,
-        MerklePriceUpdate,
-        Proof,
-    },
+    pythnet_sdk::wire::v1::MerklePriceUpdate,
     serde_wormhole::RawMessage,
     solana_client::{
         rpc_client::RpcClient,
@@ -96,7 +93,8 @@ fn main() -> Result<()> {
             let payer =
                 read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");
 
-            let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(&payload)?;
+            let payload_bytes: Vec<u8> = base64::decode(payload)?;
+            let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(payload_bytes)?;
 
             process_write_encoded_vaa_and_post_price_update(
                 &rpc_client,
@@ -114,7 +112,8 @@ fn main() -> Result<()> {
             let payer =
                 read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");
 
-            let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(&payload)?;
+            let payload_bytes: Vec<u8> = base64::decode(payload)?;
+            let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(payload_bytes)?;
 
             process_post_price_update_atomic(
                 &rpc_client,
@@ -202,17 +201,6 @@ fn main() -> Result<()> {
     Ok(())
 }
 
-pub fn deserialize_accumulator_update_data(
-    payload: &str,
-) -> Result<(Vec<u8>, Vec<MerklePriceUpdate>)> {
-    let payload_bytes: Vec<u8> = base64::decode(payload)?;
-    let accumulator_update_data = AccumulatorUpdateData::try_from_slice(payload_bytes.as_slice())?;
-
-    match accumulator_update_data.proof {
-        Proof::WormholeMerkle { vaa, updates } => return Ok((vaa.as_ref().to_vec(), updates)),
-    }
-}
-
 pub fn process_upgrade_guardian_set(
     rpc_client: &RpcClient,
     vaa: &[u8],

+ 2 - 1
target_chains/solana/programs/pyth-solana-receiver/Cargo.toml

@@ -17,7 +17,7 @@ test-bpf = []
 
 [dependencies]
 anchor-lang = "0.28.0"
-pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk", version = "2.0.0" }
+pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk" }
 solana-program = "1.16.20"
 byteorder = "1.4.3"
 wormhole-core-bridge-solana = {workspace = true}
@@ -36,3 +36,4 @@ lazy_static = "1.4.0"
 program-simulator = { path = "../../program_simulator" }
 wormhole-sdk = { workspace = true }
 serde_wormhole = { workspace = true }
+pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk", features = ["test-utils"] }

+ 61 - 9
target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs

@@ -3,6 +3,7 @@ use {
         accounts,
         instruction,
         state::config::Config,
+        PostUpdatesAtomicParams,
         CONFIG_SEED,
         ID,
         TREASURY_SEED,
@@ -12,7 +13,11 @@ use {
         system_program,
         InstructionData,
     },
-    pythnet_sdk::wire::v1::MerklePriceUpdate,
+    pythnet_sdk::wire::v1::{
+        AccumulatorUpdateData,
+        MerklePriceUpdate,
+        Proof,
+    },
     solana_program::instruction::Instruction,
     wormhole_core_bridge_solana::state::GuardianSet,
 };
@@ -38,14 +43,7 @@ impl accounts::PostUpdatesAtomic {
         let config = get_config_address();
         let treasury = get_treasury_address();
 
-        let guardian_set = Pubkey::find_program_address(
-            &[
-                GuardianSet::SEED_PREFIX,
-                guardian_set_index.to_be_bytes().as_ref(),
-            ],
-            &wormhole_address,
-        )
-        .0;
+        let guardian_set = get_guardian_set_address(wormhole_address, guardian_set_index);
 
         accounts::PostUpdatesAtomic {
             payer,
@@ -111,6 +109,38 @@ impl instruction::PostUpdates {
     }
 }
 
+
+impl instruction::PostUpdatesAtomic {
+    pub fn populate(
+        payer: Pubkey,
+        price_update_account: Pubkey,
+        wormhole_address: Pubkey,
+        guardian_set_index: u32,
+        vaa: Vec<u8>,
+        merkle_price_update: MerklePriceUpdate,
+    ) -> Instruction {
+        let post_update_accounts = accounts::PostUpdatesAtomic::populate(
+            payer,
+            price_update_account,
+            wormhole_address,
+            guardian_set_index,
+        )
+        .to_account_metas(None);
+        Instruction {
+            program_id: ID,
+            accounts:   post_update_accounts,
+            data:       instruction::PostUpdatesAtomic {
+                params: PostUpdatesAtomicParams {
+                    vaa,
+                    merkle_price_update,
+                },
+            }
+            .data(),
+        }
+    }
+}
+
+
 pub fn get_treasury_address() -> Pubkey {
     Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &ID).0
 }
@@ -118,3 +148,25 @@ pub fn get_treasury_address() -> Pubkey {
 pub fn get_config_address() -> Pubkey {
     Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &ID).0
 }
+
+pub fn get_guardian_set_address(wormhole_address: Pubkey, guardian_set_index: u32) -> Pubkey {
+    Pubkey::find_program_address(
+        &[
+            GuardianSet::SEED_PREFIX,
+            guardian_set_index.to_be_bytes().as_ref(),
+        ],
+        &wormhole_address,
+    )
+    .0
+}
+
+pub fn deserialize_accumulator_update_data(
+    accumulator_message: Vec<u8>,
+) -> Result<(Vec<u8>, Vec<MerklePriceUpdate>)> {
+    let accumulator_update_data =
+        AccumulatorUpdateData::try_from_slice(accumulator_message.as_slice()).unwrap();
+
+    match accumulator_update_data.proof {
+        Proof::WormholeMerkle { vaa, updates } => return Ok((vaa.as_ref().to_vec(), updates)),
+    }
+}

+ 62 - 121
target_chains/solana/programs/pyth-solana-receiver/tests/common/mod.rs

@@ -1,10 +1,12 @@
 use {
     anchor_lang::AnchorSerialize,
+    libsecp256k1::PublicKey,
     program_simulator::ProgramSimulator,
     pyth_solana_receiver::{
         instruction::Initialize,
         sdk::{
             get_config_address,
+            get_guardian_set_address,
             get_treasury_address,
         },
         state::config::{
@@ -13,23 +15,13 @@ use {
         },
         ID,
     },
-    pythnet_sdk::{
-        accumulators::{
-            merkle::MerkleTree,
-            Accumulator,
-        },
-        hashers::keccak256_160::Keccak160,
-        messages::{
-            Message,
-            PriceFeedMessage,
-        },
-        wire::{
-            v1::MerklePriceUpdate,
-            PrefixedVec,
-        },
+    pythnet_sdk::test_utils::{
+        dummy_guardians,
+        DEFAULT_DATA_SOURCE,
     },
     serde_wormhole::RawMessage,
     solana_program::{
+        keccak,
         pubkey::Pubkey,
         rent::Rent,
     },
@@ -42,111 +34,38 @@ use {
     wormhole_core_bridge_solana::{
         state::{
             EncodedVaa,
+            GuardianSet,
             Header,
             ProcessingStatus,
         },
         ID as BRIDGE_ID,
     },
-    wormhole_sdk::{
-        Chain,
-        Vaa,
-    },
+    wormhole_sdk::Vaa,
 };
 
-pub fn dummy_receiver_config(data_source: DataSource) -> Config {
+pub const DEFAULT_GUARDIAN_SET_INDEX: u32 = 0;
+
+pub fn default_receiver_config() -> Config {
     Config {
         governance_authority:          Pubkey::new_unique(),
         target_governance_authority:   None,
         wormhole:                      BRIDGE_ID,
         valid_data_sources:            vec![DataSource {
-            chain:   data_source.chain,
-            emitter: data_source.emitter,
+            chain:   DEFAULT_DATA_SOURCE.chain.into(),
+            emitter: Pubkey::from(DEFAULT_DATA_SOURCE.address.0),
         }],
         single_update_fee_in_lamports: 1,
         minimum_signatures:            5,
     }
 }
 
-pub fn dummy_data_source() -> DataSource {
-    let emitter_address = Pubkey::new_unique().to_bytes();
-    let emitter_chain = Chain::Pythnet;
-    DataSource {
-        chain:   emitter_chain.into(),
-        emitter: Pubkey::from(emitter_address),
-    }
-}
-
-pub fn dummy_price_messages() -> Vec<PriceFeedMessage> {
-    vec![
-        PriceFeedMessage {
-            feed_id:           [0u8; 32],
-            price:             1,
-            conf:              2,
-            exponent:          3,
-            publish_time:      4,
-            prev_publish_time: 5,
-            ema_price:         6,
-            ema_conf:          7,
-        },
-        PriceFeedMessage {
-            feed_id:           [8u8; 32],
-            price:             9,
-            conf:              10,
-            exponent:          11,
-            publish_time:      12,
-            prev_publish_time: 13,
-            ema_price:         14,
-            ema_conf:          15,
-        },
-    ]
-}
 
-pub fn dummy_price_updates(
-    price_feed_messages: Vec<PriceFeedMessage>,
-) -> (MerkleTree<Keccak160>, Vec<MerklePriceUpdate>) {
-    let messages = price_feed_messages
-        .iter()
-        .map(|x| Message::PriceFeedMessage(*x))
-        .collect::<Vec<Message>>();
-    let price_feed_message_as_vec: Vec<Vec<u8>> = messages
-        .iter()
-        .map(|x| pythnet_sdk::wire::to_vec::<_, byteorder::BigEndian>(&x).unwrap())
-        .collect();
-
-    let merkle_tree_accumulator =
-        MerkleTree::<Keccak160>::from_set(price_feed_message_as_vec.iter().map(|x| x.as_ref()))
-            .unwrap();
-    let merkle_price_updates: Vec<MerklePriceUpdate> = price_feed_message_as_vec
-        .iter()
-        .map(|x| MerklePriceUpdate {
-            message: PrefixedVec::<u16, u8>::from(x.clone()),
-            proof:   merkle_tree_accumulator.prove(x.as_ref()).unwrap(),
-        })
-        .collect();
-
-    (merkle_tree_accumulator, merkle_price_updates)
+pub struct ProgramTestFixtures {
+    pub program_simulator:     ProgramSimulator,
+    pub encoded_vaa_addresses: Vec<Pubkey>,
 }
 
-
-pub fn build_merkle_root_encoded_vaa(
-    merkle_tree_accumulator: MerkleTree<Keccak160>,
-    data_source: &DataSource,
-) -> Account {
-    let merkle_tree_payload: Vec<u8> = merkle_tree_accumulator.serialize(1, 1);
-
-    let vaa_header: Vaa<Box<RawMessage>> = Vaa {
-        version:            1,
-        guardian_set_index: 0,
-        signatures:         vec![],
-        timestamp:          0,
-        nonce:              0,
-        emitter_chain:      Chain::from(data_source.chain),
-        emitter_address:    wormhole_sdk::Address(data_source.emitter.to_bytes()),
-        sequence:           0,
-        consistency_level:  0,
-        payload:            <Box<RawMessage>>::from(merkle_tree_payload),
-    };
-
+pub fn build_encoded_vaa_account_from_vaa(vaa: Vaa<&RawMessage>) -> Account {
     let encoded_vaa_data = (
         <EncodedVaa as anchor_lang::Discriminator>::DISCRIMINATOR,
         Header {
@@ -154,12 +73,11 @@ pub fn build_merkle_root_encoded_vaa(
             write_authority: Pubkey::new_unique(),
             version:         1,
         },
-        serde_wormhole::to_vec(&vaa_header).unwrap(),
+        serde_wormhole::to_vec(&vaa).unwrap(),
     )
         .try_to_vec()
         .unwrap();
 
-
     Account {
         lamports:   Rent::default().minimum_balance(encoded_vaa_data.len()),
         data:       encoded_vaa_data,
@@ -169,37 +87,61 @@ pub fn build_merkle_root_encoded_vaa(
     }
 }
 
+pub fn build_guardian_set_account() -> Account {
+    let guardian_set = GuardianSet {
+        index:           DEFAULT_GUARDIAN_SET_INDEX,
+        keys:            dummy_guardians()
+            .iter()
+            .map(|x| {
+                let mut result: [u8; 20] = [0u8; 20];
+                result.copy_from_slice(
+                    &keccak::hashv(&[&PublicKey::from_secret_key(x).serialize()[1..]]).0[12..],
+                );
+                result
+            })
+            .collect::<Vec<[u8; 20]>>(),
+        creation_time:   0.into(),
+        expiration_time: 0.into(),
+    };
 
-pub struct ProgramTestFixtures {
-    pub program_simulator:    ProgramSimulator,
-    pub encoded_vaa_address:  Pubkey,
-    pub merkle_price_updates: Vec<MerklePriceUpdate>,
+    let guardian_set_data = (
+        <GuardianSet as anchor_lang::Discriminator>::DISCRIMINATOR,
+        guardian_set,
+    )
+        .try_to_vec()
+        .unwrap();
+
+    Account {
+        lamports:   Rent::default().minimum_balance(guardian_set_data.len()),
+        data:       guardian_set_data,
+        owner:      BRIDGE_ID,
+        executable: false,
+        rent_epoch: 0,
+    }
 }
 /**
  * Setup to test the Pyth Receiver. The return values are a tuple composed of :
  * - The program simulator, which is used to send transactions
- * - The pubkey of an encoded VAA account, which is pre-populated and can be used to test post_updates
- * - A vector of MerklePriceUpdate, corresponding to that VAA
+ * - The pubkeys of the encoded VAA accounts corresponding to the VAAs passed as argument, these accounts are prepopulated and can be used to test post_updates
  */
-pub async fn setup_pyth_receiver(
-    price_feed_messages: Vec<PriceFeedMessage>,
-) -> ProgramTestFixtures {
+pub async fn setup_pyth_receiver(vaas: Vec<Vaa<&RawMessage>>) -> ProgramTestFixtures {
     let mut program_test = ProgramTest::default();
     program_test.add_program("pyth_solana_receiver", ID, None);
 
-    let (merkle_tree_accumulator, merkle_price_updates) = dummy_price_updates(price_feed_messages);
-    let data_source = dummy_data_source();
-    let merkle_root_encoded_vaa =
-        build_merkle_root_encoded_vaa(merkle_tree_accumulator, &data_source);
-
-    let encoded_vaa_address = Pubkey::new_unique();
-    program_test.add_account(encoded_vaa_address, merkle_root_encoded_vaa);
-
+    let mut encoded_vaa_addresses: Vec<Pubkey> = vec![];
+    for vaa in vaas {
+        let encoded_vaa_address = Pubkey::new_unique();
+        encoded_vaa_addresses.push(encoded_vaa_address);
+        program_test.add_account(encoded_vaa_address, build_encoded_vaa_account_from_vaa(vaa));
+    }
+    program_test.add_account(
+        get_guardian_set_address(BRIDGE_ID, DEFAULT_GUARDIAN_SET_INDEX),
+        build_guardian_set_account(),
+    );
 
     let mut program_simulator = ProgramSimulator::start_from_program_test(program_test).await;
 
-    let initial_config = dummy_receiver_config(data_source);
-
+    let initial_config = default_receiver_config();
     let setup_keypair: Keypair = program_simulator.get_funded_keypair().await.unwrap();
 
     program_simulator
@@ -225,7 +167,6 @@ pub async fn setup_pyth_receiver(
 
     ProgramTestFixtures {
         program_simulator,
-        encoded_vaa_address,
-        merkle_price_updates,
+        encoded_vaa_addresses,
     }
 }

+ 25 - 9
target_chains/solana/programs/pyth-solana-receiver/tests/test_initialize.rs → target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs

@@ -1,16 +1,23 @@
 use {
-    crate::common::dummy_price_messages,
     common::{
         setup_pyth_receiver,
         ProgramTestFixtures,
     },
     pyth_solana_receiver::{
         instruction::PostUpdates,
+        sdk::deserialize_accumulator_update_data,
         state::price_update::{
             PriceUpdateV1,
             VerificationLevel,
         },
     },
+    pythnet_sdk::{
+        messages::Message,
+        test_utils::{
+            create_accumulator_message,
+            create_dummy_price_feed_message,
+        },
+    },
     solana_sdk::{
         signature::Keypair,
         signer::Signer,
@@ -22,13 +29,16 @@ mod common;
 
 #[tokio::test]
 async fn test_post_updates() {
-    let dummy_price_messages = dummy_price_messages();
+    let feed_1 = create_dummy_price_feed_message(100);
+    let feed_2 = create_dummy_price_feed_message(200);
+    let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false);
+    let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap();
+
 
     let ProgramTestFixtures {
         mut program_simulator,
-        encoded_vaa_address,
-        merkle_price_updates,
-    } = setup_pyth_receiver(dummy_price_messages.clone()).await;
+        encoded_vaa_addresses,
+    } = setup_pyth_receiver(vec![serde_wormhole::from_slice(&vaa).unwrap()]).await;
 
     let poster = program_simulator.get_funded_keypair().await.unwrap();
     let price_update_keypair = Keypair::new();
@@ -38,7 +48,7 @@ async fn test_post_updates() {
         .process_ix(
             PostUpdates::populate(
                 poster.pubkey(),
-                encoded_vaa_address,
+                encoded_vaa_addresses[0],
                 price_update_keypair.pubkey(),
                 merkle_price_updates[0].clone(),
             ),
@@ -59,14 +69,17 @@ async fn test_post_updates() {
         price_update_account.verification_level,
         VerificationLevel::Full
     );
-    assert_eq!(price_update_account.price_message, dummy_price_messages[0]);
+    assert_eq!(
+        Message::PriceFeedMessage(price_update_account.price_message),
+        feed_1
+    );
 
     // post another update to the same account
     program_simulator
         .process_ix(
             PostUpdates::populate(
                 poster.pubkey(),
-                encoded_vaa_address,
+                encoded_vaa_addresses[0],
                 price_update_keypair.pubkey(),
                 merkle_price_updates[1].clone(),
             ),
@@ -87,5 +100,8 @@ async fn test_post_updates() {
         price_update_account.verification_level,
         VerificationLevel::Full
     );
-    assert_eq!(price_update_account.price_message, dummy_price_messages[1]);
+    assert_eq!(
+        Message::PriceFeedMessage(price_update_account.price_message),
+        feed_2
+    );
 }

+ 118 - 0
target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs

@@ -0,0 +1,118 @@
+use {
+    crate::common::DEFAULT_GUARDIAN_SET_INDEX,
+    common::{
+        setup_pyth_receiver,
+        ProgramTestFixtures,
+    },
+    pyth_solana_receiver::{
+        instruction::PostUpdatesAtomic,
+        sdk::deserialize_accumulator_update_data,
+        state::price_update::{
+            PriceUpdateV1,
+            VerificationLevel,
+        },
+    },
+    pythnet_sdk::{
+        messages::Message,
+        test_utils::{
+            create_accumulator_message,
+            create_dummy_price_feed_message,
+            trim_vaa_signatures,
+        },
+    },
+    solana_sdk::{
+        signature::Keypair,
+        signer::Signer,
+    },
+    wormhole_core_bridge_solana::ID as BRIDGE_ID,
+};
+
+mod common;
+
+
+#[tokio::test]
+async fn test_post_updates_atomic() {
+    let feed_1 = create_dummy_price_feed_message(100);
+    let feed_2 = create_dummy_price_feed_message(200);
+    let message = create_accumulator_message(&[feed_1, feed_2], &[feed_1, feed_2], false);
+    let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(message).unwrap();
+    let vaa = serde_wormhole::to_vec(&trim_vaa_signatures(
+        serde_wormhole::from_slice(&vaa).unwrap(),
+        5,
+    ))
+    .unwrap();
+
+    let ProgramTestFixtures {
+        mut program_simulator,
+        encoded_vaa_addresses: _,
+    } = setup_pyth_receiver(vec![]).await;
+
+    let poster = program_simulator.get_funded_keypair().await.unwrap();
+    let price_update_keypair = Keypair::new();
+
+    // post one update atomically
+    program_simulator
+        .process_ix(
+            PostUpdatesAtomic::populate(
+                poster.pubkey(),
+                price_update_keypair.pubkey(),
+                BRIDGE_ID,
+                DEFAULT_GUARDIAN_SET_INDEX,
+                vaa.clone(),
+                merkle_price_updates[0].clone(),
+            ),
+            &vec![&poster, &price_update_keypair],
+            None,
+        )
+        .await
+        .unwrap();
+
+
+    let mut price_update_account = program_simulator
+        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .await
+        .unwrap();
+
+    assert_eq!(price_update_account.write_authority, poster.pubkey());
+    assert_eq!(
+        price_update_account.verification_level,
+        VerificationLevel::Partial(5)
+    );
+    assert_eq!(
+        Message::PriceFeedMessage(price_update_account.price_message),
+        feed_1
+    );
+
+    // post another update to the same account
+    program_simulator
+        .process_ix(
+            PostUpdatesAtomic::populate(
+                poster.pubkey(),
+                price_update_keypair.pubkey(),
+                BRIDGE_ID,
+                DEFAULT_GUARDIAN_SET_INDEX,
+                vaa.clone(),
+                merkle_price_updates[1].clone(),
+            ),
+            &vec![&poster, &price_update_keypair],
+            None,
+        )
+        .await
+        .unwrap();
+
+
+    price_update_account = program_simulator
+        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .await
+        .unwrap();
+
+    assert_eq!(price_update_account.write_authority, poster.pubkey());
+    assert_eq!(
+        price_update_account.verification_level,
+        VerificationLevel::Partial(5)
+    );
+    assert_eq!(
+        Message::PriceFeedMessage(price_update_account.price_message),
+        feed_2
+    );
+}