[solana receiver] Try solana signature verification builtin (#736)
* gr * wtf * wtf * jfc * jfc * oh thank god * make the test work * cleanup * cleanup
This commit is contained in:
parent
bbdd98fa06
commit
c3b2322ac0
|
@ -16,3 +16,4 @@ wallet = "~/.config/solana/id.json"
|
|||
deploy = "anchor deploy --program-keypair program_address.json --program-name solana-receiver"
|
||||
cli_build = "cargo build --package pyth-solana-receiver-cli"
|
||||
cli_test = "cargo run --package pyth-solana-receiver-cli post-and-receive-vaa -v AQAAAAABABPz9W9ARa/V06ZBp68lkoGJ3tlnJRZ2/jYU4Wi1jdQAdQj3ZkPLKIMtzN3EqaCAjKrLG/sKqADTbPoaBtn9zKUBZAYEhwAAAAAAAfNGGVrALzfWDU24/6bvdMsb41UAR1Q6Sp7prPTXhpewAAAAAApC0/gBUDJXSAADAAEAAQIAAwCdfVoraihHxJUfK+NKfkJ4r4DsPSm8uMcxEWv1D/av5l7/DsJkQsV9dFZpW4Q2lOc3mxXPGyULJ+DkfmV/GVWq/wAAAAAAG8iMAAAAAAAAARb////7AAAAAAAbz04AAAAAAAABfQEAAAACAAAACwAAAABkBgSHAAAAAGQGBIcAAAAAZAYEhwAAAAAAG8hzAAAAAAAAAS8AAAAAZAYEhD3tPwvP5dgslVQroHu37+dlyxeoTqcDWIDDMUz5Y0KBMhuk1gj6dbp21tc9qnFavL3rnboCJX8FobWReLSfWZsAAAAAACBQdgAAAAAAAANS////+wAAAAAAID+JAAAAAAAAA00BAAAAAgAAAAsAAAAAZAYEhwAAAABkBgSHAAAAAGQGBIYAAAAAACBQKwAAAAAAAAOdAAAAAGQGBIRPu3srgFy1FVEoeBBWS6f74PhYsJvdbggVRmGyUzFy1DChkVj1pUwK34+3VgYnND8iobyFK4nVa+GszcXb+W0OAAAAAAAcQ44AAAAAAAAAhP////0AAAAAABxBSQAAAAAAAACXAQAAAAIAAAATAAAAAGQGBIcAAAAAZAYEhwAAAABkBgSGAAAAAAAcQ28AAAAAAAAAZgAAAABkBgSE"
|
||||
test = "anchor build && cargo test"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -29,3 +29,8 @@ Transaction successful : 3L1vxzSHQv6B6TwtoMv2Y6m7vFGz3hzqApGHEhHSLA9Jn5dNKeWRWKv
|
|||
Receiver program ID is 5dXnHcDXdXaiEp9QgknCDPsEhJStSqZqJ4ATirWfEqeY
|
||||
Transaction successful : u5y9Hqc18so3BnjSUvZkLZR4mvA8zkiBgzGKHSEYyWkHQhH3uQatM7xWf4kdrhjZFVGbfBLdR8RJJUmuf28ePtG
|
||||
```
|
||||
|
||||
## Unit tests
|
||||
|
||||
Run `anchor run test` to run the unit tests in the `src/tests/` directory.
|
||||
**Warning**: do not confuse this command with `anchor test`, which doesn't do anything useful.
|
||||
|
|
|
@ -20,7 +20,15 @@ anchor-lang = "0.26.0"
|
|||
wormhole-core = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}
|
||||
wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}
|
||||
pyth-wormhole-attester-sdk = { path = "../../../../wormhole_attester/sdk/rust" }
|
||||
solana-program = "1.15.2"
|
||||
hex = "0.4.3"
|
||||
|
||||
[dev-dependencies]
|
||||
pyth-sdk = "0.5.0"
|
||||
pyth-sdk-solana = "0.1.0"
|
||||
solana-program-test = "=1.15.2"
|
||||
solana-sdk = "=1.15.2"
|
||||
tokio = "1.14.1"
|
||||
bincode = "1.3.3"
|
||||
libsecp256k1 = "0.7.1"
|
||||
rand = "0.8.5"
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
pub mod error;
|
||||
pub mod state;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use {
|
||||
wormhole::Chain::{
|
||||
self,
|
||||
|
@ -10,8 +13,9 @@ use {
|
|||
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");
|
||||
|
@ -42,6 +46,33 @@ pub mod pyth_solana_receiver {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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();
|
||||
hasher.hash(&data);
|
||||
hasher.result()
|
||||
};
|
||||
|
||||
// This costs about 25k compute units
|
||||
let recovered_pubkey = secp256k1_recover(
|
||||
&message_hash.0,
|
||||
recovery_id,
|
||||
&signature,
|
||||
).map_err(|_| ProgramError::InvalidArgument)?;
|
||||
|
||||
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.
|
||||
//
|
||||
// if recovered_pubkey.0 != AUTHORIZED_PUBLIC_KEY {
|
||||
// return Err(ProgramError::InvalidArgument);
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -64,34 +95,8 @@ impl crate::accounts::DecodePostedVaa {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pyth_sdk::Identifier;
|
||||
use pyth_wormhole_attester_sdk::PriceStatus;
|
||||
use pyth_wormhole_attester_sdk::PriceAttestation;
|
||||
|
||||
#[test]
|
||||
fn mock_attestation() {
|
||||
// TODO: create a VAA with this attestation as payload
|
||||
// and then invoke DecodePostedVaa
|
||||
|
||||
let _attestation = PriceAttestation {
|
||||
product_id: Identifier::new([18u8; 32]),
|
||||
price_id: Identifier::new([150u8; 32]),
|
||||
price: 0x2bad2feed7,
|
||||
conf: 101,
|
||||
ema_price: -42,
|
||||
ema_conf: 42,
|
||||
expo: -3,
|
||||
status: PriceStatus::Trading,
|
||||
num_publishers: 123212u32,
|
||||
max_num_publishers: 321232u32,
|
||||
attestation_time: (0xdeadbeeffadeu64) as i64,
|
||||
publish_time: 0xdadebeefi64,
|
||||
prev_publish_time: 0xdeadbabei64,
|
||||
prev_price: 0xdeadfacebeefi64,
|
||||
prev_conf: 0xbadbadbeefu64,
|
||||
last_attested_publish_time: (0xdeadbeeffadedeafu64) as i64,
|
||||
};
|
||||
}
|
||||
#[derive(Accounts)]
|
||||
pub struct Update<'info> {
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
mod simulator;
|
||||
mod test_update_price;
|
|
@ -0,0 +1,163 @@
|
|||
use {
|
||||
crate::ID,
|
||||
solana_program::{
|
||||
bpf_loader_upgradeable::{
|
||||
self,
|
||||
UpgradeableLoaderState,
|
||||
},
|
||||
hash::hash,
|
||||
hash::Hash,
|
||||
instruction::{
|
||||
AccountMeta,
|
||||
Instruction,
|
||||
},
|
||||
native_token::LAMPORTS_PER_SOL,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
stake_history::Epoch,
|
||||
system_instruction,
|
||||
system_program,
|
||||
},
|
||||
solana_program_test::{
|
||||
BanksClient,
|
||||
BanksClientError,
|
||||
ProgramTest,
|
||||
ProgramTestBanksClientExt,
|
||||
read_file,
|
||||
},
|
||||
solana_sdk::{
|
||||
account::Account,
|
||||
signature::{
|
||||
Keypair,
|
||||
Signer,
|
||||
},
|
||||
transaction::Transaction,
|
||||
},
|
||||
std::{
|
||||
mem::size_of,
|
||||
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,
|
||||
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
|
||||
/// results.
|
||||
last_blockhash: Hash,
|
||||
programdata_id: Pubkey,
|
||||
pub upgrade_authority: Keypair,
|
||||
pub genesis_keypair: Keypair,
|
||||
}
|
||||
|
||||
impl ProgramSimulator {
|
||||
|
||||
/// Deploys the target chain contract as upgradable
|
||||
pub async fn new() -> ProgramSimulator {
|
||||
let mut bpf_data = read_file(
|
||||
std::env::current_dir()
|
||||
.unwrap()
|
||||
.join(Path::new("../../target/deploy/pyth_solana_receiver.so")),
|
||||
);
|
||||
|
||||
|
||||
let mut program_test = ProgramTest::default();
|
||||
let program_key = Pubkey::try_from(ID).unwrap();
|
||||
// This PDA is the actual address in the real world
|
||||
// https://docs.rs/solana-program/1.6.4/solana_program/bpf_loader_upgradeable/index.html
|
||||
let (programdata_key, _) =
|
||||
Pubkey::find_program_address(&[&program_key.to_bytes()], &bpf_loader_upgradeable::id());
|
||||
|
||||
let upgrade_authority_keypair = Keypair::new();
|
||||
|
||||
let program_deserialized = UpgradeableLoaderState::Program {
|
||||
programdata_address: programdata_key,
|
||||
};
|
||||
let programdata_deserialized = UpgradeableLoaderState::ProgramData {
|
||||
slot: 1,
|
||||
upgrade_authority_address: Some(upgrade_authority_keypair.pubkey()),
|
||||
};
|
||||
|
||||
// Program contains a pointer to progradata
|
||||
let program_vec = bincode::serialize(&program_deserialized).unwrap();
|
||||
// Programdata contains a header and the binary of the program
|
||||
let mut programdata_vec = bincode::serialize(&programdata_deserialized).unwrap();
|
||||
programdata_vec.append(&mut bpf_data);
|
||||
|
||||
let program_account = Account {
|
||||
lamports: Rent::default().minimum_balance(program_vec.len()),
|
||||
data: program_vec,
|
||||
owner: bpf_loader_upgradeable::ID,
|
||||
executable: true,
|
||||
rent_epoch: Epoch::default(),
|
||||
};
|
||||
let programdata_account = Account {
|
||||
lamports: Rent::default().minimum_balance(programdata_vec.len()),
|
||||
data: programdata_vec,
|
||||
owner: bpf_loader_upgradeable::ID,
|
||||
executable: false,
|
||||
rent_epoch: Epoch::default(),
|
||||
};
|
||||
|
||||
// Add to both accounts to program test, now the program is deploy as upgradable
|
||||
program_test.add_account(program_key, program_account);
|
||||
program_test.add_account(programdata_key, programdata_account);
|
||||
|
||||
|
||||
// Start validator
|
||||
let (banks_client, genesis_keypair, recent_blockhash) = program_test.start().await;
|
||||
|
||||
let mut result = ProgramSimulator {
|
||||
program_id: program_key,
|
||||
banks_client,
|
||||
last_blockhash: recent_blockhash,
|
||||
programdata_id: programdata_key,
|
||||
upgrade_authority: upgrade_authority_keypair,
|
||||
genesis_keypair,
|
||||
};
|
||||
|
||||
// Transfer money to upgrade_authority so it can call the instructions
|
||||
result
|
||||
.airdrop(&result.upgrade_authority.pubkey(), 1000 * LAMPORTS_PER_SOL)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
/// Process a transaction containing `instruction` signed by `signers`.
|
||||
/// `payer` is used to pay for and sign the transaction.
|
||||
pub async fn process_ix(
|
||||
&mut self,
|
||||
instruction: Instruction,
|
||||
signers: &Vec<&Keypair>,
|
||||
payer: &Keypair,
|
||||
) -> Result<(), BanksClientError> {
|
||||
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||
|
||||
let blockhash = self
|
||||
.banks_client
|
||||
.get_new_latest_blockhash(&self.last_blockhash)
|
||||
.await
|
||||
.unwrap();
|
||||
self.last_blockhash = blockhash;
|
||||
|
||||
transaction.partial_sign(&[payer], self.last_blockhash);
|
||||
transaction.partial_sign(signers, self.last_blockhash);
|
||||
|
||||
self.banks_client.process_transaction(transaction).await
|
||||
}
|
||||
|
||||
/// Send `lamports` worth of SOL to the pubkey `to`.
|
||||
pub async fn airdrop(&mut self, to: &Pubkey, lamports: u64) -> Result<(), BanksClientError> {
|
||||
let instruction =
|
||||
system_instruction::transfer(&self.genesis_keypair.pubkey(), to, lamports);
|
||||
|
||||
self.process_ix(instruction, &vec![], &self.genesis_keypair.insecure_clone())
|
||||
.await
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
use {
|
||||
solana_program::{
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
instruction::Instruction,
|
||||
},
|
||||
solana_sdk::{
|
||||
signature::Signer,
|
||||
keccak,
|
||||
},
|
||||
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() {
|
||||
let mut sim = ProgramSimulator::new().await;
|
||||
|
||||
let message = b"hello world";
|
||||
let message_hash = {
|
||||
let mut hasher = keccak::Hasher::default();
|
||||
hasher.hash(message);
|
||||
hasher.result()
|
||||
};
|
||||
|
||||
let secp256k1_secret_key = libsecp256k1::SecretKey::random(&mut OsRng);
|
||||
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 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();
|
||||
}
|
Loading…
Reference in New Issue