pyth2wormhole-client attestation call
Change-Id: I407734b5fcf919f437a0e0a32d2b35e7ec50b999
This commit is contained in:
parent
5dbd3ea722
commit
7d6db92c35
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue