pyth2wormhole-client attestation call

Change-Id: I407734b5fcf919f437a0e0a32d2b35e7ec50b999
This commit is contained in:
Stan Drozd 2021-08-02 14:52:24 +02:00
parent 5dbd3ea722
commit 7d6db92c35
7 changed files with 192 additions and 33 deletions

View File

@ -255,6 +255,7 @@ dependencies = [
"sha3",
"solana-program",
"solitaire",
"solitaire-client",
"wasm-bindgen",
]
@ -2078,6 +2079,7 @@ name = "pyth2wormhole-client"
version = "0.1.0"
dependencies = [
"borsh",
"bridge",
"clap 3.0.0-beta.2",
"env_logger",
"log",

View File

@ -6,13 +6,14 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["pyth2wormhole/client"]
default = ["pyth2wormhole/client", "bridge/client"]
[dependencies]
borsh = "0.8.1"
clap = "3.0.0-beta.2" # This beta assimilates structopt into clap
env_logger = "0.8.4"
log = "0.4.14"
bridge = {path = "../../bridge/program"}
pyth2wormhole = {path = "../program"}
shellexpand = "2.1.0"
solana-client = "=1.7.0"

View File

@ -24,7 +24,11 @@ pub struct Cli {
pub payer: String,
#[clap(long, default_value = "http://localhost:8899")]
pub rpc_url: String,
#[clap(long)]
pub p2w_addr: Pubkey,
/// The bridge program account
#[clap(long = "wh-prog")]
pub wh_prog: Pubkey,
#[clap(subcommand)]
pub action: Action,
}
@ -35,16 +39,18 @@ pub enum Action {
Init {
#[clap(long = "owner")]
new_owner_addr: Pubkey,
#[clap(long = "wormhole")]
wormhole_addr: Pubkey,
#[clap(long = "pyth")]
#[clap(long = "pyth-owner")]
pyth_owner_addr: Pubkey,
},
#[clap(
about = "Use an existing pyth2wormhole program to forward product price information to another chain"
about = "Use an existing pyth2wormhole program to attest product price information to another chain"
)]
Forward {
Attest {
#[clap(long = "product")]
product_addr: Pubkey,
#[clap(long = "price")]
price_addr: Pubkey,
#[clap(long)]
nonce: u32,
},
}

View File

@ -4,16 +4,60 @@ use borsh::BorshSerialize;
use clap::Clap;
use log::LevelFilter;
use solana_client::rpc_client::RpcClient;
use solana_program::{hash::Hash, pubkey::Pubkey};
use solana_sdk::{
commitment_config::CommitmentConfig, signature::read_keypair_file, transaction::Transaction,
use solana_program::{
hash::Hash,
instruction::{
AccountMeta,
Instruction,
},
pubkey::Pubkey,
system_program,
sysvar::{
clock,
rent,
},
};
use solana_sdk::{
commitment_config::CommitmentConfig,
signature::read_keypair_file,
transaction::Transaction,
};
use solitaire::{
processors::seeded::Seeded,
AccountState,
Derive,
Info,
};
use solitaire_client::{
AccEntry,
Keypair,
SolSigner,
ToInstruction,
};
use solitaire::{AccountState, processors::seeded::Seeded};
use solitaire_client::{AccEntry, Keypair, SolSigner, ToInstruction};
use cli::{Action, Cli};
use cli::{
Action,
Cli,
};
use pyth2wormhole::{config::P2WConfigAccount, initialize::InitializeAccounts, Pyth2WormholeConfig};
use bridge::{
accounts::{
Bridge,
FeeCollector,
Sequence,
SequenceDerivationData,
},
types::ConsistencyLevel,
CHAIN_ID_SOLANA,
};
use pyth2wormhole::{
config::P2WConfigAccount,
initialize::InitializeAccounts,
types::PriceAttestation,
AttestData,
Pyth2WormholeConfig,
};
pub type ErrBox = Box<dyn std::error::Error>;
@ -22,7 +66,7 @@ fn main() -> Result<(), ErrBox> {
init_logging(cli.log_level);
let payer = read_keypair_file(&*shellexpand::tilde(&cli.payer))?;
let rpc_client = RpcClient::new_with_commitment(cli.rpc_url, CommitmentConfig::processed());
let rpc_client = RpcClient::new_with_commitment(cli.rpc_url, CommitmentConfig::finalized());
let p2w_addr = cli.p2w_addr;
@ -31,19 +75,28 @@ fn main() -> Result<(), ErrBox> {
let tx = match cli.action {
Action::Init {
new_owner_addr,
wormhole_addr,
pyth_owner_addr,
} => handle_init(
payer,
p2w_addr,
new_owner_addr,
wormhole_addr,
cli.wh_prog,
pyth_owner_addr,
recent_blockhash,
)?,
Action::Forward { product_addr: _ } => {
todo!()
}
Action::Attest {
product_addr,
price_addr,
nonce,
} => handle_attest(
&rpc_client,
payer,
p2w_addr,
product_addr,
price_addr,
nonce,
recent_blockhash,
)?,
};
rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
@ -55,7 +108,7 @@ fn handle_init(
payer: Keypair,
p2w_addr: Pubkey,
new_owner_addr: Pubkey,
wormhole_addr: Pubkey,
wh_prog: Pubkey,
pyth_owner_addr: Pubkey,
recent_blockhash: Hash,
) -> Result<Transaction, ErrBox> {
@ -65,17 +118,17 @@ fn handle_init(
let accs = InitializeAccounts {
payer: Signer(payer),
new_config: Unprivileged(<P2WConfigAccount<'_, {AccountState::Uninitialized}>>::key(None, &p2w_addr)),
new_config: Derived(p2w_addr),
};
let config = Pyth2WormholeConfig {
owner: new_owner_addr,
wormhole_program_addr: wormhole_addr,
wh_prog: wh_prog,
pyth_owner: pyth_owner_addr,
};
let ix_data = pyth2wormhole::instruction::Instruction::Initialize(config);
let ix_data = (pyth2wormhole::instruction::Instruction::Initialize, config);
let (ix, signers) = accs.gen_client_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
&[ix],
@ -86,6 +139,86 @@ fn handle_init(
Ok(tx_signed)
}
fn handle_attest(
rpc: &RpcClient, // Needed for reading Pyth account data
payer: Keypair,
p2w_addr: Pubkey,
product_addr: Pubkey,
price_addr: Pubkey,
nonce: u32,
recent_blockhash: Hash,
) -> Result<Transaction, ErrBox> {
let emitter_keypair = Keypair::new();
let message_keypair = Keypair::new();
// Derive dynamic seeded accounts
let seq_addr = Sequence::key(
&SequenceDerivationData {
emitter_key: &emitter_keypair.pubkey(),
},
&wh_prog,
);
// Arrange Attest accounts
let acc_metas = vec![
// payer
AccountMeta::new(payer.pubkey(), true),
// system_program
AccountMeta::new_readonly(system_program::id(), false),
// config
AccountMeta::new_readonly(
P2WConfigAccount::<{ AccountState::Initialized }>::key(None, &p2w_addr),
false,
),
// pyth_product
AccountMeta::new_readonly(product_addr, false),
// pyth_price
AccountMeta::new_readonly(price_addr, false),
// clock
AccountMeta::new_readonly(clock::id(), false),
// wh_prog
AccountMeta::new_readonly(wh_prog, false),
// wh_bridge
AccountMeta::new(
Bridge::<{ AccountState::Initialized }>::key(None, &wh_prog),
false,
),
// wh_message
AccountMeta::new(message_keypair.pubkey(), true),
// wh_emitter
AccountMeta::new_readonly(emitter_keypair.pubkey(), true),
// wh_sequence
AccountMeta::new(seq_addr, false),
// wh_fee_collector
AccountMeta::new(FeeCollector::<'_>::key(None, &wh_prog), false),
AccountMeta::new_readonly(rent::id(), false),
];
let ix_data = (
pyth2wormhole::instruction::Instruction::Attest,
AttestData {
nonce,
consistency_level: ConsistencyLevel::Finalized,
},
);
let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
// Signers that use off-chain keypairs
let signer_keypairs = vec![&payer, &message_keypair, &emitter_keypair];
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
&[ix],
Some(&payer.pubkey()),
&signer_keypairs,
recent_blockhash,
);
Ok(tx_signed)
}
fn init_logging(verbosity: u32) {
use LevelFilter::*;
let filter = match verbosity {

View File

@ -54,7 +54,6 @@ pub struct Attest<'b> {
pub payer: Mut<Signer<Info<'b>>>,
pub system_program: Info<'b>,
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
pub wormhole_program: Info<'b>,
pub pyth_product: Info<'b>,
pub pyth_price: Info<'b>,
pub clock: Sysvar<'b, Clock>,

View File

@ -8,10 +8,11 @@ use crate::config::{P2WConfigAccount, Pyth2WormholeConfig};
#[derive(FromAccounts, ToInstruction)]
pub struct SetConfig<'b> {
/// New config to apply to the program
/// Current config used by the program
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
/// Current owner authority of the program
pub current_owner: Signer<Info<'b>>,
/// Payer account for updating the account data
pub payer: Signer<Info<'b>>,
}

View File

@ -21,18 +21,20 @@ pub use solana_sdk::{
use borsh::BorshSerialize;
use solitaire::{
AccountState,
Info,
Sysvar,
};
pub use solitaire::{
processors::seeded::Seeded,
Data,
Derive,
Keyed,
Owned,
Signer,
};
use solitaire::{
AccountState,
Info,
Mut,
Sysvar,
};
type StdResult<T, E> = std::result::Result<T, E>;
@ -104,12 +106,12 @@ impl<'a, 'b: 'a, T, const Seed: &'static str> Wrap for Derive<T, Seed> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::Derived(program_id) => {
let (k, extra_seed) = Pubkey::find_program_address(&[Seed.as_bytes()], &program_id);
let k = Self::key(None, program_id);
Ok(vec![AccountMeta::new(k, false)])
}
AccEntry::DerivedRO(program_id) => {
let (k, extra_seed) = Pubkey::find_program_address(&[Seed.as_bytes()], &program_id);
let k = Self::key(None, program_id);
Ok(vec![AccountMeta::new_readonly(k, false)])
}
@ -187,6 +189,21 @@ impl<'b> Wrap for Info<'b> {
}
}
impl<T: Wrap> Wrap for Mut<T> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::Unprivileged(_) | AccEntry::Signer(_) | AccEntry::Derived(_) => {
Ok(T::wrap(a)?)
}
_other => Err(format!(
"{} must be passed as Unprivileged, Signer or Derived (Must be mutable on-chain)",
std::any::type_name::<Self>()
)
.into()),
}
}
}
/// Trait used on client side to easily validate a program accounts + ix_data for a bare Solana call
pub trait ToInstruction {
fn to_ix(