pyth2wormhole: implement deserialization, print sequence
Change-Id: I39308ad6431df52f35a0496e71836497561640c5
This commit is contained in:
parent
92b75555b1
commit
a8465ef791
|
@ -2067,6 +2067,9 @@ dependencies = [
|
||||||
"bridge",
|
"bridge",
|
||||||
"pyth-client",
|
"pyth-client",
|
||||||
"rocksalt",
|
"rocksalt",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
"solitaire",
|
"solitaire",
|
||||||
"solitaire-client",
|
"solitaire-client",
|
||||||
|
@ -2087,6 +2090,7 @@ dependencies = [
|
||||||
"solana-client",
|
"solana-client",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
"solana-sdk",
|
"solana-sdk",
|
||||||
|
"solana-transaction-status",
|
||||||
"solitaire",
|
"solitaire",
|
||||||
"solitaire-client",
|
"solitaire-client",
|
||||||
]
|
]
|
||||||
|
|
|
@ -19,5 +19,6 @@ shellexpand = "2.1.0"
|
||||||
solana-client = "=1.7.0"
|
solana-client = "=1.7.0"
|
||||||
solana-program = "=1.7.0"
|
solana-program = "=1.7.0"
|
||||||
solana-sdk = "=1.7.0"
|
solana-sdk = "=1.7.0"
|
||||||
|
solana-transaction-status = "=1.7.0"
|
||||||
solitaire-client = {path = "../../solitaire/client"}
|
solitaire-client = {path = "../../solitaire/client"}
|
||||||
solitaire = {path = "../../solitaire/program"}
|
solitaire = {path = "../../solitaire/program"}
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSerialize,
|
||||||
|
};
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
use log::{LevelFilter, error};
|
use log::{
|
||||||
|
warn,
|
||||||
|
LevelFilter,
|
||||||
|
};
|
||||||
use solana_client::rpc_client::RpcClient;
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
|
@ -22,6 +28,7 @@ use solana_sdk::{
|
||||||
signature::read_keypair_file,
|
signature::read_keypair_file,
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
|
use solana_transaction_status::UiTransactionEncoding;
|
||||||
use solitaire::{
|
use solitaire::{
|
||||||
processors::seeded::Seeded,
|
processors::seeded::Seeded,
|
||||||
AccountState,
|
AccountState,
|
||||||
|
@ -52,6 +59,7 @@ use bridge::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use pyth2wormhole::{
|
use pyth2wormhole::{
|
||||||
|
attest::P2WEmitter,
|
||||||
config::P2WConfigAccount,
|
config::P2WConfigAccount,
|
||||||
initialize::InitializeAccounts,
|
initialize::InitializeAccounts,
|
||||||
set_config::SetConfigAccounts,
|
set_config::SetConfigAccounts,
|
||||||
|
@ -62,6 +70,8 @@ use pyth2wormhole::{
|
||||||
|
|
||||||
pub type ErrBox = Box<dyn std::error::Error>;
|
pub type ErrBox = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
|
pub const SEQNO_PREFIX: &'static str = "Program log: Sequence: ";
|
||||||
|
|
||||||
fn main() -> Result<(), ErrBox> {
|
fn main() -> Result<(), ErrBox> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
init_logging(cli.log_level);
|
init_logging(cli.log_level);
|
||||||
|
@ -87,7 +97,7 @@ fn main() -> Result<(), ErrBox> {
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
)?,
|
)?,
|
||||||
Action::SetConfig {
|
Action::SetConfig {
|
||||||
owner,
|
ref owner,
|
||||||
new_owner_addr,
|
new_owner_addr,
|
||||||
new_wh_prog,
|
new_wh_prog,
|
||||||
new_pyth_owner_addr,
|
new_pyth_owner_addr,
|
||||||
|
@ -115,7 +125,23 @@ fn main() -> Result<(), ErrBox> {
|
||||||
)?,
|
)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
let sig = rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
||||||
|
|
||||||
|
// To complete attestation, retrieve sequence number from transaction logs
|
||||||
|
if let Action::Attest { .. } = cli.action {
|
||||||
|
let this_tx = rpc_client.get_transaction(&sig, UiTransactionEncoding::Json)?;
|
||||||
|
|
||||||
|
if let Some(logs) = this_tx.transaction.meta.and_then(|meta| meta.log_messages) {
|
||||||
|
for log in logs {
|
||||||
|
if log.starts_with(SEQNO_PREFIX) {
|
||||||
|
let seqno = log.replace(SEQNO_PREFIX, "");
|
||||||
|
println!("Sequence number: {}", seqno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Could not get program logs for attestation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -203,17 +229,19 @@ fn handle_attest(
|
||||||
nonce: u32,
|
nonce: u32,
|
||||||
recent_blockhash: Hash,
|
recent_blockhash: Hash,
|
||||||
) -> Result<Transaction, ErrBox> {
|
) -> Result<Transaction, ErrBox> {
|
||||||
let emitter_keypair = Keypair::new();
|
|
||||||
let message_keypair = Keypair::new();
|
let message_keypair = Keypair::new();
|
||||||
|
|
||||||
|
let emitter_addr = P2WEmitter::key(None, &p2w_addr);
|
||||||
|
|
||||||
let p2w_config_addr = P2WConfigAccount::<{ AccountState::Initialized }>::key(None, &p2w_addr);
|
let p2w_config_addr = P2WConfigAccount::<{ AccountState::Initialized }>::key(None, &p2w_addr);
|
||||||
|
|
||||||
let config = Pyth2WormholeConfig::try_from_slice(rpc.get_account_data(&p2w_config_addr)?.as_slice())?;
|
let config =
|
||||||
|
Pyth2WormholeConfig::try_from_slice(rpc.get_account_data(&p2w_config_addr)?.as_slice())?;
|
||||||
|
|
||||||
// Derive dynamic seeded accounts
|
// Derive dynamic seeded accounts
|
||||||
let seq_addr = Sequence::key(
|
let seq_addr = Sequence::key(
|
||||||
&SequenceDerivationData {
|
&SequenceDerivationData {
|
||||||
emitter_key: &emitter_keypair.pubkey(),
|
emitter_key: &emitter_addr,
|
||||||
},
|
},
|
||||||
&config.wh_prog,
|
&config.wh_prog,
|
||||||
);
|
);
|
||||||
|
@ -225,10 +253,7 @@ fn handle_attest(
|
||||||
// system_program
|
// system_program
|
||||||
AccountMeta::new_readonly(system_program::id(), false),
|
AccountMeta::new_readonly(system_program::id(), false),
|
||||||
// config
|
// config
|
||||||
AccountMeta::new_readonly(
|
AccountMeta::new_readonly(p2w_config_addr, false),
|
||||||
p2w_config_addr,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
// pyth_product
|
// pyth_product
|
||||||
AccountMeta::new_readonly(product_addr, false),
|
AccountMeta::new_readonly(product_addr, false),
|
||||||
// pyth_price
|
// pyth_price
|
||||||
|
@ -245,7 +270,7 @@ fn handle_attest(
|
||||||
// wh_message
|
// wh_message
|
||||||
AccountMeta::new(message_keypair.pubkey(), true),
|
AccountMeta::new(message_keypair.pubkey(), true),
|
||||||
// wh_emitter
|
// wh_emitter
|
||||||
AccountMeta::new_readonly(emitter_keypair.pubkey(), true),
|
AccountMeta::new_readonly(emitter_addr, false),
|
||||||
// wh_sequence
|
// wh_sequence
|
||||||
AccountMeta::new(seq_addr, false),
|
AccountMeta::new(seq_addr, false),
|
||||||
// wh_fee_collector
|
// wh_fee_collector
|
||||||
|
@ -264,7 +289,7 @@ fn handle_attest(
|
||||||
let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
|
let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
|
||||||
|
|
||||||
// Signers that use off-chain keypairs
|
// Signers that use off-chain keypairs
|
||||||
let signer_keypairs = vec![&payer, &message_keypair, &emitter_keypair];
|
let signer_keypairs = vec![&payer, &message_keypair];
|
||||||
|
|
||||||
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
|
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
|
||||||
&[ix],
|
&[ix],
|
||||||
|
|
|
@ -13,7 +13,7 @@ default = ["bridge/no-entrypoint"]
|
||||||
client = ["solitaire/client", "solitaire-client", "no-entrypoint"]
|
client = ["solitaire/client", "solitaire-client", "no-entrypoint"]
|
||||||
trace = ["solitaire/trace", "bridge/trace"]
|
trace = ["solitaire/trace", "bridge/trace"]
|
||||||
no-entrypoint = []
|
no-entrypoint = []
|
||||||
wasm = ["no-entrypoint", "wasm-bindgen"]
|
wasm = ["no-entrypoint", "wasm-bindgen", "serde", "serde_derive", "serde_json"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bridge = {path = "../../bridge/program"}
|
bridge = {path = "../../bridge/program"}
|
||||||
|
@ -24,4 +24,8 @@ solana-program = "=1.7.0"
|
||||||
borsh = "0.8.1"
|
borsh = "0.8.1"
|
||||||
# NOTE: We're following bleeding edge to encounter format changes more easily
|
# NOTE: We're following bleeding edge to encounter format changes more easily
|
||||||
pyth-client = {git = "https://github.com/pyth-network/pyth-client-rs", branch = "v2"}
|
pyth-client = {git = "https://github.com/pyth-network/pyth-client-rs", branch = "v2"}
|
||||||
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true}
|
# Crates needed for easier wasm data passing
|
||||||
|
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true}
|
||||||
|
serde = { version = "1", optional = true}
|
||||||
|
serde_derive = { version = "1", optional = true}
|
||||||
|
serde_json = { version = "1", optional = true}
|
|
@ -40,12 +40,15 @@ use solitaire::{
|
||||||
Peel,
|
Peel,
|
||||||
Result as SoliResult,
|
Result as SoliResult,
|
||||||
Seeded,
|
Seeded,
|
||||||
|
invoke_seeded,
|
||||||
Signer,
|
Signer,
|
||||||
SolitaireError,
|
SolitaireError,
|
||||||
Sysvar,
|
Sysvar,
|
||||||
ToInstruction,
|
ToInstruction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub type P2WEmitter<'b> = Derive<Info<'b>, "p2w-emitter">;
|
||||||
|
|
||||||
#[derive(FromAccounts, ToInstruction)]
|
#[derive(FromAccounts, ToInstruction)]
|
||||||
pub struct Attest<'b> {
|
pub struct Attest<'b> {
|
||||||
// Payer also used for wormhole
|
// Payer also used for wormhole
|
||||||
|
@ -67,7 +70,7 @@ pub struct Attest<'b> {
|
||||||
pub wh_message: Signer<Mut<Info<'b>>>,
|
pub wh_message: Signer<Mut<Info<'b>>>,
|
||||||
|
|
||||||
/// Emitter of the VAA
|
/// Emitter of the VAA
|
||||||
pub wh_emitter: Info<'b>,
|
pub wh_emitter: P2WEmitter<'b>,
|
||||||
|
|
||||||
/// Tracker for the emitter sequence
|
/// Tracker for the emitter sequence
|
||||||
pub wh_sequence: Mut<Info<'b>>,
|
pub wh_sequence: Mut<Info<'b>>,
|
||||||
|
@ -163,7 +166,8 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
|
||||||
);
|
);
|
||||||
|
|
||||||
trace!("Before cross-call");
|
trace!("Before cross-call");
|
||||||
invoke(&ix, ctx.accounts)?;
|
|
||||||
|
invoke_seeded(&ix, ctx, &accs.wh_emitter, None)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
pub mod pyth_extensions;
|
pub mod pyth_extensions;
|
||||||
|
|
||||||
use std::mem;
|
use std::{
|
||||||
|
convert::{
|
||||||
|
TryFrom,
|
||||||
|
TryInto,
|
||||||
|
},
|
||||||
|
io::Read,
|
||||||
|
mem,
|
||||||
|
};
|
||||||
|
|
||||||
use borsh::BorshSerialize;
|
use borsh::BorshSerialize;
|
||||||
use pyth_client::{
|
use pyth_client::{
|
||||||
|
@ -11,9 +18,14 @@ use pyth_client::{
|
||||||
PriceStatus,
|
PriceStatus,
|
||||||
PriceType,
|
PriceType,
|
||||||
};
|
};
|
||||||
use solana_program::{clock::UnixTimestamp, program_error::ProgramError, pubkey::Pubkey};
|
use solana_program::{
|
||||||
|
clock::UnixTimestamp,
|
||||||
|
program_error::ProgramError,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
use solitaire::{
|
use solitaire::{
|
||||||
trace,
|
trace,
|
||||||
|
ErrBox,
|
||||||
Result as SoliResult,
|
Result as SoliResult,
|
||||||
SolitaireError,
|
SolitaireError,
|
||||||
};
|
};
|
||||||
|
@ -33,6 +45,8 @@ pub const P2W_MAGIC: &'static [u8] = b"P2WH";
|
||||||
/// Format version used and understood by this codebase
|
/// Format version used and understood by this codebase
|
||||||
pub const P2W_FORMAT_VERSION: u16 = 1;
|
pub const P2W_FORMAT_VERSION: u16 = 1;
|
||||||
|
|
||||||
|
pub const PUBKEY_LEN: usize = 32;
|
||||||
|
|
||||||
/// Decides the format of following bytes
|
/// Decides the format of following bytes
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum PayloadId {
|
pub enum PayloadId {
|
||||||
|
@ -42,6 +56,7 @@ pub enum PayloadId {
|
||||||
// On-chain data types
|
// On-chain data types
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||||
pub struct PriceAttestation {
|
pub struct PriceAttestation {
|
||||||
pub product_id: Pubkey,
|
pub product_id: Pubkey,
|
||||||
pub price_id: Pubkey,
|
pub price_id: Pubkey,
|
||||||
|
@ -57,7 +72,11 @@ pub struct PriceAttestation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceAttestation {
|
impl PriceAttestation {
|
||||||
pub fn from_pyth_price_bytes(price_id: Pubkey, timestamp: UnixTimestamp, value: &[u8]) -> Result<Self, SolitaireError> {
|
pub fn from_pyth_price_bytes(
|
||||||
|
price_id: Pubkey,
|
||||||
|
timestamp: UnixTimestamp,
|
||||||
|
value: &[u8],
|
||||||
|
) -> Result<Self, SolitaireError> {
|
||||||
let price = parse_pyth_price(value)?;
|
let price = parse_pyth_price(value)?;
|
||||||
|
|
||||||
Ok(PriceAttestation {
|
Ok(PriceAttestation {
|
||||||
|
@ -71,14 +90,14 @@ impl PriceAttestation {
|
||||||
confidence_interval: price.agg.conf,
|
confidence_interval: price.agg.conf,
|
||||||
status: (&price.agg.status).into(),
|
status: (&price.agg.status).into(),
|
||||||
corp_act: (&price.agg.corp_act).into(),
|
corp_act: (&price.agg.corp_act).into(),
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize this attestation according to the Pyth-over-wormhole serialization format
|
/// Serialize this attestation according to the Pyth-over-wormhole serialization format
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
// A nifty trick to get us yelled at if we forget to serialize a field
|
// A nifty trick to get us yelled at if we forget to serialize a field
|
||||||
#[deny(warnings)]
|
#[deny(warnings)]
|
||||||
let PriceAttestation {
|
let PriceAttestation {
|
||||||
product_id,
|
product_id,
|
||||||
price_id,
|
price_id,
|
||||||
|
@ -90,7 +109,7 @@ impl PriceAttestation {
|
||||||
confidence_interval,
|
confidence_interval,
|
||||||
status,
|
status,
|
||||||
corp_act,
|
corp_act,
|
||||||
timestamp
|
timestamp,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// magic
|
// magic
|
||||||
|
@ -123,20 +142,136 @@ impl PriceAttestation {
|
||||||
// twac
|
// twac
|
||||||
buf.append(&mut twac.serialize());
|
buf.append(&mut twac.serialize());
|
||||||
|
|
||||||
// confidence_interval
|
// confidence_interval
|
||||||
buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
|
buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
|
||||||
|
|
||||||
// status
|
// status
|
||||||
buf.push(status.clone() as u8);
|
buf.push(status.clone() as u8);
|
||||||
|
|
||||||
// corp_act
|
// corp_act
|
||||||
buf.push(corp_act.clone() as u8);
|
buf.push(corp_act.clone() as u8);
|
||||||
|
|
||||||
// timestamp
|
// timestamp
|
||||||
buf.extend_from_slice(×tamp.to_be_bytes()[..]);
|
buf.extend_from_slice(×tamp.to_be_bytes()[..]);
|
||||||
|
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
|
||||||
|
use P2WCorpAction::*;
|
||||||
|
use P2WPriceStatus::*;
|
||||||
|
use P2WPriceType::*;
|
||||||
|
|
||||||
|
println!("Using {} bytes for magic", P2W_MAGIC.len());
|
||||||
|
let mut magic_vec = vec![0u8; P2W_MAGIC.len()];
|
||||||
|
|
||||||
|
bytes.read_exact(magic_vec.as_mut_slice())?;
|
||||||
|
|
||||||
|
if magic_vec.as_slice() != P2W_MAGIC {
|
||||||
|
return Err(format!(
|
||||||
|
"Invalid magic {:02X?}, expected {:02X?}",
|
||||||
|
magic_vec, P2W_MAGIC,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
|
||||||
|
bytes.read_exact(version_vec.as_mut_slice())?;
|
||||||
|
let mut version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
if version != P2W_FORMAT_VERSION {
|
||||||
|
return Err(format!(
|
||||||
|
"Unsupported format version {}, expected {}",
|
||||||
|
version, P2W_FORMAT_VERSION
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
|
||||||
|
bytes.read_exact(payload_id_vec.as_mut_slice())?;
|
||||||
|
|
||||||
|
if PayloadId::PriceAttestation as u8 != payload_id_vec[0] {
|
||||||
|
return Err(format!(
|
||||||
|
"Invalid Payload ID {}, expected {}",
|
||||||
|
payload_id_vec[0],
|
||||||
|
PayloadId::PriceAttestation as u8,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut product_id_vec = vec![0u8; PUBKEY_LEN];
|
||||||
|
bytes.read_exact(product_id_vec.as_mut_slice())?;
|
||||||
|
let product_id = Pubkey::new(product_id_vec.as_slice());
|
||||||
|
|
||||||
|
let mut price_id_vec = vec![0u8; PUBKEY_LEN];
|
||||||
|
bytes.read_exact(price_id_vec.as_mut_slice())?;
|
||||||
|
let price_id = Pubkey::new(price_id_vec.as_slice());
|
||||||
|
|
||||||
|
let mut price_type_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
|
||||||
|
bytes.read_exact(price_type_vec.as_mut_slice())?;
|
||||||
|
let price_type = match price_type_vec[0] {
|
||||||
|
a if a == Price as u8 => Price,
|
||||||
|
a if a == P2WPriceType::Unknown as u8 => P2WPriceType::Unknown,
|
||||||
|
other => {
|
||||||
|
return Err(format!("Invalid price_type value {}", other).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut price_vec = vec![0u8; mem::size_of::<i64>()];
|
||||||
|
bytes.read_exact(price_vec.as_mut_slice())?;
|
||||||
|
let price = i64::from_be_bytes(price_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
let mut expo_vec = vec![0u8; mem::size_of::<i32>()];
|
||||||
|
bytes.read_exact(expo_vec.as_mut_slice())?;
|
||||||
|
let expo = i32::from_be_bytes(expo_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
let twap = P2WEma::deserialize(&mut bytes)?;
|
||||||
|
let twac = P2WEma::deserialize(&mut bytes)?;
|
||||||
|
|
||||||
|
println!("twac OK");
|
||||||
|
let mut confidence_interval_vec = vec![0u8; mem::size_of::<u64>()];
|
||||||
|
bytes.read_exact(confidence_interval_vec.as_mut_slice())?;
|
||||||
|
let confidence_interval = u64::from_be_bytes(confidence_interval_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
let mut status_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
|
||||||
|
bytes.read_exact(status_vec.as_mut_slice())?;
|
||||||
|
let status = match status_vec[0] {
|
||||||
|
a if a == P2WPriceStatus::Unknown as u8 => P2WPriceStatus::Unknown,
|
||||||
|
a if a == Trading as u8 => Trading,
|
||||||
|
a if a == Halted as u8 => Halted,
|
||||||
|
a if a == Auction as u8 => Auction,
|
||||||
|
other => {
|
||||||
|
return Err(format!("Invalid status value {}", other).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let mut corp_act_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
|
||||||
|
bytes.read_exact(corp_act_vec.as_mut_slice())?;
|
||||||
|
let corp_act = match corp_act_vec[0] {
|
||||||
|
a if a == NoCorpAct as u8 => NoCorpAct,
|
||||||
|
other => {
|
||||||
|
return Err(format!("Invalid corp_act value {}", other).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut timestamp_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
|
||||||
|
bytes.read_exact(timestamp_vec.as_mut_slice())?;
|
||||||
|
let timestamp = UnixTimestamp::from_be_bytes(timestamp_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
Ok( Self {
|
||||||
|
product_id,
|
||||||
|
price_id,
|
||||||
|
price_type,
|
||||||
|
price,
|
||||||
|
expo,
|
||||||
|
twap,
|
||||||
|
twac,
|
||||||
|
confidence_interval,
|
||||||
|
status,
|
||||||
|
corp_act,
|
||||||
|
timestamp
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserializes Price from raw bytes, sanity-check.
|
/// Deserializes Price from raw bytes, sanity-check.
|
||||||
|
@ -296,7 +431,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialize() -> SoliResult<()> {
|
fn test_serialize_deserialize() -> Result<(), ErrBox> {
|
||||||
let product_id_bytes = [21u8; 32];
|
let product_id_bytes = [21u8; 32];
|
||||||
let price_id_bytes = [222u8; 32];
|
let price_id_bytes = [222u8; 32];
|
||||||
println!("Hex product_id: {:02X?}", &product_id_bytes);
|
println!("Hex product_id: {:02X?}", &product_id_bytes);
|
||||||
|
@ -305,7 +440,7 @@ mod tests {
|
||||||
product_id: Pubkey::new_from_array(product_id_bytes),
|
product_id: Pubkey::new_from_array(product_id_bytes),
|
||||||
price_id: Pubkey::new_from_array(price_id_bytes),
|
price_id: Pubkey::new_from_array(price_id_bytes),
|
||||||
price: (0xdeadbeefdeadbabe as u64) as i64,
|
price: (0xdeadbeefdeadbabe as u64) as i64,
|
||||||
price_type: P2WPriceType::Price,
|
price_type: P2WPriceType::Price,
|
||||||
twap: P2WEma {
|
twap: P2WEma {
|
||||||
val: -42,
|
val: -42,
|
||||||
numer: 15,
|
numer: 15,
|
||||||
|
@ -317,15 +452,18 @@ mod tests {
|
||||||
denom: 2222,
|
denom: 2222,
|
||||||
},
|
},
|
||||||
expo: -3,
|
expo: -3,
|
||||||
status: P2WPriceStatus::Trading,
|
status: P2WPriceStatus::Trading,
|
||||||
confidence_interval: 101,
|
confidence_interval: 101,
|
||||||
corp_act: P2WCorpAction::NoCorpAct,
|
corp_act: P2WCorpAction::NoCorpAct,
|
||||||
timestamp: 123456789i64,
|
timestamp: 123456789i64,
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("Regular: {:#?}", &attestation);
|
println!("Regular: {:#?}", &attestation);
|
||||||
println!("Hex: {:#02X?}", &attestation);
|
println!("Hex: {:#02X?}", &attestation);
|
||||||
println!("Hex Bytes: {:02X?}", attestation.serialize());
|
let bytes = attestation.serialize();
|
||||||
|
println!("Hex Bytes: {:02X?}", bytes);
|
||||||
|
|
||||||
|
assert_eq!(PriceAttestation::deserialize(bytes.as_slice())?, attestation);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! This module contains 1:1 (or close) copies of selected Pyth types
|
//! This module contains 1:1 (or close) copies of selected Pyth types
|
||||||
//! with quick and dirty enhancements.
|
//! with quick and dirty enhancements.
|
||||||
|
|
||||||
use std::mem;
|
use std::{convert::TryInto, io::Read, mem};
|
||||||
|
|
||||||
use pyth_client::{
|
use pyth_client::{
|
||||||
CorpAction,
|
CorpAction,
|
||||||
|
@ -9,9 +9,11 @@ use pyth_client::{
|
||||||
PriceStatus,
|
PriceStatus,
|
||||||
PriceType,
|
PriceType,
|
||||||
};
|
};
|
||||||
|
use solitaire::ErrBox;
|
||||||
|
|
||||||
/// 1:1 Copy of pyth_client::PriceType with derived additional traits.
|
/// 1:1 Copy of pyth_client::PriceType with derived additional traits.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum P2WPriceType {
|
pub enum P2WPriceType {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -35,6 +37,7 @@ impl Default for P2WPriceType {
|
||||||
|
|
||||||
/// 1:1 Copy of pyth_client::PriceStatus with derived additional traits.
|
/// 1:1 Copy of pyth_client::PriceStatus with derived additional traits.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||||
pub enum P2WPriceStatus {
|
pub enum P2WPriceStatus {
|
||||||
Unknown,
|
Unknown,
|
||||||
Trading,
|
Trading,
|
||||||
|
@ -55,12 +58,13 @@ impl From<&PriceStatus> for P2WPriceStatus {
|
||||||
|
|
||||||
impl Default for P2WPriceStatus {
|
impl Default for P2WPriceStatus {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Trading
|
Self::Trading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 1:1 Copy of pyth_client::CorpAction with derived additional traits.
|
/// 1:1 Copy of pyth_client::CorpAction with derived additional traits.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||||
pub enum P2WCorpAction {
|
pub enum P2WCorpAction {
|
||||||
NoCorpAct,
|
NoCorpAct,
|
||||||
}
|
}
|
||||||
|
@ -81,6 +85,7 @@ impl From<&CorpAction> for P2WCorpAction {
|
||||||
|
|
||||||
/// 1:1 Copy of pyth_client::Ema with all-pub fields.
|
/// 1:1 Copy of pyth_client::Ema with all-pub fields.
|
||||||
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct P2WEma {
|
pub struct P2WEma {
|
||||||
pub val: i64,
|
pub val: i64,
|
||||||
|
@ -136,4 +141,20 @@ impl P2WEma {
|
||||||
|
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
|
||||||
|
let mut val_vec = vec![0u8; mem::size_of::<i64>()];
|
||||||
|
bytes.read_exact(val_vec.as_mut_slice())?;
|
||||||
|
let val = i64::from_be_bytes(val_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
let mut numer_vec = vec![0u8; mem::size_of::<i64>()];
|
||||||
|
bytes.read_exact(numer_vec.as_mut_slice())?;
|
||||||
|
let numer = i64::from_be_bytes(numer_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
let mut denom_vec = vec![0u8; mem::size_of::<i64>()];
|
||||||
|
bytes.read_exact(denom_vec.as_mut_slice())?;
|
||||||
|
let denom = i64::from_be_bytes(denom_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
Ok(Self { val, numer, denom })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,29 @@
|
||||||
|
use solitaire::Seeded;
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::{attest::P2WEmitter, types::PriceAttestation};
|
||||||
|
|
||||||
/// sanity check for wasm compilation, TODO(sdrozd): remove after
|
/// sanity check for wasm compilation, TODO(sdrozd): remove after
|
||||||
/// meaningful endpoints are added
|
/// meaningful endpoints are added
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn hello_p2w() -> String {
|
pub fn hello_p2w() -> String {
|
||||||
"Ciao mondo!".to_owned()
|
"Ciao mondo!".to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn get_emitter_address(program_id: String) -> Vec<u8> {
|
||||||
|
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
|
||||||
|
let emitter = P2WEmitter::key(None, &program_id);
|
||||||
|
|
||||||
|
emitter.to_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn parse_attestation(bytes: Vec<u8>) -> JsValue {
|
||||||
|
let a = PriceAttestation::deserialize(bytes.as_slice()).unwrap();
|
||||||
|
|
||||||
|
JsValue::from_serde(&a).unwrap()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue