diff --git a/lazer/Cargo.lock b/lazer/Cargo.lock index 977b27f93d..f1221c6527 100644 --- a/lazer/Cargo.lock +++ b/lazer/Cargo.lock @@ -3772,7 +3772,7 @@ dependencies = [ "futures-util", "hex", "libsecp256k1 0.7.1", - "pyth-lazer-protocol 0.6.1", + "pyth-lazer-protocol", "serde", "serde_json", "tokio", @@ -3781,22 +3781,6 @@ dependencies = [ "url", ] -[[package]] -name = "pyth-lazer-protocol" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560a20d7f1040abad40245e524e657a2ec731e7e0560e25a1532301a1a04cb3f" -dependencies = [ - "anyhow", - "base64 0.22.1", - "byteorder", - "derive_more", - "itertools 0.13.0", - "rust_decimal", - "serde", - "serde_json", -] - [[package]] name = "pyth-lazer-protocol" version = "0.6.1" @@ -3825,7 +3809,7 @@ dependencies = [ "bytemuck", "byteorder", "hex", - "pyth-lazer-protocol 0.5.0", + "pyth-lazer-protocol", "solana-program-test", "solana-sdk", "thiserror 2.0.3", diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml index 7fe41257d4..b825fdb290 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml @@ -19,7 +19,7 @@ no-log-ix-name = [] idl-build = ["anchor-lang/idl-build"] [dependencies] -pyth-lazer-protocol = "0.5.0" +pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.6.0" } anchor-lang = "0.30.1" bytemuck = "1.20.0" diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs index ef7a128647..390033a954 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs @@ -2,7 +2,11 @@ mod signature; use { crate::signature::VerifiedMessage, - anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES, system_program}, + anchor_lang::{ + prelude::*, + solana_program::{keccak, pubkey::PUBKEY_BYTES, secp256k1_recover::secp256k1_recover}, + system_program, + }, std::mem::size_of, }; @@ -26,45 +30,81 @@ fn test_ids() { 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; +pub const SPACE_FOR_TRUSTED_ECDSA_SIGNERS: usize = 2; +pub const EXTRA_SPACE: usize = 43; #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, AnchorSerialize, AnchorDeserialize)] -pub struct TrustedSignerInfo { - pub pubkey: Pubkey, +pub struct TrustedSignerInfo { + pub pubkey: T, pub expires_at: i64, } -impl TrustedSignerInfo { - const SERIALIZED_LEN: usize = PUBKEY_BYTES + size_of::(); -} +pub const EVM_ADDRESS_LEN: usize = 20; +pub type EvmAddress = [u8; EVM_ADDRESS_LEN]; #[account] +#[derive(Debug, PartialEq)] 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; SPACE_FOR_TRUSTED_SIGNERS], + pub trusted_signers: [TrustedSignerInfo; SPACE_FOR_TRUSTED_SIGNERS], + pub num_trusted_ecdsa_signers: u8, + pub trusted_ecdsa_signers: [TrustedSignerInfo; SPACE_FOR_TRUSTED_ECDSA_SIGNERS], pub _extra_space: [u8; EXTRA_SPACE], } +#[test] +fn storage_size() { + // Keep the size the same when possible. If the size increases, we'll need to perform + // a migration that increases the account size on-chain. + assert_eq!(Storage::SERIALIZED_LEN, 373); +} + impl Storage { const SERIALIZED_LEN: usize = PUBKEY_BYTES + PUBKEY_BYTES + size_of::() + size_of::() - + TrustedSignerInfo::SERIALIZED_LEN * SPACE_FOR_TRUSTED_SIGNERS + + (PUBKEY_BYTES + size_of::()) * SPACE_FOR_TRUSTED_SIGNERS + + size_of::() + + (EVM_ADDRESS_LEN + size_of::()) * SPACE_FOR_TRUSTED_ECDSA_SIGNERS + EXTRA_SPACE; - pub fn initialized_trusted_signers(&self) -> &[TrustedSignerInfo] { + pub fn initialized_trusted_signers(&self) -> &[TrustedSignerInfo] { &self.trusted_signers[0..usize::from(self.num_trusted_signers)] } + + pub fn initialized_trusted_ecdsa_signers(&self) -> &[TrustedSignerInfo] { + &self.trusted_ecdsa_signers[0..usize::from(self.num_trusted_ecdsa_signers)] + } + + pub fn is_trusted(&self, signer: &Pubkey) -> std::result::Result { + let now = Clock::get()?.unix_timestamp; + + Ok(self + .initialized_trusted_signers() + .iter() + .any(|s| &s.pubkey == signer && s.expires_at > now)) + } + + pub fn is_ecdsa_trusted(&self, signer: &EvmAddress) -> std::result::Result { + let now = Clock::get()?.unix_timestamp; + + Ok(self + .initialized_trusted_ecdsa_signers() + .iter() + .any(|s| &s.pubkey == signer && s.expires_at > now)) + } } pub const STORAGE_SEED: &[u8] = b"storage"; #[program] pub mod pyth_lazer_solana_contract { + use pyth_lazer_protocol::message::LeEcdsaMessage; + use super::*; pub fn initialize( @@ -79,51 +119,27 @@ pub mod pyth_lazer_solana_contract { } pub fn update(ctx: Context, trusted_signer: Pubkey, expires_at: i64) -> Result<()> { - let num_trusted_signers: usize = ctx.accounts.storage.num_trusted_signers.into(); - 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 { - // Delete - let pos = trusted_signers - .iter() - .position(|item| item.pubkey == trusted_signer) - .ok_or(ProgramError::InvalidInstructionData)?; - trusted_signers.remove(pos); - } else if let Some(item) = trusted_signers - .iter_mut() - .find(|item| item.pubkey == trusted_signer) - { - // Modify - item.expires_at = expires_at; - } else { - // Add - trusted_signers.push(TrustedSignerInfo { - pubkey: trusted_signer, - expires_at, - }); - } - - 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()); - } + let storage = &mut *ctx.accounts.storage; + update_trusted_signer( + &mut storage.num_trusted_signers, + &mut storage.trusted_signers, + trusted_signer, + expires_at, + ) + } - ctx.accounts.storage.trusted_signers = Default::default(); - ctx.accounts.storage.trusted_signers[..trusted_signers.len()] - .copy_from_slice(&trusted_signers); - ctx.accounts.storage.num_trusted_signers = trusted_signers - .len() - .try_into() - .expect("num signers overflow"); - Ok(()) + pub fn update_ecdsa_signer( + ctx: Context, + trusted_signer: EvmAddress, + expires_at: i64, + ) -> Result<()> { + let storage = &mut *ctx.accounts.storage; + update_trusted_signer( + &mut storage.num_trusted_ecdsa_signers, + &mut storage.trusted_ecdsa_signers, + trusted_signer, + expires_at, + ) } /// Verifies a ed25519 signature on Solana by checking that the transaction contains @@ -164,6 +180,47 @@ pub mod pyth_lazer_solana_contract { err.into() }) } + + pub fn verify_ecdsa_message( + ctx: Context, + message_data: Vec, + ) -> Result<()> { + 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, + )?; + + let message = LeEcdsaMessage::deserialize_slice(&message_data) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + let pubkey = secp256k1_recover( + &keccak::hash(&message.payload).0, + message.recovery_id, + &message.signature, + ) + .map_err(|err| { + msg!("secp256k1_recover failed: {:?}", err); + ProgramError::InvalidInstructionData + })?; + let addr: EvmAddress = keccak::hash(&pubkey.0).0[12..] + .try_into() + .expect("invalid addr len"); + if addr == EvmAddress::default() { + msg!("secp256k1_recover failed: zero output"); + return Err(ProgramError::InvalidInstructionData.into()); + } + if !ctx.accounts.storage.is_ecdsa_trusted(&addr)? { + msg!("untrusted signer: {:?}", addr); + return Err(ProgramError::MissingRequiredSignature.into()); + } + Ok(()) + } } #[derive(Accounts)] @@ -211,3 +268,137 @@ pub struct VerifyMessage<'info> { /// This account is not usable with anchor's `Program` account type because it's not executable. pub instructions_sysvar: AccountInfo<'info>, } + +#[derive(Accounts)] +pub struct VerifyEcdsaMessage<'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>, +} + +fn update_trusted_signer( + stored_num_trusted_signers: &mut u8, + stored_trusted_signers: &mut [TrustedSignerInfo], + trusted_signer: T, + expires_at: i64, +) -> Result<()> { + let num_trusted_signers: usize = (*stored_num_trusted_signers).into(); + if num_trusted_signers > stored_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 = stored_trusted_signers[..num_trusted_signers].to_vec(); + if expires_at == 0 { + // Delete + let pos = trusted_signers + .iter() + .position(|item| item.pubkey == trusted_signer) + .ok_or(ProgramError::InvalidInstructionData)?; + trusted_signers.remove(pos); + } else if let Some(item) = trusted_signers + .iter_mut() + .find(|item| item.pubkey == trusted_signer) + { + // Modify + item.expires_at = expires_at; + } else { + // Add + trusted_signers.push(TrustedSignerInfo { + pubkey: trusted_signer, + expires_at, + }); + } + + if trusted_signers.len() > trusted_signers.len() { + return Err(ProgramError::AccountDataTooSmall.into()); + } + if trusted_signers.len() > MAX_NUM_TRUSTED_SIGNERS { + return Err(ProgramError::InvalidInstructionData.into()); + } + + stored_trusted_signers[..trusted_signers.len()].copy_from_slice(&trusted_signers); + for item in &mut stored_trusted_signers[trusted_signers.len()..] { + *item = Default::default(); + } + *stored_num_trusted_signers = trusted_signers + .len() + .try_into() + .expect("num signers overflow"); + Ok(()) +} + +#[test] +fn test_storage_compat_after_adding_ecdsa() { + // This is data of a storage account created by the previous version of the contract. + let data = [ + 209, 117, 255, 185, 196, 175, 68, 9, 221, 56, 75, 202, 174, 248, 122, 155, 212, 29, 112, + 50, 82, 65, 161, 137, 16, 164, 61, 134, 119, 132, 149, 1, 178, 177, 3, 187, 25, 187, 143, + 244, 233, 140, 161, 230, 115, 255, 214, 103, 208, 40, 16, 101, 45, 35, 153, 15, 145, 134, + 250, 244, 248, 255, 51, 165, 169, 186, 183, 210, 155, 137, 30, 84, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 116, 49, 58, 101, 37, 237, 249, 153, 54, 170, 20, 119, 233, 76, 114, 188, 92, 198, 23, + 178, 23, 69, 245, 240, 50, 150, 243, 21, 68, 97, 242, 20, 255, 255, 255, 255, 255, 255, + 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + let storage = Storage::deserialize(&mut &data[..]).unwrap(); + assert_eq!( + storage, + Storage { + top_authority: pubkey!("F6eZvgfuPtncCUDzYgzaFPRodHwZXQHe1pC4kkyvkYwa"), + treasury: pubkey!("D2Y884NqR9TVagZftdzzuEgtTEwd3AsS2nLMHEnVkXCQ"), + single_update_fee_in_lamports: 6061433450835458729, + num_trusted_signers: 1, + trusted_signers: [ + TrustedSignerInfo { + pubkey: pubkey!("1111111avyLnoUfmuX6KZaaTrSfth7n9tX4u4rVV"), + expires_at: 1509375770176493106 + }, + TrustedSignerInfo { + pubkey: pubkey!("JEKNVnkbo2qryGmQn1b2RCJcGKVCn6WvNZmFdEiZGVSo"), + expires_at: 0 + }, + TrustedSignerInfo { + pubkey: Pubkey::default(), + expires_at: 0 + }, + TrustedSignerInfo { + pubkey: Pubkey::default(), + expires_at: 0 + }, + TrustedSignerInfo { + pubkey: Pubkey::default(), + expires_at: 0 + } + ], + num_trusted_ecdsa_signers: 0, + trusted_ecdsa_signers: [ + TrustedSignerInfo { + pubkey: Default::default(), + expires_at: 0 + }, + TrustedSignerInfo { + pubkey: Default::default(), + expires_at: 0 + }, + ], + _extra_space: [0; 43], + } + ); +} diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/signature.rs b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/signature.rs index 55af5a49e4..1de839a3e4 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/signature.rs +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/signature.rs @@ -1,7 +1,7 @@ use { crate::Storage, anchor_lang::{ - prelude::{borsh, AccountInfo, Clock, ProgramError, Pubkey, SolanaSysvar}, + prelude::{borsh, AccountInfo, ProgramError, Pubkey}, solana_program::{ ed25519_program, program_memory::sol_memcmp, pubkey::PUBKEY_BYTES, sysvar, }, @@ -9,6 +9,7 @@ use { }, bytemuck::{cast_slice, checked::try_cast_slice, Pod, Zeroable}, byteorder::{ByteOrder, LE}, + pyth_lazer_protocol::message::format_magics_le::SOLANA_FORMAT_MAGIC, thiserror::Error, }; @@ -172,8 +173,6 @@ pub fn verify_message( ed25519_instruction_index: u16, signature_index: u8, ) -> Result { - const SOLANA_FORMAT_MAGIC_LE: u32 = 2182742457; - let self_instruction_index = sysvar::instructions::load_current_index_checked(instructions_sysvar) .map_err(SignatureVerificationError::LoadCurrentIndexFailed)?; @@ -237,7 +236,7 @@ pub fn verify_message( } let magic = LE::read_u32(&message_data[..MAGIC_LEN.into()]); - if magic != SOLANA_FORMAT_MAGIC_LE { + if magic != SOLANA_FORMAT_MAGIC { return Err(SignatureVerificationError::FormatMagicMismatch); } @@ -294,13 +293,10 @@ pub fn verify_message( .ok_or(SignatureVerificationError::MessageOffsetOverflow)?; &message_data[start..end] }; - let now = Clock::get() - .map_err(SignatureVerificationError::ClockGetFailed)? - .unix_timestamp; + if !storage - .initialized_trusted_signers() - .iter() - .any(|s| s.pubkey.as_ref() == public_key && s.expires_at > now) + .is_trusted(&public_key.try_into().expect("invalid pubkey len")) + .map_err(SignatureVerificationError::ClockGetFailed)? { return Err(SignatureVerificationError::NotTrustedSigner); } diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/tests/test1.rs b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/tests/test1.rs index 6417d9b835..ff5af72b53 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/tests/test1.rs +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/tests/test1.rs @@ -1,6 +1,6 @@ use { anchor_lang::{prelude::AccountMeta, InstructionData}, - pyth_lazer_solana_contract::ed25519_program_args, + pyth_lazer_solana_contract::{ed25519_program_args, EvmAddress}, solana_program_test::{BanksClient, BanksClientError, ProgramTest}, solana_sdk::{ ed25519_program, @@ -37,31 +37,32 @@ struct Setup { banks_client: BanksClient, payer: Keypair, recent_blockhash: Hash, + treasury: Pubkey, } impl Setup { async fn with_program_test(program_test: ProgramTest) -> Self { let (banks_client, payer, recent_blockhash) = program_test.start().await; - Self { + let mut setup = Self { + treasury: Pubkey::create_with_seed(&payer.pubkey(), "treasury", &system_program::ID) + .unwrap(), banks_client, payer, recent_blockhash, - } + }; + setup.create_treasury().await; + setup } 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(); - + async fn create_treasury(&mut self) { let mut transaction_create_treasury = Transaction::new_with_payer( &[system_instruction::create_account_with_seed( &self.payer.pubkey(), - &treasury, + &self.treasury, &self.payer.pubkey(), "treasury", 10_000_000, @@ -75,7 +76,30 @@ impl Setup { .process_transaction(transaction_create_treasury) .await .unwrap(); - treasury + } + + async fn init_contract(&mut self) { + 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: self.payer.pubkey(), + treasury: self.treasury, + } + .data(), + vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new(pyth_lazer_solana_contract::STORAGE_ID, false), + AccountMeta::new_readonly(system_program::ID, false), + ], + )], + Some(&self.payer.pubkey()), + ); + transaction_init_contract.sign(&[&self.payer], self.recent_blockhash); + self.banks_client + .process_transaction(transaction_init_contract) + .await + .unwrap(); } async fn set_trusted(&mut self, verifying_key: Pubkey) { @@ -101,23 +125,44 @@ impl Setup { .unwrap(); } - async fn verify_message(&mut self, message: &[u8], treasury: Pubkey) { + async fn set_trusted_ecdsa(&mut self, verifying_key: EvmAddress) { + let mut transaction_set_trusted = Transaction::new_with_payer( + &[Instruction::new_with_bytes( + pyth_lazer_solana_contract::ID, + &pyth_lazer_solana_contract::instruction::UpdateEcdsaSigner { + 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]) { let treasury_starting_lamports = self .banks_client - .get_account(treasury) + .get_account(self.treasury) .await .unwrap() .unwrap() .lamports; // 8 bytes for Anchor header, 4 bytes for Vec length. - self.verify_message_with_offset(message, treasury, 12) - .await - .unwrap(); + self.verify_message_with_offset(message, 12).await.unwrap(); assert_eq!( self.banks_client - .get_account(treasury) + .get_account(self.treasury) .await .unwrap() .unwrap() @@ -129,7 +174,6 @@ impl Setup { async fn verify_message_with_offset( &mut self, message: &[u8], - treasury: Pubkey, message_offset: u16, ) -> Result<(), BanksClientError> { // Instruction #0 will be ed25519 instruction; @@ -159,7 +203,7 @@ impl Setup { vec![ AccountMeta::new(self.payer.pubkey(), true), AccountMeta::new_readonly(pyth_lazer_solana_contract::STORAGE_ID, false), - AccountMeta::new(treasury, false), + AccountMeta::new(self.treasury, false), AccountMeta::new_readonly(system_program::ID, false), AccountMeta::new_readonly(sysvar::instructions::ID, false), ], @@ -172,35 +216,54 @@ impl Setup { .process_transaction(transaction_verify) .await } + + async fn verify_message_ecdsa(&mut self, message: &[u8]) { + let treasury_starting_lamports = self + .banks_client + .get_account(self.treasury) + .await + .unwrap() + .unwrap() + .lamports; + + let mut transaction_verify = Transaction::new_with_payer( + &[Instruction::new_with_bytes( + pyth_lazer_solana_contract::ID, + &pyth_lazer_solana_contract::instruction::VerifyEcdsaMessage { + message_data: message.to_vec(), + } + .data(), + vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new_readonly(pyth_lazer_solana_contract::STORAGE_ID, false), + AccountMeta::new(self.treasury, false), + AccountMeta::new_readonly(system_program::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(self.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(); + setup.init_contract().await; let verifying_key = hex::decode("74313a6525edf99936aa1477e94c72bc5cc617b21745f5f03296f3154461f214").unwrap(); @@ -212,36 +275,13 @@ async fn test_basic() { .unwrap(); setup.set_trusted(verifying_key.try_into().unwrap()).await; - setup.verify_message(&message, treasury).await; + setup.verify_message(&message).await; } #[tokio::test] async fn test_alignment() { 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(); + setup.init_contract().await; let verifying_key = hex::decode("f65210bee4fcf5b1cee1e537fabcfd95010297653b94af04d454fc473e94834f").unwrap(); @@ -255,36 +295,13 @@ async fn test_alignment() { .unwrap(); setup.set_trusted(verifying_key.try_into().unwrap()).await; - setup.verify_message(&message, treasury).await; + setup.verify_message(&message).await; } #[tokio::test] async fn test_rejects_wrong_offset() { 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(); + setup.init_contract().await; let verifying_key = hex::decode("74313a6525edf99936aa1477e94c72bc5cc617b21745f5f03296f3154461f214").unwrap(); @@ -324,7 +341,7 @@ async fn test_rejects_wrong_offset() { setup.set_trusted(verifying_key.try_into().unwrap()).await; setup.set_trusted(verifying_key_2.try_into().unwrap()).await; let err = setup - .verify_message_with_offset(&message, treasury, 12 + 130) + .verify_message_with_offset(&message, 12 + 130) .await .unwrap_err(); assert!(matches!( @@ -335,3 +352,20 @@ async fn test_rejects_wrong_offset() { )) )); } + +#[tokio::test] +async fn test_ecdsa() { + let mut setup = Setup::new().await; + setup.init_contract().await; + + let verifying_key = hex::decode("b8d50f0bae75bf6e03c104903d7c3afc4a6596da").unwrap(); + let message = hex::decode( + "e4bd474dd4b822eca4509650613e58b21db858e60750ab3498d4a484028785981740adf42cd558bb4f9efd5157bcbb60a1939470ead091b82b63641ad962c7a537db4eb300310075d3c793c0afe900e42e060003010100000004000054e616b201000004f8ff020035dc1cb2010000010073f010b2010000", + ) + .unwrap(); + + setup + .set_trusted_ecdsa(verifying_key.try_into().unwrap()) + .await; + setup.verify_message_ecdsa(&message).await; +}