wormhole/solana/solitaire/client/src/lib.rs

228 lines
7.0 KiB
Rust
Raw Normal View History

#![allow(incomplete_features)]
#![feature(adt_const_params)]
#![feature(const_generics_defaults)]
//! Client-specific code
pub use solana_program::pubkey::Pubkey;
use solana_program::sysvar::Sysvar as SolSysvar;
pub use solana_sdk;
pub use solana_sdk::{
instruction::{
AccountMeta,
Instruction,
},
signature::{
Keypair,
Signer as SolSigner,
},
};
use borsh::BorshSerialize;
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>;
pub type ErrBox = Box<dyn std::error::Error>;
/// The sum type for clearly specifying the accounts required on client side.
#[derive(Debug)]
pub enum AccEntry {
/// Least privileged account.
Unprivileged(Pubkey),
/// Least privileged account, read-only.
UnprivilegedRO(Pubkey),
/// Accounts that need to sign a Solana call
Signer(Keypair),
/// Accounts that need to sign a Solana call, read-only.
SignerRO(Keypair),
/// Program addresses for unprivileged cross calls
CPIProgram(Pubkey),
/// Program addresses for privileged cross calls
CPIProgramSigner(Keypair),
/// Key decided from SPL constants
Sysvar(Pubkey),
/// Key derived from constants and/or program address
Derived(Pubkey),
/// Key derived from constants and/or program address, read-only.
DerivedRO(Pubkey),
[WIP] Pr/drozdziak1/p2w batching/5e704f8b (#877) * ethereum: p2w contract -> p2w emitter, fill in essential envs Change-Id: I6fa9364a96738d2cc02ec829a31fedba0586d8e8 commit-id:0a56f1f8 * Add p2w-relay, a p2w-sdk integration test commit-id:6bfab639 * p2w-sdk: Expand README Change-Id: I17cb547d6aaddc240588923561c26d11a787df2e commit-id:6ebd6a22 * p2w-sdk: don't build ETH contracts, only the types Change-Id: I7cbd18328227700635d7688aa24a9671e8919fcd commit-id:adf079f7 * p2w: configurability and sane envs commit-id:f10fd90e * Solitaire: Implement Option<T> support in structs commit-id:31aa12d6 * bridge/governance.rs: Stop pestering about EMITTER_ADDRESS commit-id:d5bd7234 * p2w-attest: price batching This commit introduces support for multiple Pyth product/price pairs per call. The initial maximum batch size is 5 and is enforced using a `P2W_MAX_BATCH_SIZE` constant. solana/pyth2wormhole/program: * On-chain batching logic * Batch message parsing logic solana/pyth2wormhole/client: * Off-chain batching logic - divides any number of symbols into largest possible batches * Use a multi-symbol config file instead of CLI arguments third_party/pyth/p2w-sdk: * Expose batch parsing logic third_party/pyth/p2w-relay: * Comment out target chain calls until ETH contract supports batching * Test the batch parsing function third_party/pyth/p2w_autoattest.py: * Generate and use the symbol config file with pyth2wormhole-client third_party/pyth/pyth_publisher.py: * Add a configurable number of mock Pyth symbols * Adjust HTTP endpoint for multiple symbols commit-id:73787a61 * p2w-attest: mention attestation size in batch This commit ensures that no matter the attestation format, a batch will never contain attestations of different sizes. This guarantee enables forward compatibility by adding new constant-size fields at the end of a batch at all times. An older implementation will simply not consume the remaining newer values while respecting the stated batch member alignment. commit-id:210da230 * pyth2wormhole-client: use fresh blockhashes, harden batch errors This commit makes sure we don't have to deal with expired transactions due to stale blockhashes. The problem existed with larger symbol configs as well as on Solana mainnet. Additionally, the attestation logic now treats transaction errors as non-critical - a failure for a batch does not prevent attestation attempts for batches farther in the queue commit-id:5e704f8b
2022-02-23 10:12:16 -08:00
/// Empty value for nullables
Empty,
}
/// Types implementing Wrap are those that can be turned into a
/// partial account vector for a program call.
pub trait Wrap {
fn wrap(_: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox>;
/// If the implementor wants to sign using other AccEntry
/// variants, they should override this.
fn keypair(a: AccEntry) -> Option<Keypair> {
use AccEntry::*;
match a {
Signer(pair) => Some(pair),
SignerRO(pair) => Some(pair),
_other => None,
}
}
}
[WIP] Pr/drozdziak1/p2w batching/5e704f8b (#877) * ethereum: p2w contract -> p2w emitter, fill in essential envs Change-Id: I6fa9364a96738d2cc02ec829a31fedba0586d8e8 commit-id:0a56f1f8 * Add p2w-relay, a p2w-sdk integration test commit-id:6bfab639 * p2w-sdk: Expand README Change-Id: I17cb547d6aaddc240588923561c26d11a787df2e commit-id:6ebd6a22 * p2w-sdk: don't build ETH contracts, only the types Change-Id: I7cbd18328227700635d7688aa24a9671e8919fcd commit-id:adf079f7 * p2w: configurability and sane envs commit-id:f10fd90e * Solitaire: Implement Option<T> support in structs commit-id:31aa12d6 * bridge/governance.rs: Stop pestering about EMITTER_ADDRESS commit-id:d5bd7234 * p2w-attest: price batching This commit introduces support for multiple Pyth product/price pairs per call. The initial maximum batch size is 5 and is enforced using a `P2W_MAX_BATCH_SIZE` constant. solana/pyth2wormhole/program: * On-chain batching logic * Batch message parsing logic solana/pyth2wormhole/client: * Off-chain batching logic - divides any number of symbols into largest possible batches * Use a multi-symbol config file instead of CLI arguments third_party/pyth/p2w-sdk: * Expose batch parsing logic third_party/pyth/p2w-relay: * Comment out target chain calls until ETH contract supports batching * Test the batch parsing function third_party/pyth/p2w_autoattest.py: * Generate and use the symbol config file with pyth2wormhole-client third_party/pyth/pyth_publisher.py: * Add a configurable number of mock Pyth symbols * Adjust HTTP endpoint for multiple symbols commit-id:73787a61 * p2w-attest: mention attestation size in batch This commit ensures that no matter the attestation format, a batch will never contain attestations of different sizes. This guarantee enables forward compatibility by adding new constant-size fields at the end of a batch at all times. An older implementation will simply not consume the remaining newer values while respecting the stated batch member alignment. commit-id:210da230 * pyth2wormhole-client: use fresh blockhashes, harden batch errors This commit makes sure we don't have to deal with expired transactions due to stale blockhashes. The problem existed with larger symbol configs as well as on Solana mainnet. Additionally, the attestation logic now treats transaction errors as non-critical - a failure for a batch does not prevent attestation attempts for batches farther in the queue commit-id:5e704f8b
2022-02-23 10:12:16 -08:00
impl<T: Wrap> Wrap for Option<T> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::Empty => Ok(vec![AccountMeta::new_readonly(Pubkey::new_from_array([0u8; 32]), false)]),
other => T::wrap(other)
}
}
}
impl<'a, 'b: 'a, T> Wrap for Signer<T>
where
T: Keyed<'a, 'b>,
{
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
use AccEntry::*;
match a {
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
SignerRO(pair) => Ok(vec![AccountMeta::new_readonly(pair.pubkey(), true)]),
_other => Err(format!(
"{} must be passed as Signer or SignerRO",
std::any::type_name::<Self>()
)
.into()),
}
}
}
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 = Self::key(None, program_id);
Ok(vec![AccountMeta::new(k, false)])
}
AccEntry::DerivedRO(program_id) => {
let k = Self::key(None, program_id);
Ok(vec![AccountMeta::new_readonly(k, false)])
}
_other => Err(format!(
"{} must be passed as Derived or DerivedRO",
std::any::type_name::<Self>()
)
.into()),
}
}
}
impl<'a, T, const IS_INITIALIZED: AccountState> Wrap for Data<'a, T, IS_INITIALIZED>
where
T: BorshSerialize + Owned + Default,
{
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
use AccEntry::*;
use AccountState::*;
match IS_INITIALIZED {
Initialized => match a {
Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]),
UnprivilegedRO(k) => Ok(vec![AccountMeta::new_readonly(*k, false)]),
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
SignerRO(pair) => Ok(vec![AccountMeta::new_readonly(pair.pubkey(), true)]),
_other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged, Signer or the respective read-only variant", std::any::type_name::<Self>(), a).into())
},
Uninitialized => match a {
Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]),
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
_other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged or Signer (write access required for initialization)", std::any::type_name::<Self>(), a).into())
}
MaybeInitialized => match a {
Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]),
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
_other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged or Signer (write access required in case of initialization)", std::any::type_name::<Self>(), a).into())
}
}
}
}
impl<'b, Var> Wrap for Sysvar<'b, Var>
where
Var: SolSysvar,
{
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
if let AccEntry::Sysvar(k) = a {
if Var::check_id(k) {
Ok(vec![AccountMeta::new_readonly(*k, false)])
} else {
Err(format!(
"{} does not point at sysvar {}",
k,
std::any::type_name::<Var>()
)
.into())
}
} else {
Err(format!("{} must be passed as Sysvar", std::any::type_name::<Self>()).into())
}
}
}
impl<'b> Wrap for Info<'b> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::UnprivilegedRO(k) => Ok(vec![AccountMeta::new_readonly(*k, false)]),
AccEntry::Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]),
_other => Err(format!(
"{} must be passed as Unprivileged or UnprivilegedRO",
std::any::type_name::<Self>()
)
.into()),
}
}
}
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(
self,
program_id: Pubkey,
ix_data: &[u8],
) -> StdResult<(Instruction, Vec<Keypair>), ErrBox>;
}