pyth2wormhole-client attestation call
Change-Id: I407734b5fcf919f437a0e0a32d2b35e7ec50b999
This commit is contained in:
parent
5dbd3ea722
commit
7d6db92c35
|
@ -255,6 +255,7 @@ dependencies = [
|
||||||
"sha3",
|
"sha3",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
"solitaire",
|
"solitaire",
|
||||||
|
"solitaire-client",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2078,6 +2079,7 @@ name = "pyth2wormhole-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"borsh",
|
"borsh",
|
||||||
|
"bridge",
|
||||||
"clap 3.0.0-beta.2",
|
"clap 3.0.0-beta.2",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -6,13 +6,14 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["pyth2wormhole/client"]
|
default = ["pyth2wormhole/client", "bridge/client"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
borsh = "0.8.1"
|
borsh = "0.8.1"
|
||||||
clap = "3.0.0-beta.2" # This beta assimilates structopt into clap
|
clap = "3.0.0-beta.2" # This beta assimilates structopt into clap
|
||||||
env_logger = "0.8.4"
|
env_logger = "0.8.4"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
|
bridge = {path = "../../bridge/program"}
|
||||||
pyth2wormhole = {path = "../program"}
|
pyth2wormhole = {path = "../program"}
|
||||||
shellexpand = "2.1.0"
|
shellexpand = "2.1.0"
|
||||||
solana-client = "=1.7.0"
|
solana-client = "=1.7.0"
|
||||||
|
|
|
@ -24,7 +24,11 @@ pub struct Cli {
|
||||||
pub payer: String,
|
pub payer: String,
|
||||||
#[clap(long, default_value = "http://localhost:8899")]
|
#[clap(long, default_value = "http://localhost:8899")]
|
||||||
pub rpc_url: String,
|
pub rpc_url: String,
|
||||||
|
#[clap(long)]
|
||||||
pub p2w_addr: Pubkey,
|
pub p2w_addr: Pubkey,
|
||||||
|
/// The bridge program account
|
||||||
|
#[clap(long = "wh-prog")]
|
||||||
|
pub wh_prog: Pubkey,
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub action: Action,
|
pub action: Action,
|
||||||
}
|
}
|
||||||
|
@ -35,16 +39,18 @@ pub enum Action {
|
||||||
Init {
|
Init {
|
||||||
#[clap(long = "owner")]
|
#[clap(long = "owner")]
|
||||||
new_owner_addr: Pubkey,
|
new_owner_addr: Pubkey,
|
||||||
#[clap(long = "wormhole")]
|
#[clap(long = "pyth-owner")]
|
||||||
wormhole_addr: Pubkey,
|
|
||||||
#[clap(long = "pyth")]
|
|
||||||
pyth_owner_addr: Pubkey,
|
pyth_owner_addr: Pubkey,
|
||||||
},
|
},
|
||||||
#[clap(
|
#[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")]
|
#[clap(long = "product")]
|
||||||
product_addr: Pubkey,
|
product_addr: Pubkey,
|
||||||
|
#[clap(long = "price")]
|
||||||
|
price_addr: Pubkey,
|
||||||
|
#[clap(long)]
|
||||||
|
nonce: u32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,60 @@ use borsh::BorshSerialize;
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use solana_client::rpc_client::RpcClient;
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_program::{hash::Hash, pubkey::Pubkey};
|
use solana_program::{
|
||||||
use solana_sdk::{
|
hash::Hash,
|
||||||
commitment_config::CommitmentConfig, signature::read_keypair_file, transaction::Transaction,
|
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>;
|
pub type ErrBox = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
|
@ -22,7 +66,7 @@ fn main() -> Result<(), ErrBox> {
|
||||||
init_logging(cli.log_level);
|
init_logging(cli.log_level);
|
||||||
|
|
||||||
let payer = read_keypair_file(&*shellexpand::tilde(&cli.payer))?;
|
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;
|
let p2w_addr = cli.p2w_addr;
|
||||||
|
|
||||||
|
@ -31,19 +75,28 @@ fn main() -> Result<(), ErrBox> {
|
||||||
let tx = match cli.action {
|
let tx = match cli.action {
|
||||||
Action::Init {
|
Action::Init {
|
||||||
new_owner_addr,
|
new_owner_addr,
|
||||||
wormhole_addr,
|
|
||||||
pyth_owner_addr,
|
pyth_owner_addr,
|
||||||
} => handle_init(
|
} => handle_init(
|
||||||
payer,
|
payer,
|
||||||
p2w_addr,
|
p2w_addr,
|
||||||
new_owner_addr,
|
new_owner_addr,
|
||||||
wormhole_addr,
|
cli.wh_prog,
|
||||||
pyth_owner_addr,
|
pyth_owner_addr,
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
)?,
|
)?,
|
||||||
Action::Forward { product_addr: _ } => {
|
Action::Attest {
|
||||||
todo!()
|
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)?;
|
rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
||||||
|
@ -55,7 +108,7 @@ fn handle_init(
|
||||||
payer: Keypair,
|
payer: Keypair,
|
||||||
p2w_addr: Pubkey,
|
p2w_addr: Pubkey,
|
||||||
new_owner_addr: Pubkey,
|
new_owner_addr: Pubkey,
|
||||||
wormhole_addr: Pubkey,
|
wh_prog: Pubkey,
|
||||||
pyth_owner_addr: Pubkey,
|
pyth_owner_addr: Pubkey,
|
||||||
recent_blockhash: Hash,
|
recent_blockhash: Hash,
|
||||||
) -> Result<Transaction, ErrBox> {
|
) -> Result<Transaction, ErrBox> {
|
||||||
|
@ -65,17 +118,17 @@ fn handle_init(
|
||||||
|
|
||||||
let accs = InitializeAccounts {
|
let accs = InitializeAccounts {
|
||||||
payer: Signer(payer),
|
payer: Signer(payer),
|
||||||
new_config: Unprivileged(<P2WConfigAccount<'_, {AccountState::Uninitialized}>>::key(None, &p2w_addr)),
|
new_config: Derived(p2w_addr),
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = Pyth2WormholeConfig {
|
let config = Pyth2WormholeConfig {
|
||||||
owner: new_owner_addr,
|
owner: new_owner_addr,
|
||||||
wormhole_program_addr: wormhole_addr,
|
wh_prog: wh_prog,
|
||||||
pyth_owner: pyth_owner_addr,
|
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>>(
|
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
|
||||||
&[ix],
|
&[ix],
|
||||||
|
@ -86,6 +139,86 @@ fn handle_init(
|
||||||
Ok(tx_signed)
|
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) {
|
fn init_logging(verbosity: u32) {
|
||||||
use LevelFilter::*;
|
use LevelFilter::*;
|
||||||
let filter = match verbosity {
|
let filter = match verbosity {
|
||||||
|
|
|
@ -54,7 +54,6 @@ pub struct Attest<'b> {
|
||||||
pub payer: Mut<Signer<Info<'b>>>,
|
pub payer: Mut<Signer<Info<'b>>>,
|
||||||
pub system_program: Info<'b>,
|
pub system_program: Info<'b>,
|
||||||
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
|
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
|
||||||
pub wormhole_program: Info<'b>,
|
|
||||||
pub pyth_product: Info<'b>,
|
pub pyth_product: Info<'b>,
|
||||||
pub pyth_price: Info<'b>,
|
pub pyth_price: Info<'b>,
|
||||||
pub clock: Sysvar<'b, Clock>,
|
pub clock: Sysvar<'b, Clock>,
|
||||||
|
|
|
@ -8,10 +8,11 @@ use crate::config::{P2WConfigAccount, Pyth2WormholeConfig};
|
||||||
|
|
||||||
#[derive(FromAccounts, ToInstruction)]
|
#[derive(FromAccounts, ToInstruction)]
|
||||||
pub struct SetConfig<'b> {
|
pub struct SetConfig<'b> {
|
||||||
/// New config to apply to the program
|
/// Current config used by the program
|
||||||
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
|
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
|
||||||
/// Current owner authority of the program
|
/// Current owner authority of the program
|
||||||
pub current_owner: Signer<Info<'b>>,
|
pub current_owner: Signer<Info<'b>>,
|
||||||
|
/// Payer account for updating the account data
|
||||||
pub payer: Signer<Info<'b>>,
|
pub payer: Signer<Info<'b>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,18 +21,20 @@ pub use solana_sdk::{
|
||||||
|
|
||||||
use borsh::BorshSerialize;
|
use borsh::BorshSerialize;
|
||||||
|
|
||||||
use solitaire::{
|
|
||||||
AccountState,
|
|
||||||
Info,
|
|
||||||
Sysvar,
|
|
||||||
};
|
|
||||||
pub use solitaire::{
|
pub use solitaire::{
|
||||||
|
processors::seeded::Seeded,
|
||||||
Data,
|
Data,
|
||||||
Derive,
|
Derive,
|
||||||
Keyed,
|
Keyed,
|
||||||
Owned,
|
Owned,
|
||||||
Signer,
|
Signer,
|
||||||
};
|
};
|
||||||
|
use solitaire::{
|
||||||
|
AccountState,
|
||||||
|
Info,
|
||||||
|
Mut,
|
||||||
|
Sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
type StdResult<T, E> = std::result::Result<T, E>;
|
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> {
|
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
|
||||||
match a {
|
match a {
|
||||||
AccEntry::Derived(program_id) => {
|
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)])
|
Ok(vec![AccountMeta::new(k, false)])
|
||||||
}
|
}
|
||||||
AccEntry::DerivedRO(program_id) => {
|
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)])
|
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
|
/// Trait used on client side to easily validate a program accounts + ix_data for a bare Solana call
|
||||||
pub trait ToInstruction {
|
pub trait ToInstruction {
|
||||||
fn to_ix(
|
fn to_ix(
|
||||||
|
|
Loading…
Reference in New Issue