Implement pyth2wormhole for Terra
Change-Id: I3c206cf9850818c1fc012a593ad057e07b5dfa3e
This commit is contained in:
parent
c261a97a15
commit
0c747199ab
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["contracts/cw20-wrapped", "contracts/wormhole", "contracts/token-bridge"]
|
members = ["contracts/cw20-wrapped", "contracts/wormhole", "contracts/token-bridge", "contracts/pyth-bridge"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
@ -11,3 +11,6 @@ codegen-units = 1
|
||||||
panic = 'abort'
|
panic = 'abort'
|
||||||
incremental = false
|
incremental = false
|
||||||
overflow-checks = true
|
overflow-checks = true
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
memmap2 = { git = "https://github.com/certusone/wormhole", package = "memmap2" }
|
|
@ -3,4 +3,4 @@
|
||||||
docker run --rm -v "$(pwd)":/code \
|
docker run --rm -v "$(pwd)":/code \
|
||||||
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
|
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
|
||||||
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
|
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
|
||||||
cosmwasm/workspace-optimizer:0.10.7
|
cosmwasm/workspace-optimizer:0.12.1
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
[package]
|
||||||
|
name = "pyth-bridge"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Wormhole Contributors <contact@certus.one>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "Pyth price receiver"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
backtraces = ["cosmwasm-std/backtraces"]
|
||||||
|
# use library feature to disable all init/handle/query exports
|
||||||
|
library = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cosmwasm-std = { version = "0.16.0" }
|
||||||
|
cosmwasm-storage = { version = "0.16.0" }
|
||||||
|
schemars = "0.8.1"
|
||||||
|
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||||
|
serde_derive = { version = "1.0.103"}
|
||||||
|
cw20 = "0.8.0"
|
||||||
|
cw20-base = { version = "0.8.0", features = ["library"] }
|
||||||
|
cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
|
||||||
|
terraswap = "2.4.0"
|
||||||
|
wormhole = { path = "../wormhole", features = ["library"] }
|
||||||
|
thiserror = { version = "1.0.20" }
|
||||||
|
k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] }
|
||||||
|
sha3 = { version = "0.9.1", default-features = false }
|
||||||
|
generic-array = { version = "0.14.4" }
|
||||||
|
hex = "0.4.2"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
bigint = "4"
|
||||||
|
pyth-client = {git = "https://github.com/pyth-network/pyth-client-rs", branch = "v2"}
|
||||||
|
solana-program = "=1.7.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
cosmwasm-vm = { version = "0.16.0", default-features = false }
|
||||||
|
serde_json = "1.0"
|
|
@ -0,0 +1,176 @@
|
||||||
|
use cosmwasm_std::{
|
||||||
|
entry_point,
|
||||||
|
to_binary,
|
||||||
|
Binary,
|
||||||
|
CosmosMsg,
|
||||||
|
Deps,
|
||||||
|
DepsMut,
|
||||||
|
Env,
|
||||||
|
MessageInfo,
|
||||||
|
QueryRequest,
|
||||||
|
Response,
|
||||||
|
StdError,
|
||||||
|
StdResult,
|
||||||
|
WasmMsg,
|
||||||
|
WasmQuery,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
msg::{
|
||||||
|
ExecuteMsg,
|
||||||
|
InstantiateMsg,
|
||||||
|
MigrateMsg,
|
||||||
|
QueryMsg,
|
||||||
|
},
|
||||||
|
state::{
|
||||||
|
config,
|
||||||
|
config_read,
|
||||||
|
price_info,
|
||||||
|
price_info_read,
|
||||||
|
ConfigInfo,
|
||||||
|
UpgradeContract,
|
||||||
|
},
|
||||||
|
types::PriceAttestation,
|
||||||
|
};
|
||||||
|
use wormhole::{
|
||||||
|
byte_utils::get_string_from_32,
|
||||||
|
error::ContractError,
|
||||||
|
msg::QueryMsg as WormholeQueryMsg,
|
||||||
|
state::{
|
||||||
|
vaa_archive_add,
|
||||||
|
vaa_archive_check,
|
||||||
|
GovernancePacket,
|
||||||
|
ParsedVAA,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chain ID of Terra
|
||||||
|
const CHAIN_ID: u16 = 3;
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||||
|
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
|
||||||
|
Ok(Response::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||||
|
pub fn instantiate(
|
||||||
|
deps: DepsMut,
|
||||||
|
_env: Env,
|
||||||
|
_info: MessageInfo,
|
||||||
|
msg: InstantiateMsg,
|
||||||
|
) -> StdResult<Response> {
|
||||||
|
// Save general wormhole info
|
||||||
|
let state = ConfigInfo {
|
||||||
|
gov_chain: msg.gov_chain,
|
||||||
|
gov_address: msg.gov_address.as_slice().to_vec(),
|
||||||
|
wormhole_contract: msg.wormhole_contract,
|
||||||
|
pyth_emitter: msg.pyth_emitter.as_slice().to_vec(),
|
||||||
|
};
|
||||||
|
config(deps.storage).save(&state)?;
|
||||||
|
|
||||||
|
Ok(Response::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_vaa(deps: DepsMut, block_time: u64, data: &Binary) -> StdResult<ParsedVAA> {
|
||||||
|
let cfg = config_read(deps.storage).load()?;
|
||||||
|
let vaa: ParsedVAA = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
|
||||||
|
contract_addr: cfg.wormhole_contract.clone(),
|
||||||
|
msg: to_binary(&WormholeQueryMsg::VerifyVAA {
|
||||||
|
vaa: data.clone(),
|
||||||
|
block_time,
|
||||||
|
})?,
|
||||||
|
}))?;
|
||||||
|
Ok(vaa)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||||
|
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
|
||||||
|
match msg {
|
||||||
|
ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_vaa(
|
||||||
|
mut deps: DepsMut,
|
||||||
|
env: Env,
|
||||||
|
_info: MessageInfo,
|
||||||
|
data: &Binary,
|
||||||
|
) -> StdResult<Response> {
|
||||||
|
let state = config_read(deps.storage).load()?;
|
||||||
|
|
||||||
|
let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?;
|
||||||
|
let data = vaa.payload;
|
||||||
|
|
||||||
|
if vaa_archive_check(deps.storage, vaa.hash.as_slice()) {
|
||||||
|
return ContractError::VaaAlreadyExecuted.std_err();
|
||||||
|
}
|
||||||
|
vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
|
||||||
|
|
||||||
|
// check if vaa is from governance
|
||||||
|
if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
|
||||||
|
return handle_governance_payload(deps, env, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let message =
|
||||||
|
PriceAttestation::deserialize(&data[..]).map_err(|_| ContractError::InvalidVAA.std())?;
|
||||||
|
if vaa.emitter_address != state.pyth_emitter {
|
||||||
|
return ContractError::InvalidVAA.std_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update price
|
||||||
|
price_info(deps.storage).save(&message.product_id.to_bytes()[..], &data)?;
|
||||||
|
|
||||||
|
Ok(Response::new()
|
||||||
|
.add_attribute("action", "price_update")
|
||||||
|
.add_attribute("price_feed", message.product_id.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
|
||||||
|
let gov_packet = GovernancePacket::deserialize(&data)?;
|
||||||
|
let module = get_string_from_32(&gov_packet.module)?;
|
||||||
|
|
||||||
|
if module != "PythBridge" {
|
||||||
|
return Err(StdError::generic_err("this is not a valid module"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if gov_packet.chain != 0 && gov_packet.chain != CHAIN_ID {
|
||||||
|
return Err(StdError::generic_err(
|
||||||
|
"the governance VAA is for another chain",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
match gov_packet.action {
|
||||||
|
2u8 => handle_upgrade_contract(deps, env, &gov_packet.payload),
|
||||||
|
_ => ContractError::InvalidVAAAction.std_err(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_upgrade_contract(_deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
|
||||||
|
let UpgradeContract { new_contract } = UpgradeContract::deserialize(&data)?;
|
||||||
|
|
||||||
|
Ok(Response::new()
|
||||||
|
.add_message(CosmosMsg::Wasm(WasmMsg::Migrate {
|
||||||
|
contract_addr: env.contract.address.to_string(),
|
||||||
|
new_code_id: new_contract,
|
||||||
|
msg: to_binary(&MigrateMsg {})?,
|
||||||
|
}))
|
||||||
|
.add_attribute("action", "contract_upgrade"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||||
|
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
|
||||||
|
match msg {
|
||||||
|
QueryMsg::PriceInfo { product_id } => {
|
||||||
|
to_binary(&query_price_info(deps, product_id.as_slice())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query_price_info(deps: Deps, address: &[u8]) -> StdResult<PriceAttestation> {
|
||||||
|
match price_info_read(deps.storage).load(address) {
|
||||||
|
Ok(data) => PriceAttestation::deserialize(&data[..]).map_err(|_| {
|
||||||
|
StdError::parse_err("PriceAttestation", "failed to decode price attestation")
|
||||||
|
}),
|
||||||
|
Err(_) => ContractError::AssetNotFound.std_err(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
pub mod contract;
|
||||||
|
pub mod msg;
|
||||||
|
pub mod state;
|
||||||
|
pub mod types;
|
|
@ -0,0 +1,38 @@
|
||||||
|
use cosmwasm_std::{
|
||||||
|
Binary,
|
||||||
|
};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{
|
||||||
|
Deserialize,
|
||||||
|
Serialize,
|
||||||
|
};
|
||||||
|
|
||||||
|
type HumanAddr = String;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||||
|
pub struct InstantiateMsg {
|
||||||
|
// governance contract details
|
||||||
|
pub gov_chain: u16,
|
||||||
|
pub gov_address: Binary,
|
||||||
|
|
||||||
|
pub wormhole_contract: HumanAddr,
|
||||||
|
pub pyth_emitter: Binary,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ExecuteMsg {
|
||||||
|
SubmitVaa {
|
||||||
|
data: Binary,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct MigrateMsg {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum QueryMsg {
|
||||||
|
PriceInfo { product_id: Binary },
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{
|
||||||
|
Deserialize,
|
||||||
|
Serialize,
|
||||||
|
};
|
||||||
|
|
||||||
|
use cosmwasm_std::{
|
||||||
|
StdResult,
|
||||||
|
Storage,
|
||||||
|
};
|
||||||
|
use cosmwasm_storage::{
|
||||||
|
bucket,
|
||||||
|
bucket_read,
|
||||||
|
singleton,
|
||||||
|
singleton_read,
|
||||||
|
Bucket,
|
||||||
|
ReadonlyBucket,
|
||||||
|
ReadonlySingleton,
|
||||||
|
Singleton,
|
||||||
|
};
|
||||||
|
|
||||||
|
use wormhole::byte_utils::ByteUtils;
|
||||||
|
|
||||||
|
type HumanAddr = String;
|
||||||
|
|
||||||
|
pub static CONFIG_KEY: &[u8] = b"config";
|
||||||
|
pub static PRICE_INFO_KEY: &[u8] = b"price_info";
|
||||||
|
|
||||||
|
// Guardian set information
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||||
|
pub struct ConfigInfo {
|
||||||
|
// governance contract details
|
||||||
|
pub gov_chain: u16,
|
||||||
|
pub gov_address: Vec<u8>,
|
||||||
|
|
||||||
|
pub wormhole_contract: HumanAddr,
|
||||||
|
pub pyth_emitter: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
|
||||||
|
singleton(storage, CONFIG_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
|
||||||
|
singleton_read(storage, CONFIG_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn price_info(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
|
||||||
|
bucket(storage, PRICE_INFO_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn price_info_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
|
||||||
|
bucket_read(storage, PRICE_INFO_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct UpgradeContract {
|
||||||
|
pub new_contract: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpgradeContract {
|
||||||
|
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
|
||||||
|
let data = data.as_slice();
|
||||||
|
let new_contract = data.get_u64(24);
|
||||||
|
Ok(UpgradeContract { new_contract })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
pub mod pyth_extensions;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
convert::{
|
||||||
|
TryInto,
|
||||||
|
},
|
||||||
|
io::Read,
|
||||||
|
mem,
|
||||||
|
};
|
||||||
|
|
||||||
|
use solana_program::{
|
||||||
|
clock::UnixTimestamp,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::pyth_extensions::{
|
||||||
|
P2WCorpAction,
|
||||||
|
P2WEma,
|
||||||
|
P2WPriceStatus,
|
||||||
|
P2WPriceType,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constants and values common to every p2w custom-serialized message
|
||||||
|
|
||||||
|
/// Precedes every message implementing the p2w serialization format
|
||||||
|
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 {
|
||||||
|
PriceAttestation = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// On-chain data types
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
|
||||||
|
pub struct PriceAttestation {
|
||||||
|
pub product_id: Pubkey,
|
||||||
|
pub price_id: Pubkey,
|
||||||
|
pub price_type: P2WPriceType,
|
||||||
|
pub price: i64,
|
||||||
|
pub expo: i32,
|
||||||
|
pub twap: P2WEma,
|
||||||
|
pub twac: P2WEma,
|
||||||
|
pub confidence_interval: u64,
|
||||||
|
pub status: P2WPriceStatus,
|
||||||
|
pub corp_act: P2WCorpAction,
|
||||||
|
pub timestamp: UnixTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PriceAttestation {
|
||||||
|
/// 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)]
|
||||||
|
let PriceAttestation {
|
||||||
|
product_id,
|
||||||
|
price_id,
|
||||||
|
price_type,
|
||||||
|
price,
|
||||||
|
expo,
|
||||||
|
twap,
|
||||||
|
twac,
|
||||||
|
confidence_interval,
|
||||||
|
status,
|
||||||
|
corp_act,
|
||||||
|
timestamp,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
// magic
|
||||||
|
let mut buf = P2W_MAGIC.to_vec();
|
||||||
|
|
||||||
|
// version
|
||||||
|
buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
// payload_id
|
||||||
|
buf.push(PayloadId::PriceAttestation as u8);
|
||||||
|
|
||||||
|
// product_id
|
||||||
|
buf.extend_from_slice(&product_id.to_bytes()[..]);
|
||||||
|
|
||||||
|
// price_id
|
||||||
|
buf.extend_from_slice(&price_id.to_bytes()[..]);
|
||||||
|
|
||||||
|
// price_type
|
||||||
|
buf.push(price_type.clone() as u8);
|
||||||
|
|
||||||
|
// price
|
||||||
|
buf.extend_from_slice(&price.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
// exponent
|
||||||
|
buf.extend_from_slice(&expo.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
// twap
|
||||||
|
buf.append(&mut twap.serialize());
|
||||||
|
|
||||||
|
// twac
|
||||||
|
buf.append(&mut twac.serialize());
|
||||||
|
|
||||||
|
// confidence_interval
|
||||||
|
buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
// status
|
||||||
|
buf.push(status.clone() as u8);
|
||||||
|
|
||||||
|
// corp_act
|
||||||
|
buf.push(corp_act.clone() as u8);
|
||||||
|
|
||||||
|
// timestamp
|
||||||
|
buf.extend_from_slice(×tamp.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
pub fn deserialize(mut bytes: impl Read) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
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 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
//! This module contains 1:1 (or close) copies of selected Pyth types
|
||||||
|
//! with quick and dirty enhancements.
|
||||||
|
|
||||||
|
use std::{convert::TryInto, io::Read, mem};
|
||||||
|
|
||||||
|
use pyth_client::{
|
||||||
|
CorpAction,
|
||||||
|
Ema,
|
||||||
|
PriceStatus,
|
||||||
|
PriceType,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 1:1 Copy of pyth_client::PriceType with derived additional traits.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum P2WPriceType {
|
||||||
|
Unknown,
|
||||||
|
Price,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&PriceType> for P2WPriceType {
|
||||||
|
fn from(pt: &PriceType) -> Self {
|
||||||
|
match pt {
|
||||||
|
PriceType::Unknown => Self::Unknown,
|
||||||
|
PriceType::Price => Self::Price,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for P2WPriceType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 1:1 Copy of pyth_client::PriceStatus with derived additional traits.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
|
||||||
|
pub enum P2WPriceStatus {
|
||||||
|
Unknown,
|
||||||
|
Trading,
|
||||||
|
Halted,
|
||||||
|
Auction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&PriceStatus> for P2WPriceStatus {
|
||||||
|
fn from(ps: &PriceStatus) -> Self {
|
||||||
|
match ps {
|
||||||
|
PriceStatus::Unknown => Self::Unknown,
|
||||||
|
PriceStatus::Trading => Self::Trading,
|
||||||
|
PriceStatus::Halted => Self::Halted,
|
||||||
|
PriceStatus::Auction => Self::Auction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for P2WPriceStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Trading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 1:1 Copy of pyth_client::CorpAction with derived additional traits.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
|
||||||
|
pub enum P2WCorpAction {
|
||||||
|
NoCorpAct,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for P2WCorpAction {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::NoCorpAct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&CorpAction> for P2WCorpAction {
|
||||||
|
fn from(ca: &CorpAction) -> Self {
|
||||||
|
match ca {
|
||||||
|
CorpAction::NoCorpAct => P2WCorpAction::NoCorpAct,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 1:1 Copy of pyth_client::Ema with all-pub fields.
|
||||||
|
#[derive(Clone, Default, Debug, Eq, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct P2WEma {
|
||||||
|
pub val: i64,
|
||||||
|
pub numer: i64,
|
||||||
|
pub denom: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CAUTION: This impl may panic and requires an unsafe cast
|
||||||
|
impl From<&Ema> for P2WEma {
|
||||||
|
fn from(ema: &Ema) -> Self {
|
||||||
|
let our_size = mem::size_of::<P2WEma>();
|
||||||
|
let upstream_size = mem::size_of::<Ema>();
|
||||||
|
if our_size == upstream_size {
|
||||||
|
unsafe { std::mem::transmute_copy(ema) }
|
||||||
|
} else {
|
||||||
|
dbg!(our_size);
|
||||||
|
dbg!(upstream_size);
|
||||||
|
// Because of private upstream fields it's impossible to
|
||||||
|
// complain about type-level changes at compile-time
|
||||||
|
panic!("P2WEma sizeof mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CAUTION: This impl may panic and requires an unsafe cast
|
||||||
|
impl Into<Ema> for &P2WEma {
|
||||||
|
fn into(self) -> Ema {
|
||||||
|
let our_size = mem::size_of::<P2WEma>();
|
||||||
|
let upstream_size = mem::size_of::<Ema>();
|
||||||
|
if our_size == upstream_size {
|
||||||
|
unsafe { std::mem::transmute_copy(self) }
|
||||||
|
} else {
|
||||||
|
dbg!(our_size);
|
||||||
|
dbg!(upstream_size);
|
||||||
|
// Because of private upstream fields it's impossible to
|
||||||
|
// complain about type-level changes at compile-time
|
||||||
|
panic!("P2WEma sizeof mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl P2WEma {
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut v = vec![];
|
||||||
|
// val
|
||||||
|
v.extend(&self.val.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
// numer
|
||||||
|
v.extend(&self.numer.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
// denom
|
||||||
|
v.extend(&self.denom.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(mut bytes: impl Read) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue