Skip to content

[solana] Add pre-commit for solana target chain contract #753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,16 @@ repos:
entry: cargo +nightly clippy --manifest-path ./accumulator_updater/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
pass_filenames: false
files: accumulator_updater
# Hooks for solana receiver contract
- id: cargo-fmt-solana-receiver
name: Cargo format for solana target chain contract
language: "rust"
entry: cargo +nightly fmt --manifest-path ./target_chains/solana/Cargo.toml --all -- --config-path rustfmt.toml
pass_filenames: false
files: target_chains/solana
- id: cargo-clippy-solana-receiver
name: Cargo clippy for solana target chain contract
language: "rust"
entry: cargo +nightly clippy --manifest-path ./target_chains/solana/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
pass_filenames: false
files: target_chains/solana
17 changes: 7 additions & 10 deletions target_chains/solana/cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use {
clap::{
Parser,
Subcommand,
},
use clap::{
Parser,
Subcommand,
};

#[derive(Parser, Debug)]
Expand All @@ -12,22 +10,21 @@ use {
)]
pub struct Cli {
#[clap(subcommand)]
pub action: Action,
pub action: Action,
}

#[derive(Subcommand, Debug)]
pub enum Action {
#[clap(about = "Verify, post and receive the price VAA on solana")]
PostAndReceiveVAA {
#[clap(short = 'v', long,
help = "Price VAA from Pythnet")]
#[clap(short = 'v', long, help = "Price VAA from Pythnet")]
vaa: String,
#[clap(
short = 'k', long,
short = 'k',
long,
default_value = "~/.config/solana/id.json",
help = "Keypair of the payer of transactions"
)]
keypair: String,
},

}
41 changes: 18 additions & 23 deletions target_chains/solana/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
pub mod cli;

use {
anchor_client::anchor_lang::{
AnchorDeserialize,
InstructionData,
Owner,
ToAccountMetas,
},
anyhow::Result,
clap::Parser,
cli::{
Action,
Cli,
},
clap::Parser,
anyhow::Result,

pyth_solana_receiver::{
accounts::DecodePostedVaa,
state::AnchorVaa,
ID,
},
solana_client::rpc_client::RpcClient,
solana_sdk::{
instruction::Instruction,
signature::{
read_keypair_file,
Keypair,
},
signer::Signer,
instruction::Instruction,
transaction::Transaction,
},

wormhole::VAA,
wormhole_solana::{
instructions::{
Expand All @@ -26,25 +36,10 @@ use {
PostVAAData,
},
Account,
GuardianSet,
Config as WormholeConfig,
GuardianSet,
VAA as WormholeSolanaVAA,
},

pyth_solana_receiver::{
ID,
state::AnchorVaa,
accounts::DecodePostedVaa,
},

anchor_client::anchor_lang::{
Owner,
ToAccountMetas,
InstructionData,
AnchorDeserialize,
},

solana_client::rpc_client::RpcClient,
};

fn main() -> Result<()> {
Expand Down Expand Up @@ -111,8 +106,8 @@ fn main() -> Result<()> {
)?;

println!("[5/5] Receive and deserialize the VAA on solana");
let account_metas = DecodePostedVaa::populate(&payer.pubkey(), &posted_vaa_key)
.to_account_metas(None);
let account_metas =
DecodePostedVaa::populate(&payer.pubkey(), &posted_vaa_key).to_account_metas(None);

println!("Receiver program ID is {}", ID);
let invoke_receiver_instruction = Instruction {
Expand Down
56 changes: 32 additions & 24 deletions target_chains/solana/programs/solana-receiver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ pub mod state;
mod tests;

use {
crate::error::ReceiverError::*,
anchor_lang::prelude::*,
hex::ToHex,
pyth_wormhole_attester_sdk::BatchPriceAttestation,
solana_program::{
keccak,
secp256k1_recover::secp256k1_recover,
},
state::AnchorVaa,
wormhole::Chain::{
self,
Solana,
Pythnet,
Solana,
},
state::AnchorVaa,
anchor_lang::prelude::*,
pyth_wormhole_attester_sdk::BatchPriceAttestation,
solana_program::{ keccak, secp256k1_recover::secp256k1_recover },
};
use hex::ToHex;
use crate::error::ReceiverError::*;

declare_id!("pythKkWXoywbvTQVcWrNDz5ENvWteF7tem7xzW52NBK");

Expand All @@ -28,9 +31,12 @@ pub mod pyth_solana_receiver {
let posted_vaa = &ctx.accounts.posted_vaa.payload;
let batch: BatchPriceAttestation =
BatchPriceAttestation::deserialize(posted_vaa.as_slice())
.map_err(|_| DeserializeVAAFailed)?;
.map_err(|_| DeserializeVAAFailed)?;

msg!("There are {} attestations in this batch.", batch.price_attestations.len());
msg!(
"There are {} attestations in this batch.",
batch.price_attestations.len()
);

for attestation in batch.price_attestations {
msg!("product_id: {}", attestation.product_id);
Expand All @@ -47,7 +53,12 @@ pub mod pyth_solana_receiver {
Ok(())
}

pub fn update(ctx: Context<Update>, data: Vec<u8>, recovery_id: u8, signature: [u8; 64]) -> Result<()> {
pub fn update(
_ctx: Context<Update>,
data: Vec<u8>,
recovery_id: u8,
signature: [u8; 64],
) -> Result<()> {
// This costs about 10k compute units
let message_hash = {
let mut hasher = keccak::Hasher::default();
Expand All @@ -56,13 +67,13 @@ pub mod pyth_solana_receiver {
};

// This costs about 25k compute units
let recovered_pubkey = secp256k1_recover(
&message_hash.0,
recovery_id,
&signature,
).map_err(|_| ProgramError::InvalidArgument)?;
let recovered_pubkey = secp256k1_recover(&message_hash.0, recovery_id, &signature)
.map_err(|_| ProgramError::InvalidArgument)?;

msg!("Recovered key: {}", recovered_pubkey.0.encode_hex::<String>());
msg!(
"Recovered key: {}",
recovered_pubkey.0.encode_hex::<String>()
);

// TODO: Check the pubkey is an expected value.
// Here we are checking the secp256k1 pubkey against a known authorized pubkey.
Expand All @@ -78,18 +89,15 @@ pub mod pyth_solana_receiver {
#[derive(Accounts)]
pub struct DecodePostedVaa<'info> {
#[account(mut)]
pub payer: Signer<'info>,
pub payer: Signer<'info>,
#[account(constraint = (Chain::from(posted_vaa.emitter_chain) == Solana || Chain::from(posted_vaa.emitter_chain) == Pythnet) @ EmitterChainNotSolanaOrPythnet, constraint = (&posted_vaa.magic == b"vaa" || &posted_vaa.magic == b"msg" || &posted_vaa.magic == b"msu") @PostedVaaHeaderWrongMagicNumber)]
pub posted_vaa: Account<'info, AnchorVaa>,
pub posted_vaa: Account<'info, AnchorVaa>,
}

impl crate::accounts::DecodePostedVaa {
pub fn populate(
payer: &Pubkey,
posted_vaa: &Pubkey,
) -> Self {
pub fn populate(payer: &Pubkey, posted_vaa: &Pubkey) -> Self {
crate::accounts::DecodePostedVaa {
payer: *payer,
payer: *payer,
posted_vaa: *posted_vaa,
}
}
Expand All @@ -98,5 +106,5 @@ impl crate::accounts::DecodePostedVaa {
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub payer: Signer<'info>,
pub payer: Signer<'info>,
}
2 changes: 1 addition & 1 deletion target_chains/solana/programs/solana-receiver/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use {
anchor_lang::prelude::*,
std::{
io::Write,
ops::Deref,
str::FromStr,
},
wormhole_solana::VAA,
anchor_lang::prelude::*,
};

// The current chain's wormhole bridge owns the VAA accounts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,20 @@ use {
self,
UpgradeableLoaderState,
},
hash::hash,
hash::Hash,
instruction::{
AccountMeta,
Instruction,
},
instruction::Instruction,
native_token::LAMPORTS_PER_SOL,
pubkey::Pubkey,
rent::Rent,
stake_history::Epoch,
system_instruction,
system_program,
},
solana_program_test::{
read_file,
BanksClient,
BanksClientError,
ProgramTest,
ProgramTestBanksClientExt,
read_file,
},
solana_sdk::{
account::Account,
Expand All @@ -33,16 +28,13 @@ use {
},
transaction::Transaction,
},
std::{
mem::size_of,
path::Path,
},
std::path::Path,
};

/// Simulator for the state of the target chain program on Solana. You can run solana transactions against
/// this struct to test how pyth instructions execute in the Solana runtime.
pub struct ProgramSimulator {
pub program_id: Pubkey,
pub program_id: Pubkey,
banks_client: BanksClient,
/// Hash used to submit the last transaction. The hash must be advanced for each new
/// transaction; otherwise, replayed transactions in different states can return stale
Expand All @@ -54,7 +46,6 @@ pub struct ProgramSimulator {
}

impl ProgramSimulator {

/// Deploys the target chain contract as upgradable
pub async fn new() -> ProgramSimulator {
let mut bpf_data = read_file(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
use {
solana_program::{
program_error::ProgramError,
pubkey::Pubkey,
instruction::Instruction,
crate::{
accounts as receiver_accounts,
instruction as receiver_instruction,
tests::simulator::ProgramSimulator,
},
anchor_lang::{
prelude::*,
InstructionData,
ToAccountMetas,
},
rand::rngs::OsRng,
solana_program::instruction::Instruction,
solana_sdk::{
signature::Signer,
keccak,
signature::Signer,
},
crate::instruction as receiver_instruction,
crate::accounts as receiver_accounts,
};
use anchor_lang::prelude::*;
use anchor_lang::Discriminator;
use anchor_lang::{
Owner,
ToAccountMetas,
InstructionData,
AnchorDeserialize,
};
use rand::rngs::OsRng;

use crate::tests::simulator::ProgramSimulator;

#[tokio::test]
async fn test_update_price() {
Expand All @@ -38,10 +32,20 @@ async fn test_update_price() {
let secp_message = libsecp256k1::Message::parse(&message_hash.0);
let (signature, recovery_id) = libsecp256k1::sign(&secp_message, &secp256k1_secret_key);

let accounts = receiver_accounts::Update { payer: sim.genesis_keypair.pubkey() }.to_account_metas(None);
let instruction_data = receiver_instruction::Update { data: message.to_vec(), recovery_id: recovery_id.serialize(), signature: signature.serialize() }.data();
let accounts = receiver_accounts::Update {
payer: sim.genesis_keypair.pubkey(),
}
.to_account_metas(None);
let instruction_data = receiver_instruction::Update {
data: message.to_vec(),
recovery_id: recovery_id.serialize(),
signature: signature.serialize(),
}
.data();

let inst = Instruction::new_with_bytes(sim.program_id, &instruction_data, accounts);

let result = sim.process_ix(inst, &vec![], &sim.genesis_keypair.insecure_clone()).await.unwrap();
sim.process_ix(inst, &vec![], &sim.genesis_keypair.insecure_clone())
.await
.unwrap();
}