Ver código fonte

feat(lazer): add fees to solana contract (#2146)

* refactor(lazer): change verify_message to a contract call, add test

* feat(lazer): add treasury and fees to solana contract

* feat(lazer): make treasury configurable, add migration

* feat(lazer): add extra space to contract's pda

* fix(lazer): keep old names for legacy structs for compatibility

* refactor(lazer): keep good names for new types and methods
Pavel Strakhov 11 meses atrás
pai
commit
9c49daf3d5

Diferenças do arquivo suprimidas por serem muito extensas
+ 4454 - 636
lazer/Cargo.lock


+ 0 - 1
lazer/Cargo.toml

@@ -3,7 +3,6 @@ resolver = "2"
 members = [
   "sdk/rust/protocol",
   "contracts/solana/programs/pyth-lazer-solana-contract",
-  "sdk/solana",
 ]
 
 # TODO: only for solana programs

+ 13 - 2
lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml

@@ -1,8 +1,8 @@
 [package]
 name = "pyth-lazer-solana-contract"
-version = "0.1.0"
+version = "0.2.0"
 edition = "2021"
-description = "Pyth Lazer Solana contract."
+description = "Pyth Lazer Solana contract and SDK."
 license = "Apache-2.0"
 repository = "https://github.com/pyth-network/pyth-crosschain"
 
@@ -19,4 +19,15 @@ no-log-ix-name = []
 idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
+pyth-lazer-protocol = { version = "0.1.0", path = "../../../../sdk/rust/protocol" }
+
 anchor-lang = "0.30.1"
+bytemuck = "1.20.0"
+byteorder = "1.5.0"
+thiserror = "2.0.3"
+
+[dev-dependencies]
+hex = "0.4.3"
+solana-program-test = "1.18.26"
+solana-sdk = "1.18.26"
+tokio = { version = "1.40.0", features = ["full"] }

+ 169 - 19
lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs

@@ -1,27 +1,34 @@
+mod signature;
+
 use {
-    anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES},
-    std::mem::size_of,
+    crate::signature::VerifiedMessage,
+    anchor_lang::{
+        prelude::*, solana_program::pubkey::PUBKEY_BYTES, system_program, Discriminator,
+    },
+    std::{io::Cursor, mem::size_of},
 };
 
-declare_id!("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");
-
-pub mod storage {
-    use anchor_lang::prelude::{pubkey, Pubkey};
+pub use {
+    crate::signature::{ed25519_program_args, Ed25519SignatureOffsets},
+    pyth_lazer_protocol as protocol,
+};
 
-    pub const ID: Pubkey = pubkey!("3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL");
+declare_id!("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");
 
-    #[test]
-    fn test_storage_id() {
-        use {crate::STORAGE_SEED, anchor_lang::prelude::Pubkey};
+pub const STORAGE_ID: Pubkey = pubkey!("3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL");
 
-        assert_eq!(
-            Pubkey::find_program_address(&[STORAGE_SEED], &super::ID).0,
-            ID
-        );
-    }
+#[test]
+fn test_ids() {
+    assert_eq!(
+        Pubkey::find_program_address(&[STORAGE_SEED], &ID).0,
+        STORAGE_ID
+    );
 }
 
+pub const ANCHOR_DISCRIMINATOR_BYTES: usize = 8;
 pub const MAX_NUM_TRUSTED_SIGNERS: usize = 2;
+pub const SPACE_FOR_TRUSTED_SIGNERS: usize = 5;
+pub const EXTRA_SPACE: usize = 100;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, AnchorSerialize, AnchorDeserialize)]
 pub struct TrustedSignerInfo {
@@ -33,17 +40,41 @@ impl TrustedSignerInfo {
     const SERIALIZED_LEN: usize = PUBKEY_BYTES + size_of::<i64>();
 }
 
+/// TODO: remove this legacy storage type
+#[derive(AnchorDeserialize)]
+pub struct StorageV010 {
+    pub top_authority: Pubkey,
+    pub num_trusted_signers: u8,
+    pub trusted_signers: [TrustedSignerInfo; MAX_NUM_TRUSTED_SIGNERS],
+}
+
+impl StorageV010 {
+    pub const SERIALIZED_LEN: usize = PUBKEY_BYTES
+        + size_of::<u8>()
+        + TrustedSignerInfo::SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS;
+
+    pub fn initialized_trusted_signers(&self) -> &[TrustedSignerInfo] {
+        &self.trusted_signers[0..usize::from(self.num_trusted_signers)]
+    }
+}
+
 #[account]
 pub struct Storage {
     pub top_authority: Pubkey,
+    pub treasury: Pubkey,
+    pub single_update_fee_in_lamports: u64,
     pub num_trusted_signers: u8,
-    pub trusted_signers: [TrustedSignerInfo; MAX_NUM_TRUSTED_SIGNERS],
+    pub trusted_signers: [TrustedSignerInfo; SPACE_FOR_TRUSTED_SIGNERS],
+    pub _extra_space: [u8; EXTRA_SPACE],
 }
 
 impl Storage {
     const SERIALIZED_LEN: usize = PUBKEY_BYTES
+        + PUBKEY_BYTES
+        + size_of::<u64>()
         + size_of::<u8>()
-        + TrustedSignerInfo::SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS;
+        + TrustedSignerInfo::SERIALIZED_LEN * SPACE_FOR_TRUSTED_SIGNERS
+        + EXTRA_SPACE;
 
     pub fn initialized_trusted_signers(&self) -> &[TrustedSignerInfo] {
         &self.trusted_signers[0..usize::from(self.num_trusted_signers)]
@@ -56,8 +87,48 @@ pub const STORAGE_SEED: &[u8] = b"storage";
 pub mod pyth_lazer_solana_contract {
     use super::*;
 
-    pub fn initialize(ctx: Context<Initialize>, top_authority: Pubkey) -> Result<()> {
+    pub fn initialize(
+        ctx: Context<Initialize>,
+        top_authority: Pubkey,
+        treasury: Pubkey,
+    ) -> Result<()> {
         ctx.accounts.storage.top_authority = top_authority;
+        ctx.accounts.storage.treasury = treasury;
+        ctx.accounts.storage.single_update_fee_in_lamports = 1;
+        Ok(())
+    }
+
+    pub fn migrate_from_0_1_0(ctx: Context<MigrateFrom010>, treasury: Pubkey) -> Result<()> {
+        let old_data = ctx.accounts.storage.data.borrow();
+        if old_data[0..ANCHOR_DISCRIMINATOR_BYTES] != Storage::DISCRIMINATOR {
+            return Err(ProgramError::InvalidAccountData.into());
+        }
+        let old_storage = StorageV010::deserialize(&mut &old_data[ANCHOR_DISCRIMINATOR_BYTES..])?;
+        if old_storage.top_authority != ctx.accounts.top_authority.key() {
+            return Err(ProgramError::MissingRequiredSignature.into());
+        }
+        drop(old_data);
+
+        let space = ANCHOR_DISCRIMINATOR_BYTES + Storage::SERIALIZED_LEN;
+        ctx.accounts.storage.realloc(space, false)?;
+        let min_lamports = Rent::get()?.minimum_balance(space);
+        if ctx.accounts.storage.lamports() < min_lamports {
+            return Err(ProgramError::AccountNotRentExempt.into());
+        }
+
+        let mut new_storage = Storage {
+            top_authority: old_storage.top_authority,
+            treasury,
+            single_update_fee_in_lamports: 1,
+            num_trusted_signers: old_storage.num_trusted_signers,
+            trusted_signers: Default::default(),
+            _extra_space: [0; EXTRA_SPACE],
+        };
+        new_storage.trusted_signers[..old_storage.trusted_signers.len()]
+            .copy_from_slice(&old_storage.trusted_signers);
+        new_storage.try_serialize(&mut Cursor::new(
+            &mut **ctx.accounts.storage.data.borrow_mut(),
+        ))?;
         Ok(())
     }
 
@@ -66,6 +137,9 @@ pub mod pyth_lazer_solana_contract {
         if num_trusted_signers > ctx.accounts.storage.trusted_signers.len() {
             return Err(ProgramError::InvalidAccountData.into());
         }
+        if num_trusted_signers > MAX_NUM_TRUSTED_SIGNERS {
+            return Err(ProgramError::InvalidAccountData.into());
+        }
         let mut trusted_signers =
             ctx.accounts.storage.trusted_signers[..num_trusted_signers].to_vec();
         if expires_at == 0 {
@@ -92,6 +166,9 @@ pub mod pyth_lazer_solana_contract {
         if trusted_signers.len() > ctx.accounts.storage.trusted_signers.len() {
             return Err(ProgramError::AccountDataTooSmall.into());
         }
+        if trusted_signers.len() > MAX_NUM_TRUSTED_SIGNERS {
+            return Err(ProgramError::InvalidInstructionData.into());
+        }
 
         ctx.accounts.storage.trusted_signers = Default::default();
         ctx.accounts.storage.trusted_signers[..trusted_signers.len()]
@@ -102,6 +179,47 @@ pub mod pyth_lazer_solana_contract {
             .expect("num signers overflow");
         Ok(())
     }
+
+    /// Verifies a ed25519 signature on Solana by checking that the transaction contains
+    /// a correct call to the built-in `ed25519_program`.
+    ///
+    /// - `message_data` is the signed message that is being verified.
+    /// - `ed25519_instruction_index` is the index of the `ed25519_program` instruction
+    ///   within the transaction. This instruction must precede the current instruction.
+    /// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
+    /// - `message_offset` is the offset of the signed message within the
+    ///   input data for the current instruction.
+    pub fn verify_message(
+        ctx: Context<VerifyMessage>,
+        message_data: Vec<u8>,
+        ed25519_instruction_index: u16,
+        signature_index: u8,
+        message_offset: u16,
+    ) -> Result<VerifiedMessage> {
+        system_program::transfer(
+            CpiContext::new(
+                ctx.accounts.system_program.to_account_info(),
+                system_program::Transfer {
+                    from: ctx.accounts.payer.to_account_info(),
+                    to: ctx.accounts.treasury.to_account_info(),
+                },
+            ),
+            ctx.accounts.storage.single_update_fee_in_lamports,
+        )?;
+
+        signature::verify_message(
+            &ctx.accounts.storage,
+            &ctx.accounts.instructions_sysvar,
+            &message_data,
+            ed25519_instruction_index,
+            signature_index,
+            message_offset,
+        )
+        .map_err(|err| {
+            msg!("signature verification error: {:?}", err);
+            err.into()
+        })
+    }
 }
 
 #[derive(Accounts)]
@@ -111,7 +229,7 @@ pub struct Initialize<'info> {
     #[account(
         init,
         payer = payer,
-        space = 8 + Storage::SERIALIZED_LEN,
+        space = ANCHOR_DISCRIMINATOR_BYTES + Storage::SERIALIZED_LEN,
         seeds = [STORAGE_SEED],
         bump,
     )]
@@ -119,6 +237,19 @@ pub struct Initialize<'info> {
     pub system_program: Program<'info, System>,
 }
 
+#[derive(Accounts)]
+pub struct MigrateFrom010<'info> {
+    pub top_authority: Signer<'info>,
+    #[account(
+        mut,
+        seeds = [STORAGE_SEED],
+        bump,
+    )]
+    /// CHECK: top_authority in storage must match top_authority account.
+    pub storage: AccountInfo<'info>,
+    pub system_program: Program<'info, System>,
+}
+
 #[derive(Accounts)]
 pub struct Update<'info> {
     pub top_authority: Signer<'info>,
@@ -130,3 +261,22 @@ pub struct Update<'info> {
     )]
     pub storage: Account<'info, Storage>,
 }
+
+#[derive(Accounts)]
+pub struct VerifyMessage<'info> {
+    #[account(mut)]
+    pub payer: Signer<'info>,
+    #[account(
+        seeds = [STORAGE_SEED],
+        bump,
+        has_one = treasury
+    )]
+    pub storage: Account<'info, Storage>,
+    /// CHECK: this account doesn't need additional constraints.
+    pub treasury: AccountInfo<'info>,
+    pub system_program: Program<'info, System>,
+    /// CHECK: account ID is checked in Solana SDK during calls
+    /// (e.g. in `sysvar::instructions::load_instruction_at_checked`).
+    /// This account is not usable with anchor's `Program` account type because it's not executable.
+    pub instructions_sysvar: AccountInfo<'info>,
+}

+ 51 - 58
lazer/sdk/solana/src/signature.rs → lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/signature.rs

@@ -1,14 +1,12 @@
 use {
-    anchor_lang::{prelude::Clock, AccountDeserialize},
+    crate::Storage,
+    anchor_lang::{
+        prelude::{borsh, AccountInfo, Clock, ProgramError, Pubkey, SolanaSysvar},
+        solana_program::{ed25519_program, pubkey::PUBKEY_BYTES, sysvar},
+        AnchorDeserialize, AnchorSerialize,
+    },
     bytemuck::{cast_slice, checked::try_cast_slice, Pod, Zeroable},
     byteorder::{ByteOrder, LE},
-    solana_program::{
-        account_info::AccountInfo,
-        ed25519_program,
-        program_error::ProgramError,
-        pubkey::PUBKEY_BYTES,
-        sysvar::{self, Sysvar},
-    },
     thiserror::Error,
 };
 
@@ -44,33 +42,32 @@ pub struct Ed25519SignatureOffsets {
     pub message_instruction_index: u16,
 }
 
-/// Sets up `Ed25519SignatureOffsets` for verifying the Pyth Lazer message signature.
-/// - `instruction_data` must be the *full* input data for your contract's instruction.
-/// - `instruction_index` is the index of that instruction within the transaction.
-/// - `starting_offset` is the offset of the Pyth Lazer message within the instruction data.
-///
-/// Panics if `starting_offset` is invalid or the `instruction_data` is not long enough to
-/// contain the message.
-pub fn signature_offsets(
-    instruction_data: &[u8],
-    instruction_index: u16,
-    starting_offset: u16,
-) -> Ed25519SignatureOffsets {
-    let signature_offset = starting_offset + MAGIC_LEN;
-    let public_key_offset = signature_offset + SIGNATURE_LEN;
-    let message_data_size_offset = public_key_offset + PUBKEY_LEN;
-    let message_data_offset = message_data_size_offset + MESSAGE_SIZE_LEN;
-    let message_data_size = LE::read_u16(
-        &instruction_data[message_data_size_offset.into()..message_data_offset.into()],
-    );
-    Ed25519SignatureOffsets {
-        signature_offset,
-        signature_instruction_index: instruction_index,
-        public_key_offset,
-        public_key_instruction_index: instruction_index,
-        message_data_offset,
-        message_data_size,
-        message_instruction_index: instruction_index,
+impl Ed25519SignatureOffsets {
+    /// Sets up `Ed25519SignatureOffsets` for verifying the Pyth Lazer message signature.
+    /// - `message` is the Pyth Lazer message being sent.
+    /// - `instruction_index` is the index of that instruction within the transaction.
+    /// - `starting_offset` is the offset of the Pyth Lazer message within the instruction data.
+    ///
+    /// Panics if `starting_offset` is invalid or the `instruction_data` is not long enough to
+    /// contain the message.
+    pub fn new(message: &[u8], instruction_index: u16, starting_offset: u16) -> Self {
+        let signature_offset = starting_offset + MAGIC_LEN;
+        let public_key_offset = signature_offset + SIGNATURE_LEN;
+        let message_data_size_offset = public_key_offset + PUBKEY_LEN;
+        let message_data_offset = message_data_size_offset + MESSAGE_SIZE_LEN;
+        let message_data_size = LE::read_u16(
+            &message[(message_data_size_offset - starting_offset).into()
+                ..(message_data_offset - starting_offset).into()],
+        );
+        Ed25519SignatureOffsets {
+            signature_offset,
+            signature_instruction_index: instruction_index,
+            public_key_offset,
+            public_key_instruction_index: instruction_index,
+            message_data_offset,
+            message_data_size,
+            message_instruction_index: instruction_index,
+        }
     }
 }
 
@@ -86,12 +83,12 @@ pub fn ed25519_program_args(signatures: &[Ed25519SignatureOffsets]) -> Vec<u8> {
 }
 
 /// A message with a verified ed25519 signature.
-#[derive(Debug, Clone, Copy)]
-pub struct VerifiedMessage<'a> {
+#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct VerifiedMessage {
     /// Public key that signed the message.
-    pub public_key: &'a [u8],
+    pub public_key: Pubkey,
     /// Signed message payload.
-    pub payload: &'a [u8],
+    pub payload: Vec<u8>,
 }
 
 #[derive(Debug, Error)]
@@ -145,6 +142,12 @@ impl From<SignatureVerificationError> for ProgramError {
     }
 }
 
+impl From<SignatureVerificationError> for anchor_lang::error::Error {
+    fn from(value: SignatureVerificationError) -> Self {
+        ProgramError::from(value).into()
+    }
+}
+
 /// Verifies a ed25519 signature on Solana by checking that the transaction contains
 /// a correct call to the built-in `ed25519_program`.
 ///
@@ -154,28 +157,18 @@ impl From<SignatureVerificationError> for ProgramError {
 /// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
 /// - `message_offset` is the offset of the signed message within the
 ///   input data for the current instruction.
-pub fn verify_message<'a>(
-    pyth_storage_account: &AccountInfo,
-    instruction_sysvar: &AccountInfo,
-    message_data: &'a [u8],
+pub fn verify_message(
+    storage: &Storage,
+    instructions_sysvar: &AccountInfo,
+    message_data: &[u8],
     ed25519_instruction_index: u16,
     signature_index: u8,
     message_offset: u16,
-) -> Result<VerifiedMessage<'a>, SignatureVerificationError> {
-    if pyth_storage_account.key != &pyth_lazer_solana_contract::storage::ID {
-        return Err(SignatureVerificationError::InvalidStorageAccountId);
-    }
-    let storage = {
-        let storage_data = pyth_storage_account.data.borrow();
-        let mut storage_data: &[u8] = *storage_data;
-        pyth_lazer_solana_contract::Storage::try_deserialize(&mut storage_data)
-            .map_err(|_| SignatureVerificationError::InvalidStorageData)?
-    };
-
+) -> Result<VerifiedMessage, SignatureVerificationError> {
     const SOLANA_FORMAT_MAGIC_LE: u32 = 2182742457;
 
     let self_instruction_index =
-        sysvar::instructions::load_current_index_checked(instruction_sysvar)
+        sysvar::instructions::load_current_index_checked(instructions_sysvar)
             .map_err(SignatureVerificationError::LoadCurrentIndexFailed)?;
 
     if ed25519_instruction_index >= self_instruction_index {
@@ -184,7 +177,7 @@ pub fn verify_message<'a>(
 
     let instruction = sysvar::instructions::load_instruction_at_checked(
         ed25519_instruction_index.into(),
-        instruction_sysvar,
+        instructions_sysvar,
     )
     .map_err(SignatureVerificationError::LoadInstructionAtFailed)?;
 
@@ -300,7 +293,7 @@ pub fn verify_message<'a>(
     };
 
     Ok(VerifiedMessage {
-        public_key,
-        payload,
+        public_key: Pubkey::new_from_array(public_key.try_into().unwrap()),
+        payload: payload.to_vec(),
     })
 }

+ 279 - 0
lazer/contracts/solana/programs/pyth-lazer-solana-contract/tests/test1.rs

@@ -0,0 +1,279 @@
+use {
+    anchor_lang::{prelude::AccountMeta, InstructionData},
+    pyth_lazer_solana_contract::{ed25519_program_args, ANCHOR_DISCRIMINATOR_BYTES},
+    solana_program_test::{BanksClient, ProgramTest},
+    solana_sdk::{
+        account::Account,
+        ed25519_program,
+        hash::Hash,
+        instruction::Instruction,
+        pubkey::{Pubkey, PUBKEY_BYTES},
+        signature::Keypair,
+        signer::Signer,
+        system_instruction, system_program, system_transaction, sysvar,
+        transaction::Transaction,
+    },
+    std::env,
+};
+
+fn program_test() -> ProgramTest {
+    if env::var("SBF_OUT_DIR").is_err() {
+        env::set_var(
+            "SBF_OUT_DIR",
+            format!(
+                "{}/../../../../target/sbf-solana-solana/release",
+                env::var("CARGO_MANIFEST_DIR").unwrap()
+            ),
+        );
+    }
+    println!("if add_program fails, run `cargo build-sbf` first.");
+    ProgramTest::new(
+        "pyth_lazer_solana_contract",
+        pyth_lazer_solana_contract::ID,
+        None,
+    )
+}
+
+struct Setup {
+    banks_client: BanksClient,
+    payer: Keypair,
+    recent_blockhash: Hash,
+}
+
+impl Setup {
+    async fn with_program_test(program_test: ProgramTest) -> Self {
+        let (banks_client, payer, recent_blockhash) = program_test.start().await;
+        Self {
+            banks_client,
+            payer,
+            recent_blockhash,
+        }
+    }
+
+    async fn new() -> Self {
+        Self::with_program_test(program_test()).await
+    }
+
+    async fn create_treasury(&mut self) -> Pubkey {
+        let treasury =
+            Pubkey::create_with_seed(&self.payer.pubkey(), "treasury", &system_program::ID)
+                .unwrap();
+
+        let mut transaction_create_treasury = Transaction::new_with_payer(
+            &[system_instruction::create_account_with_seed(
+                &self.payer.pubkey(),
+                &treasury,
+                &self.payer.pubkey(),
+                "treasury",
+                10_000_000,
+                0,
+                &system_program::ID,
+            )],
+            Some(&self.payer.pubkey()),
+        );
+        transaction_create_treasury.sign(&[&self.payer], self.recent_blockhash);
+        self.banks_client
+            .process_transaction(transaction_create_treasury)
+            .await
+            .unwrap();
+        treasury
+    }
+
+    async fn set_trusted(&mut self, verifying_key: Pubkey) {
+        let mut transaction_set_trusted = Transaction::new_with_payer(
+            &[Instruction::new_with_bytes(
+                pyth_lazer_solana_contract::ID,
+                &pyth_lazer_solana_contract::instruction::Update {
+                    trusted_signer: verifying_key,
+                    expires_at: i64::MAX,
+                }
+                .data(),
+                vec![
+                    AccountMeta::new(self.payer.pubkey(), true),
+                    AccountMeta::new(pyth_lazer_solana_contract::STORAGE_ID, false),
+                ],
+            )],
+            Some(&self.payer.pubkey()),
+        );
+        transaction_set_trusted.sign(&[&self.payer], self.recent_blockhash);
+        self.banks_client
+            .process_transaction(transaction_set_trusted)
+            .await
+            .unwrap();
+    }
+
+    async fn verify_message(&mut self, message: &[u8], treasury: Pubkey) {
+        // Instruction #0 will be ed25519 instruction;
+        // Instruction #1 will be our contract instruction.
+        let instruction_index = 1;
+        // 8 bytes for Anchor header, 4 bytes for Vec length.
+        let message_offset = 12;
+        let ed25519_args = dbg!(pyth_lazer_solana_contract::Ed25519SignatureOffsets::new(
+            message,
+            instruction_index,
+            message_offset,
+        ));
+
+        let treasury_starting_lamports = self
+            .banks_client
+            .get_account(treasury)
+            .await
+            .unwrap()
+            .unwrap()
+            .lamports;
+        let mut transaction_verify = Transaction::new_with_payer(
+            &[
+                Instruction::new_with_bytes(
+                    ed25519_program::ID,
+                    &ed25519_program_args(&[ed25519_args]),
+                    vec![],
+                ),
+                Instruction::new_with_bytes(
+                    pyth_lazer_solana_contract::ID,
+                    &pyth_lazer_solana_contract::instruction::VerifyMessage {
+                        message_data: message.to_vec(),
+                        ed25519_instruction_index: 0,
+                        signature_index: 0,
+                        message_offset,
+                    }
+                    .data(),
+                    vec![
+                        AccountMeta::new(self.payer.pubkey(), true),
+                        AccountMeta::new_readonly(pyth_lazer_solana_contract::STORAGE_ID, false),
+                        AccountMeta::new(treasury, false),
+                        AccountMeta::new_readonly(system_program::ID, false),
+                        AccountMeta::new_readonly(sysvar::instructions::ID, false),
+                    ],
+                ),
+            ],
+            Some(&self.payer.pubkey()),
+        );
+        transaction_verify.sign(&[&self.payer], self.recent_blockhash);
+        self.banks_client
+            .process_transaction(transaction_verify)
+            .await
+            .unwrap();
+
+        assert_eq!(
+            self.banks_client
+                .get_account(treasury)
+                .await
+                .unwrap()
+                .unwrap()
+                .lamports,
+            treasury_starting_lamports + 1,
+        );
+    }
+}
+
+#[tokio::test]
+async fn test_basic() {
+    let mut setup = Setup::new().await;
+    let treasury = setup.create_treasury().await;
+
+    let mut transaction_init_contract = Transaction::new_with_payer(
+        &[Instruction::new_with_bytes(
+            pyth_lazer_solana_contract::ID,
+            &pyth_lazer_solana_contract::instruction::Initialize {
+                top_authority: setup.payer.pubkey(),
+                treasury,
+            }
+            .data(),
+            vec![
+                AccountMeta::new(setup.payer.pubkey(), true),
+                AccountMeta::new(pyth_lazer_solana_contract::STORAGE_ID, false),
+                AccountMeta::new_readonly(system_program::ID, false),
+            ],
+        )],
+        Some(&setup.payer.pubkey()),
+    );
+    transaction_init_contract.sign(&[&setup.payer], setup.recent_blockhash);
+    setup
+        .banks_client
+        .process_transaction(transaction_init_contract)
+        .await
+        .unwrap();
+
+    let verifying_key =
+        hex::decode("74313a6525edf99936aa1477e94c72bc5cc617b21745f5f03296f3154461f214").unwrap();
+    let message = hex::decode(
+        "b9011a82e5cddee2c1bd364c8c57e1c98a6a28d194afcad410ff412226c8b2ae931ff59a57147cb47c7307\
+        afc2a0a1abec4dd7e835a5b7113cf5aeac13a745c6bed6c60074313a6525edf99936aa1477e94c72bc5cc61\
+        7b21745f5f03296f3154461f2141c0075d3c7931c9773f30a240600010102000000010000e1f50500000000",
+    )
+    .unwrap();
+
+    setup.set_trusted(verifying_key.try_into().unwrap()).await;
+    setup.verify_message(&message, treasury).await;
+}
+
+#[tokio::test]
+async fn test_migrate_from_0_1_0() {
+    let mut program_test = program_test();
+    // Create a storage PDA account with the data that was produced by the program v0.1.0.
+    let mut old_storage_data = hex::decode(
+        "d175ffb9c4af4409aa4dcb5d31150b162b664abd843cb231cee5c0ebf759ce371d9cb36ffc653796\
+        0174313a6525edf99936aa1477e94c72bc5cc617b21745f5f03296f3154461f214ffffffffffffff7\
+        f00000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    )
+    .unwrap();
+    let top_authority = Keypair::new();
+    // Replace top authority pubkey in storage PDA data to allow successful migration.
+    old_storage_data[ANCHOR_DISCRIMINATOR_BYTES..ANCHOR_DISCRIMINATOR_BYTES + PUBKEY_BYTES]
+        .copy_from_slice(&top_authority.pubkey().to_bytes());
+    program_test.add_account(
+        pyth_lazer_solana_contract::STORAGE_ID,
+        Account {
+            lamports: 1733040,
+            data: old_storage_data,
+            owner: pyth_lazer_solana_contract::ID,
+            executable: false,
+            rent_epoch: 18446744073709551615,
+        },
+    );
+    let mut setup = Setup::with_program_test(program_test).await;
+    let treasury = setup.create_treasury().await;
+
+    // Make sure storage PDA will be rent-exempt after resize.
+    let tx_transfer = system_transaction::transfer(
+        &setup.payer,
+        &pyth_lazer_solana_contract::STORAGE_ID,
+        10_000_000,
+        setup.recent_blockhash,
+    );
+    setup
+        .banks_client
+        .process_transaction(tx_transfer)
+        .await
+        .unwrap();
+
+    let mut transaction_migrate_contract = Transaction::new_with_payer(
+        &[Instruction::new_with_bytes(
+            pyth_lazer_solana_contract::ID,
+            &pyth_lazer_solana_contract::instruction::MigrateFrom010 { treasury }.data(),
+            vec![
+                AccountMeta::new(top_authority.pubkey(), true),
+                AccountMeta::new(pyth_lazer_solana_contract::STORAGE_ID, false),
+                AccountMeta::new_readonly(system_program::ID, false),
+            ],
+        )],
+        Some(&setup.payer.pubkey()),
+    );
+    transaction_migrate_contract.sign(&[&setup.payer, &top_authority], setup.recent_blockhash);
+    setup
+        .banks_client
+        .process_transaction(transaction_migrate_contract)
+        .await
+        .unwrap();
+
+    let message = hex::decode(
+        "b9011a82e5cddee2c1bd364c8c57e1c98a6a28d194afcad410ff412226c8b2ae931ff59a57147cb47c7307\
+        afc2a0a1abec4dd7e835a5b7113cf5aeac13a745c6bed6c60074313a6525edf99936aa1477e94c72bc5cc61\
+        7b21745f5f03296f3154461f2141c0075d3c7931c9773f30a240600010102000000010000e1f50500000000",
+    )
+    .unwrap();
+
+    // The contract will recognize the trusted signer without calling `set_trusted`
+    // because it was present in the original storage PDA data.
+    setup.verify_message(&message, treasury).await;
+}

+ 1 - 1
lazer/contracts/solana/scripts/setup.ts

@@ -39,7 +39,7 @@ async function main() {
   if (storage.length === 0) {
     console.log("Initializing the program");
     await program.methods
-      .initialize(keypair.publicKey)
+      .initialize(keypair.publicKey, anchor.web3.PublicKey.unique())
       .accounts({
         payer: wallet.publicKey,
       })

+ 1 - 1
lazer/contracts/solana/tests/pyth-lazer-solana-contract.ts

@@ -14,7 +14,7 @@ describe("pyth-lazer-solana-contract", () => {
     const topAuthorityKeypair = anchor.web3.Keypair.generate();
 
     const tx = await program.methods
-      .initialize(topAuthorityKeypair.publicKey)
+      .initialize(topAuthorityKeypair.publicKey, anchor.web3.PublicKey.unique())
       .rpc();
     console.log("Your transaction signature", tx);
 

+ 0 - 1
lazer/sdk/solana/.gitignore

@@ -1 +0,0 @@
-/target

+ 0 - 17
lazer/sdk/solana/Cargo.toml

@@ -1,17 +0,0 @@
-[package]
-name = "pyth-lazer-sdk"
-version = "0.1.0"
-edition = "2021"
-description = "Pyth Lazer SDK"
-license = "Apache-2.0"
-repository = "https://github.com/pyth-network/pyth-crosschain"
-
-[dependencies]
-pyth-lazer-protocol = { version = "0.1.0", path = "../rust/protocol" }
-pyth-lazer-solana-contract = { version = "0.1.0", path = "../../contracts/solana/programs/pyth-lazer-solana-contract", features = ["no-entrypoint"] }
-
-solana-program = "1.18.26"
-bytemuck = { version = "1.18.0", features = ["derive"] }
-byteorder = "1.5.0"
-thiserror = "1.0.64"
-anchor-lang = "0.30.1"

+ 0 - 9
lazer/sdk/solana/src/lib.rs

@@ -1,9 +0,0 @@
-mod signature;
-
-pub use {
-    pyth_lazer_protocol as protocol,
-    signature::{
-        ed25519_program_args, signature_offsets, verify_message, Ed25519SignatureOffsets,
-        VerifiedMessage,
-    },
-};

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff