pyth2wormhole: implement deserialization, print sequence
Change-Id: I39308ad6431df52f35a0496e71836497561640c5
This commit is contained in:
parent
92b75555b1
commit
a8465ef791
|
@ -2067,6 +2067,9 @@ dependencies = [
|
|||
"bridge",
|
||||
"pyth-client",
|
||||
"rocksalt",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"solana-program",
|
||||
"solitaire",
|
||||
"solitaire-client",
|
||||
|
@ -2087,6 +2090,7 @@ dependencies = [
|
|||
"solana-client",
|
||||
"solana-program",
|
||||
"solana-sdk",
|
||||
"solana-transaction-status",
|
||||
"solitaire",
|
||||
"solitaire-client",
|
||||
]
|
||||
|
|
|
@ -19,5 +19,6 @@ shellexpand = "2.1.0"
|
|||
solana-client = "=1.7.0"
|
||||
solana-program = "=1.7.0"
|
||||
solana-sdk = "=1.7.0"
|
||||
solana-transaction-status = "=1.7.0"
|
||||
solitaire-client = {path = "../../solitaire/client"}
|
||||
solitaire = {path = "../../solitaire/program"}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
pub mod cli;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use borsh::{
|
||||
BorshDeserialize,
|
||||
BorshSerialize,
|
||||
};
|
||||
use clap::Clap;
|
||||
use log::{LevelFilter, error};
|
||||
use log::{
|
||||
warn,
|
||||
LevelFilter,
|
||||
};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_program::{
|
||||
hash::Hash,
|
||||
|
@ -22,6 +28,7 @@ use solana_sdk::{
|
|||
signature::read_keypair_file,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::UiTransactionEncoding;
|
||||
use solitaire::{
|
||||
processors::seeded::Seeded,
|
||||
AccountState,
|
||||
|
@ -52,6 +59,7 @@ use bridge::{
|
|||
};
|
||||
|
||||
use pyth2wormhole::{
|
||||
attest::P2WEmitter,
|
||||
config::P2WConfigAccount,
|
||||
initialize::InitializeAccounts,
|
||||
set_config::SetConfigAccounts,
|
||||
|
@ -62,6 +70,8 @@ use pyth2wormhole::{
|
|||
|
||||
pub type ErrBox = Box<dyn std::error::Error>;
|
||||
|
||||
pub const SEQNO_PREFIX: &'static str = "Program log: Sequence: ";
|
||||
|
||||
fn main() -> Result<(), ErrBox> {
|
||||
let cli = Cli::parse();
|
||||
init_logging(cli.log_level);
|
||||
|
@ -87,7 +97,7 @@ fn main() -> Result<(), ErrBox> {
|
|||
recent_blockhash,
|
||||
)?,
|
||||
Action::SetConfig {
|
||||
owner,
|
||||
ref owner,
|
||||
new_owner_addr,
|
||||
new_wh_prog,
|
||||
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(())
|
||||
}
|
||||
|
@ -203,17 +229,19 @@ fn handle_attest(
|
|||
nonce: u32,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<Transaction, ErrBox> {
|
||||
let emitter_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 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
|
||||
let seq_addr = Sequence::key(
|
||||
&SequenceDerivationData {
|
||||
emitter_key: &emitter_keypair.pubkey(),
|
||||
emitter_key: &emitter_addr,
|
||||
},
|
||||
&config.wh_prog,
|
||||
);
|
||||
|
@ -225,10 +253,7 @@ fn handle_attest(
|
|||
// system_program
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
// config
|
||||
AccountMeta::new_readonly(
|
||||
p2w_config_addr,
|
||||
false,
|
||||
),
|
||||
AccountMeta::new_readonly(p2w_config_addr, false),
|
||||
// pyth_product
|
||||
AccountMeta::new_readonly(product_addr, false),
|
||||
// pyth_price
|
||||
|
@ -245,7 +270,7 @@ fn handle_attest(
|
|||
// wh_message
|
||||
AccountMeta::new(message_keypair.pubkey(), true),
|
||||
// wh_emitter
|
||||
AccountMeta::new_readonly(emitter_keypair.pubkey(), true),
|
||||
AccountMeta::new_readonly(emitter_addr, false),
|
||||
// wh_sequence
|
||||
AccountMeta::new(seq_addr, false),
|
||||
// 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);
|
||||
|
||||
// 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>>(
|
||||
&[ix],
|
||||
|
|
|
@ -13,7 +13,7 @@ default = ["bridge/no-entrypoint"]
|
|||
client = ["solitaire/client", "solitaire-client", "no-entrypoint"]
|
||||
trace = ["solitaire/trace", "bridge/trace"]
|
||||
no-entrypoint = []
|
||||
wasm = ["no-entrypoint", "wasm-bindgen"]
|
||||
wasm = ["no-entrypoint", "wasm-bindgen", "serde", "serde_derive", "serde_json"]
|
||||
|
||||
[dependencies]
|
||||
bridge = {path = "../../bridge/program"}
|
||||
|
@ -24,4 +24,8 @@ solana-program = "=1.7.0"
|
|||
borsh = "0.8.1"
|
||||
# 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"}
|
||||
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,
|
||||
Result as SoliResult,
|
||||
Seeded,
|
||||
invoke_seeded,
|
||||
Signer,
|
||||
SolitaireError,
|
||||
Sysvar,
|
||||
ToInstruction,
|
||||
};
|
||||
|
||||
pub type P2WEmitter<'b> = Derive<Info<'b>, "p2w-emitter">;
|
||||
|
||||
#[derive(FromAccounts, ToInstruction)]
|
||||
pub struct Attest<'b> {
|
||||
// Payer also used for wormhole
|
||||
|
@ -67,7 +70,7 @@ pub struct Attest<'b> {
|
|||
pub wh_message: Signer<Mut<Info<'b>>>,
|
||||
|
||||
/// Emitter of the VAA
|
||||
pub wh_emitter: Info<'b>,
|
||||
pub wh_emitter: P2WEmitter<'b>,
|
||||
|
||||
/// Tracker for the emitter sequence
|
||||
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");
|
||||
invoke(&ix, ctx.accounts)?;
|
||||
|
||||
invoke_seeded(&ix, ctx, &accs.wh_emitter, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
pub mod pyth_extensions;
|
||||
|
||||
use std::mem;
|
||||
use std::{
|
||||
convert::{
|
||||
TryFrom,
|
||||
TryInto,
|
||||
},
|
||||
io::Read,
|
||||
mem,
|
||||
};
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use pyth_client::{
|
||||
|
@ -11,9 +18,14 @@ use pyth_client::{
|
|||
PriceStatus,
|
||||
PriceType,
|
||||
};
|
||||
use solana_program::{clock::UnixTimestamp, program_error::ProgramError, pubkey::Pubkey};
|
||||
use solana_program::{
|
||||
clock::UnixTimestamp,
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
use solitaire::{
|
||||
trace,
|
||||
ErrBox,
|
||||
Result as SoliResult,
|
||||
SolitaireError,
|
||||
};
|
||||
|
@ -33,6 +45,8 @@ pub const P2W_MAGIC: &'static [u8] = b"P2WH";
|
|||
/// Format version used and understood by this codebase
|
||||
pub const P2W_FORMAT_VERSION: u16 = 1;
|
||||
|
||||
pub const PUBKEY_LEN: usize = 32;
|
||||
|
||||
/// Decides the format of following bytes
|
||||
#[repr(u8)]
|
||||
pub enum PayloadId {
|
||||
|
@ -42,6 +56,7 @@ pub enum PayloadId {
|
|||
// On-chain data types
|
||||
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||
pub struct PriceAttestation {
|
||||
pub product_id: Pubkey,
|
||||
pub price_id: Pubkey,
|
||||
|
@ -57,7 +72,11 @@ pub struct 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)?;
|
||||
|
||||
Ok(PriceAttestation {
|
||||
|
@ -71,14 +90,14 @@ impl PriceAttestation {
|
|||
confidence_interval: price.agg.conf,
|
||||
status: (&price.agg.status).into(),
|
||||
corp_act: (&price.agg.corp_act).into(),
|
||||
timestamp: timestamp,
|
||||
timestamp: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Serialize this attestation according to the Pyth-over-wormhole serialization format
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
// A nifty trick to get us yelled at if we forget to serialize a field
|
||||
#[deny(warnings)]
|
||||
#[deny(warnings)]
|
||||
let PriceAttestation {
|
||||
product_id,
|
||||
price_id,
|
||||
|
@ -90,7 +109,7 @@ impl PriceAttestation {
|
|||
confidence_interval,
|
||||
status,
|
||||
corp_act,
|
||||
timestamp
|
||||
timestamp,
|
||||
} = self;
|
||||
|
||||
// magic
|
||||
|
@ -123,20 +142,136 @@ impl PriceAttestation {
|
|||
// twac
|
||||
buf.append(&mut twac.serialize());
|
||||
|
||||
// confidence_interval
|
||||
buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
|
||||
// confidence_interval
|
||||
buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
|
||||
|
||||
// status
|
||||
buf.push(status.clone() as u8);
|
||||
// status
|
||||
buf.push(status.clone() as u8);
|
||||
|
||||
// corp_act
|
||||
buf.push(corp_act.clone() as u8);
|
||||
// corp_act
|
||||
buf.push(corp_act.clone() as u8);
|
||||
|
||||
// timestamp
|
||||
buf.extend_from_slice(×tamp.to_be_bytes()[..]);
|
||||
// timestamp
|
||||
buf.extend_from_slice(×tamp.to_be_bytes()[..]);
|
||||
|
||||
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.
|
||||
|
@ -296,7 +431,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize() -> SoliResult<()> {
|
||||
fn test_serialize_deserialize() -> Result<(), ErrBox> {
|
||||
let product_id_bytes = [21u8; 32];
|
||||
let price_id_bytes = [222u8; 32];
|
||||
println!("Hex product_id: {:02X?}", &product_id_bytes);
|
||||
|
@ -305,7 +440,7 @@ mod tests {
|
|||
product_id: Pubkey::new_from_array(product_id_bytes),
|
||||
price_id: Pubkey::new_from_array(price_id_bytes),
|
||||
price: (0xdeadbeefdeadbabe as u64) as i64,
|
||||
price_type: P2WPriceType::Price,
|
||||
price_type: P2WPriceType::Price,
|
||||
twap: P2WEma {
|
||||
val: -42,
|
||||
numer: 15,
|
||||
|
@ -317,15 +452,18 @@ mod tests {
|
|||
denom: 2222,
|
||||
},
|
||||
expo: -3,
|
||||
status: P2WPriceStatus::Trading,
|
||||
status: P2WPriceStatus::Trading,
|
||||
confidence_interval: 101,
|
||||
corp_act: P2WCorpAction::NoCorpAct,
|
||||
timestamp: 123456789i64,
|
||||
corp_act: P2WCorpAction::NoCorpAct,
|
||||
timestamp: 123456789i64,
|
||||
};
|
||||
|
||||
println!("Regular: {:#?}", &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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! This module contains 1:1 (or close) copies of selected Pyth types
|
||||
//! with quick and dirty enhancements.
|
||||
|
||||
use std::mem;
|
||||
use std::{convert::TryInto, io::Read, mem};
|
||||
|
||||
use pyth_client::{
|
||||
CorpAction,
|
||||
|
@ -9,9 +9,11 @@ use pyth_client::{
|
|||
PriceStatus,
|
||||
PriceType,
|
||||
};
|
||||
use solitaire::ErrBox;
|
||||
|
||||
/// 1:1 Copy of pyth_client::PriceType with derived additional traits.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||
#[repr(u8)]
|
||||
pub enum P2WPriceType {
|
||||
Unknown,
|
||||
|
@ -35,6 +37,7 @@ impl Default for P2WPriceType {
|
|||
|
||||
/// 1:1 Copy of pyth_client::PriceStatus with derived additional traits.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||
pub enum P2WPriceStatus {
|
||||
Unknown,
|
||||
Trading,
|
||||
|
@ -55,12 +58,13 @@ impl From<&PriceStatus> for P2WPriceStatus {
|
|||
|
||||
impl Default for P2WPriceStatus {
|
||||
fn default() -> Self {
|
||||
Self::Trading
|
||||
Self::Trading
|
||||
}
|
||||
}
|
||||
|
||||
/// 1:1 Copy of pyth_client::CorpAction with derived additional traits.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||
pub enum P2WCorpAction {
|
||||
NoCorpAct,
|
||||
}
|
||||
|
@ -81,6 +85,7 @@ impl From<&CorpAction> for P2WCorpAction {
|
|||
|
||||
/// 1:1 Copy of pyth_client::Ema with all-pub fields.
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "wasm", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||
#[repr(C)]
|
||||
pub struct P2WEma {
|
||||
pub val: i64,
|
||||
|
@ -136,4 +141,20 @@ impl P2WEma {
|
|||
|
||||
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 std::str::FromStr;
|
||||
|
||||
use crate::{attest::P2WEmitter, types::PriceAttestation};
|
||||
|
||||
/// sanity check for wasm compilation, TODO(sdrozd): remove after
|
||||
/// meaningful endpoints are added
|
||||
#[wasm_bindgen]
|
||||
pub fn hello_p2w() -> String {
|
||||
"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