Browse Source

Use official wormhole, introducing VerificationLevel (#1212)

* Do it

* Do it

* Cleanup deps

* Delete this
guibescos 1 year ago
parent
commit
c1cc7127c2

+ 16 - 4
target_chains/solana/Cargo.lock

@@ -3021,14 +3021,12 @@ dependencies = [
  "pyth-sdk-solana",
  "pythnet-sdk",
  "rand 0.8.5",
- "serde_wormhole",
  "solana-program",
  "solana-program-test",
  "solana-sdk",
  "tokio",
- "wormhole-core-bridge-solana",
+ "wormhole-core-bridge-solana 0.0.1-alpha.3 (git+https://github.com/wormhole-foundation/wormhole?branch=wen/solana-rewrite)",
  "wormhole-raw-vaas",
- "wormhole-sdk",
 ]
 
 [[package]]
@@ -3047,7 +3045,7 @@ dependencies = [
  "shellexpand",
  "solana-client",
  "solana-sdk",
- "wormhole-core-bridge-solana",
+ "wormhole-core-bridge-solana 0.0.1-alpha.3 (git+https://github.com/guibescos/wormhole?branch=variable-sigs)",
  "wormhole-sdk",
  "wormhole-solana",
 ]
@@ -6154,6 +6152,20 @@ dependencies = [
  "wormhole-raw-vaas",
 ]
 
+[[package]]
+name = "wormhole-core-bridge-solana"
+version = "0.0.1-alpha.3"
+source = "git+https://github.com/wormhole-foundation/wormhole?branch=wen/solana-rewrite#1020390d1bf64f6fe2f3a4845df686ff2c5fea70"
+dependencies = [
+ "anchor-lang",
+ "cfg-if",
+ "hex",
+ "ruint",
+ "solana-program",
+ "wormhole-io",
+ "wormhole-raw-vaas",
+]
+
 [[package]]
 name = "wormhole-io"
 version = "0.1.1"

+ 1 - 0
target_chains/solana/cli/src/main.rs

@@ -186,6 +186,7 @@ fn main() -> Result<()> {
                         wormhole,
                         valid_data_sources: vec![DataSource { chain, emitter }],
                         single_update_fee_in_lamports: fee,
+                        minimum_signatures: 5,
                     },
                 }
                 .data(),

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

@@ -20,10 +20,8 @@ anchor-lang = "0.28.0"
 pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk", version = "2.0.0" }
 solana-program = "1.16.20"
 byteorder = "1.4.3"
-wormhole-core-bridge-solana = {git = "https://github.com/guibescos/wormhole", branch = "variable-sigs"}
+wormhole-core-bridge-solana = {git = "https://github.com/wormhole-foundation/wormhole", branch = "wen/solana-rewrite", features = ["cpi"]}
 wormhole-raw-vaas = {version = "0.0.1-alpha.1", features = ["ruint", "on-chain"], default-features = false }
-wormhole-sdk = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1" }
-serde_wormhole = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1"}
 
 [dev-dependencies]
 pyth-sdk = "0.8.0"

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

@@ -28,6 +28,8 @@ pub enum ReceiverError {
     NonexistentGovernanceAuthorityTransferRequest,
     #[msg("Funds are insufficient to pay the receiving fee")]
     InsufficientFunds,
+    #[msg("The number of guardian signatures is below the minimum")]
+    InsufficientGuardianSignatures,
     // Wormhole errors
     #[msg("Invalid VAA version")]
     InvalidVaaVersion,

+ 40 - 27
target_chains/solana/programs/pyth-solana-receiver/src/lib.rs

@@ -17,7 +17,6 @@ use {
             },
         },
     },
-    serde_wormhole::RawMessage,
     solana_program::{
         keccak,
         program_memory::sol_memcpy,
@@ -29,23 +28,22 @@ use {
             Config,
             DataSource,
         },
-        price_update::PriceUpdateV1,
+        price_update::{
+            PriceUpdateV1,
+            VerificationLevel,
+        },
     },
     wormhole_core_bridge_solana::{
-        sdk::legacy::AccountVariant,
-        state::{
-            EncodedVaa,
-            GuardianSet,
+        sdk::{
+            legacy::AccountVariant,
+            VaaAccount,
         },
+        state::GuardianSet,
     },
     wormhole_raw_vaas::{
         GuardianSetSig,
         Vaa,
     },
-    wormhole_sdk::vaa::{
-        Body,
-        Header,
-    },
 };
 
 pub mod error;
@@ -104,6 +102,11 @@ pub mod pyth_solana_receiver {
         Ok(())
     }
 
+    pub fn set_minimum_signatures(ctx: Context<Governance>, minimum_signatures: u8) -> Result<()> {
+        let config = &mut ctx.accounts.config;
+        config.minimum_signatures = minimum_signatures;
+        Ok(())
+    }
 
     /// Post a price update using a VAA and a MerklePriceUpdate.
     /// This function allows you to post a price update in a single transaction.
@@ -126,7 +129,6 @@ pub mod pyth_solana_receiver {
             ReceiverError::GuardianSetMismatch
         );
 
-        // Do we have enough signatures for quorum?
         let guardian_keys = &guardian_set.keys;
 
         // Generate the same message hash (using keccak) that the Guardians used to generate their
@@ -159,10 +161,16 @@ pub mod pyth_solana_receiver {
         let treasury = &ctx.accounts.treasury;
         let price_update_account = &mut ctx.accounts.price_update_account;
 
+        require_gte!(
+            vaa.signature_count(),
+            config.minimum_signatures,
+            ReceiverError::InsufficientGuardianSignatures
+        );
+
         let vaa_components = VaaComponents {
-            verified_signatures: vaa.signature_count(),
-            emitter_address:     vaa.body().emitter_address(),
-            emitter_chain:       vaa.body().emitter_chain(),
+            verification_level: VerificationLevel::Partial(vaa.signature_count()),
+            emitter_address:    vaa.body().emitter_address(),
+            emitter_chain:      vaa.body().emitter_chain(),
         };
 
         post_price_update_from_vaa(
@@ -185,18 +193,15 @@ pub mod pyth_solana_receiver {
     pub fn post_updates(ctx: Context<PostUpdates>, price_update: MerklePriceUpdate) -> Result<()> {
         let config = &ctx.accounts.config;
         let payer: &Signer<'_> = &ctx.accounts.payer;
-        let encoded_vaa = &ctx.accounts.encoded_vaa;
+        let encoded_vaa = VaaAccount::load(&ctx.accounts.encoded_vaa)?;
         let treasury: &AccountInfo<'_> = &ctx.accounts.treasury;
         let price_update_account: &mut Account<'_, PriceUpdateV1> =
             &mut ctx.accounts.price_update_account;
 
-        let (_, body): (Header, Body<&RawMessage>) =
-            serde_wormhole::from_slice(&encoded_vaa.buf).unwrap();
-
         let vaa_components = VaaComponents {
-            verified_signatures: encoded_vaa.header.verified_signatures,
-            emitter_address:     body.emitter_address.0,
-            emitter_chain:       body.emitter_chain.into(),
+            verification_level: VerificationLevel::Full,
+            emitter_address:    encoded_vaa.try_emitter_address()?,
+            emitter_chain:      encoded_vaa.try_emitter_chain()?,
         };
 
         post_price_update_from_vaa(
@@ -205,7 +210,7 @@ pub mod pyth_solana_receiver {
             treasury,
             price_update_account,
             &vaa_components,
-            body.payload,
+            encoded_vaa.try_payload()?.as_ref(),
             &price_update,
         )?;
 
@@ -253,7 +258,8 @@ pub struct PostUpdates<'info> {
     #[account(mut)]
     pub payer:                Signer<'info>,
     #[account(owner = config.wormhole)]
-    pub encoded_vaa:          Account<'info, EncodedVaa>,
+    /// CHECK: We aren't deserializing the VAA here but later with VaaAccount::load, which is the recommended way
+    pub encoded_vaa:          AccountInfo<'info>,
     #[account(seeds = [CONFIG_SEED.as_ref()], bump)]
     pub config:               Account<'info, Config>,
     #[account(seeds = [TREASURY_SEED.as_ref()], bump)]
@@ -351,10 +357,17 @@ impl crate::accounts::PostUpdates {
     }
 }
 
+impl crate::accounts::Governance {
+    pub fn populate(payer: Pubkey) -> Self {
+        let config = Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &crate::ID).0;
+        crate::accounts::Governance { payer, config }
+    }
+}
+
 struct VaaComponents {
-    verified_signatures: u8,
-    emitter_address:     [u8; 32],
-    emitter_chain:       u16,
+    verification_level: VerificationLevel,
+    emitter_address:    [u8; 32],
+    emitter_chain:      u16,
 }
 
 fn post_price_update_from_vaa<'info>(
@@ -410,7 +423,7 @@ fn post_price_update_from_vaa<'info>(
     match message {
         Message::PriceFeedMessage(price_feed_message) => {
             price_update_account.write_authority = payer.key();
-            price_update_account.verified_signatures = vaa_components.verified_signatures;
+            price_update_account.verification_level = vaa_components.verification_level;
             price_update_account.price_message = price_feed_message;
         }
         Message::TwapMessage(_) => {

+ 3 - 1
target_chains/solana/programs/pyth-solana-receiver/src/state/config.rs

@@ -10,6 +10,7 @@ pub struct Config {
     pub wormhole:                      Pubkey,         // The address of the wormhole receiver
     pub valid_data_sources:            Vec<DataSource>, // The list of valid data sources for oracle price updates
     pub single_update_fee_in_lamports: u64, // The fee in lamports for a single price update
+    pub minimum_signatures:            u8, // The minimum number of signatures required to accept a VAA
 }
 
 #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
@@ -51,11 +52,12 @@ pub mod tests {
                 },
             ],
             single_update_fee_in_lamports: 0,
+            minimum_signatures:            0,
         };
 
         assert_eq!(
             test_config.try_to_vec().unwrap().len(),
-            32 + 1 + 32 + 32 + 4 + 1 + 33 + 1 + 33 + 8
+            32 + 1 + 32 + 32 + 4 + 1 + 33 + 1 + 33 + 8 + 1
         );
         assert!(
             Config::discriminator().len() + test_config.try_to_vec().unwrap().len() <= Config::LEN

+ 36 - 5
target_chains/solana/programs/pyth-solana-receiver/src/state/price_update.rs

@@ -1,16 +1,47 @@
 use {
-    anchor_lang::prelude::*,
+    anchor_lang::prelude::{
+        borsh::BorshSchema,
+        *,
+    },
     pythnet_sdk::messages::PriceFeedMessage,
     solana_program::pubkey::Pubkey,
 };
 
+
+/**
+ * This enum represents how many guardian signatures were checked for a Pythnet price update
+ * If full, guardian quorum has been attained
+ * If partial, at least config.minimum signatures have been verified, but in the case config.minimum_signatures changes in the future we also include the number of signatures that were checked */
+#[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, PartialEq, BorshSchema)]
+pub enum VerificationLevel {
+    Partial(u8),
+    Full,
+}
 #[account]
+#[derive(BorshSchema)]
 pub struct PriceUpdateV1 {
-    pub write_authority:     Pubkey, // This write authority can close this account
-    pub verified_signatures: u8, // The number of wormhole signatures that were verified by the wormhole receiver
-    pub price_message:       PriceFeedMessage,
+    pub write_authority:    Pubkey, // This write authority can close this account
+    pub verification_level: VerificationLevel, // Whether all the guardian signatures have been checked, and if not, how many have been checked
+    pub price_message:      PriceFeedMessage,
 }
 
 impl PriceUpdateV1 {
-    pub const LEN: usize = 8 + 32 + 1 + 32 + 8 + 8 + 4 + 8 + 8 + 8 + 8;
+    pub const LEN: usize = 8 + 32 + 2 + 32 + 8 + 8 + 4 + 8 + 8 + 8 + 8;
+}
+
+#[cfg(test)]
+pub mod tests {
+    use {
+        crate::state::price_update::PriceUpdateV1,
+        anchor_lang::Discriminator,
+        solana_program::borsh0_10,
+    };
+
+    #[test]
+    fn check_size() {
+        assert!(
+            PriceUpdateV1::discriminator().len() + borsh0_10::get_packed_len::<PriceUpdateV1>()
+                == PriceUpdateV1::LEN
+        );
+    }
 }