Abehjati/repo-improvement (#395)

* Use a new rustfmt file

* Run cosmwasm action only when needed

* Remove pre-commit from remote-executor action

* Fix formatting

* Run precommit for all files

As previous PR was merged without pre-commit
This commit is contained in:
Ali Behjati 2022-11-25 11:16:58 +01:00 committed by GitHub
parent c3fefc78fa
commit becc216853
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1181 additions and 1195 deletions

View File

@ -1,14 +1,14 @@
on: on:
pull_request: pull_request:
paths: paths:
- ethereum/** - ethereum/**
- third_party/pyth/xc-governance-sdk-js/** - third_party/pyth/xc-governance-sdk-js/**
push: push:
branches: branches:
- main - main
paths: paths:
- ethereum/** - ethereum/**
- third_party/pyth/xc-governance-sdk-js/** - third_party/pyth/xc-governance-sdk-js/**
name: Ethereum Contract name: Ethereum Contract
@ -33,7 +33,7 @@ jobs:
uses: foundry-rs/foundry-toolchain@v1 uses: foundry-rs/foundry-toolchain@v1
with: with:
version: nightly version: nightly
- name: Install Forge dependencies - name: Install Forge dependencies
run: npm run install-forge-deps run: npm run install-forge-deps

View File

@ -2,9 +2,15 @@ name: Pyth CosmWasm Contract
on: on:
pull_request: pull_request:
paths:
- cosmwasm/**
- third_party/pyth/p2w-sdk/rust/**
push: push:
branches: branches:
- main - main
paths:
- cosmwasm/**
- third_party/pyth/p2w-sdk/rust/**
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always

View File

@ -17,7 +17,6 @@ jobs:
profile: minimal profile: minimal
toolchain: nightly toolchain: nightly
components: rustfmt, clippy components: rustfmt, clippy
- uses: pre-commit/action@v2.0.3
- name: Install Solana - name: Install Solana
run: | run: |
sh -c "$(curl -sSfL https://release.solana.com/stable/install)" sh -c "$(curl -sSfL https://release.solana.com/stable/install)"

View File

@ -17,10 +17,10 @@ repos:
- repo: local - repo: local
hooks: hooks:
# Hooks for the remote executor # Hooks for the remote executor
- id: cargo-fmt-executor - id: cargo-fmt-executor-remote-executor
name: Cargo format executor name: Cargo format executor for remote executor
language: "rust" language: "rust"
entry: cargo +nightly fmt --manifest-path ./pythnet/remote-executor/Cargo.toml --all entry: cargo +nightly fmt --manifest-path ./pythnet/remote-executor/Cargo.toml --all -- --config-path rustfmt.toml
pass_filenames: false pass_filenames: false
files: pythnet/remote-executor/ files: pythnet/remote-executor/
- id: cargo-clippy-executor - id: cargo-clippy-executor
@ -30,9 +30,16 @@ repos:
pass_filenames: false pass_filenames: false
files: pythnet/remote-executor/ files: pythnet/remote-executor/
# Hooks for the attester # Hooks for the attester
- id: cargo-fmt-executor - id: cargo-fmt-executor-attester
name: Cargo format executor name: Cargo format executor for attester
language: "rust" language: "rust"
entry: cargo +nightly fmt --manifest-path ./solana/pyth2wormhole/Cargo.toml --all entry: cargo +nightly fmt --manifest-path ./solana/pyth2wormhole/Cargo.toml --all -- --config-path rustfmt.toml
pass_filenames: false pass_filenames: false
files: solana/pyth2wormhole/ files: solana/pyth2wormhole/
# Hooks for cosmwasm contract
- id: cargo-fmt-executor-cosmwasm
name: Cargo format executor form cosmwasm contract
language: "rust"
entry: cargo +nightly fmt --manifest-path ./cosmwasm/Cargo.toml --all -- --config-path rustfmt.toml
pass_filenames: false
files: cosmwasm/

View File

@ -1,52 +1,52 @@
use std::collections::HashSet; use {
crate::{
use cosmwasm_std::{ error::PythContractError,
entry_point, msg::{
to_binary, ExecuteMsg,
Binary, InstantiateMsg,
Deps, MigrateMsg,
DepsMut, PriceFeedResponse,
Env, QueryMsg,
MessageInfo, },
QueryRequest, state::{
Response, config,
StdResult, config_read,
Timestamp, price_info,
WasmQuery, price_info_read,
ConfigInfo,
PriceInfo,
PythDataSource,
VALID_TIME_PERIOD,
},
},
cosmwasm_std::{
entry_point,
to_binary,
Binary,
Deps,
DepsMut,
Env,
MessageInfo,
QueryRequest,
Response,
StdResult,
Timestamp,
WasmQuery,
},
p2w_sdk::BatchPriceAttestation,
pyth_sdk_cw::{
PriceFeed,
PriceIdentifier,
PriceStatus,
ProductIdentifier,
},
std::collections::HashSet,
wormhole::{
msg::QueryMsg as WormholeQueryMsg,
state::ParsedVAA,
},
}; };
use pyth_sdk_cw::{
PriceFeed,
PriceIdentifier,
PriceStatus,
ProductIdentifier,
};
use crate::msg::{
ExecuteMsg,
InstantiateMsg,
MigrateMsg,
PriceFeedResponse,
QueryMsg,
};
use crate::state::{
config,
config_read,
price_info,
price_info_read,
ConfigInfo,
PriceInfo,
PythDataSource,
VALID_TIME_PERIOD,
};
use crate::error::PythContractError;
use p2w_sdk::BatchPriceAttestation;
use wormhole::msg::QueryMsg as WormholeQueryMsg;
use wormhole::state::ParsedVAA;
#[cfg_attr(not(feature = "library"), entry_point)] #[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> { pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::new()) Ok(Response::new())
@ -303,26 +303,27 @@ pub fn query_price_feed(deps: Deps, env: Env, address: &[u8]) -> StdResult<Price
price_feed: price_info.price_feed, price_feed: price_info.price_feed,
}) })
} }
Err(_) => Err(PythContractError::PriceFeedNotFound)? Err(_) => Err(PythContractError::PriceFeedNotFound)?,
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use cosmwasm_std::testing::{ use {
mock_dependencies, super::*,
mock_env, cosmwasm_std::{
mock_info, testing::{
MockApi, mock_dependencies,
MockQuerier, mock_env,
MockStorage, mock_info,
MockApi,
MockQuerier,
MockStorage,
},
Addr,
OwnedDeps,
},
}; };
use cosmwasm_std::{
Addr,
OwnedDeps,
};
use super::*;
fn setup_test() -> (OwnedDeps<MockStorage, MockApi, MockQuerier>, Env) { fn setup_test() -> (OwnedDeps<MockStorage, MockApi, MockQuerier>, Env) {
(mock_dependencies(), mock_env()) (mock_dependencies(), mock_env())

View File

@ -1,5 +1,7 @@
use cosmwasm_std::StdError; use {
use thiserror::Error; cosmwasm_std::StdError,
thiserror::Error,
};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum PythContractError { pub enum PythContractError {

View File

@ -1,12 +1,13 @@
use cosmwasm_std::Binary; use {
use schemars::JsonSchema; crate::state::PythDataSource,
use serde::{ cosmwasm_std::Binary,
Deserialize, schemars::JsonSchema,
Serialize, serde::{
Deserialize,
Serialize,
},
}; };
use crate::state::PythDataSource;
type HumanAddr = String; type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]

View File

@ -1,29 +1,30 @@
use std::collections::HashSet; use {
use std::time::Duration; cosmwasm_std::{
Addr,
use pyth_sdk_cw::PriceFeed; Binary,
use schemars::JsonSchema; Storage,
use serde::{ Timestamp,
Deserialize, },
Serialize, cosmwasm_storage::{
}; bucket,
bucket_read,
use cosmwasm_std::{ singleton,
Addr, singleton_read,
Binary, Bucket,
Storage, ReadonlyBucket,
Timestamp, ReadonlySingleton,
}; Singleton,
},
use cosmwasm_storage::{ pyth_sdk_cw::PriceFeed,
bucket, schemars::JsonSchema,
bucket_read, serde::{
singleton, Deserialize,
singleton_read, Serialize,
Bucket, },
ReadonlyBucket, std::{
ReadonlySingleton, collections::HashSet,
Singleton, time::Duration,
},
}; };
pub static CONFIG_KEY: &[u8] = b"config"; pub static CONFIG_KEY: &[u8] = b"config";

View File

@ -1,22 +0,0 @@
# Merge similar crates together to avoid multiple use statements.
imports_granularity = "Module"
# Consistency in formatting makes tool based searching/editing better.
empty_item_single_line = false
# Easier editing when arbitrary mixed use statements do not collapse.
imports_layout = "Vertical"
# Default rustfmt formatting of match arms with branches is awful.
match_arm_leading_pipes = "Preserve"
# Align Fields
enum_discrim_align_threshold = 80
struct_field_align_threshold = 80
# Allow up to two blank lines for grouping.
blank_lines_upper_bound = 2
# Wrap comments
comment_width = 120
wrap_comments = true

View File

@ -1,11 +1,13 @@
//! CLI options //! CLI options
use clap::{ use {
Parser, clap::{
Subcommand, Parser,
}; Subcommand,
use solana_sdk::{ },
commitment_config::CommitmentConfig, solana_sdk::{
pubkey::Pubkey, commitment_config::CommitmentConfig,
pubkey::Pubkey,
},
}; };
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -17,7 +19,7 @@ pub struct Cli {
#[clap(long, default_value = "confirmed")] #[clap(long, default_value = "confirmed")]
pub commitment: CommitmentConfig, pub commitment: CommitmentConfig,
#[clap(subcommand)] #[clap(subcommand)]
pub action: Action, pub action: Action,
} }
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
@ -25,7 +27,7 @@ pub enum Action {
#[clap(about = "Post a VAA and execute it through the remote executor")] #[clap(about = "Post a VAA and execute it through the remote executor")]
PostAndExecute { PostAndExecute {
#[clap(short = 'v', long = "vaa")] #[clap(short = 'v', long = "vaa")]
vaa: String, vaa: String,
#[clap( #[clap(
long, long,
default_value = "~/.config/solana/id.json", default_value = "~/.config/solana/id.json",
@ -47,22 +49,22 @@ pub enum Action {
#[clap(about = "Get set upgrade authority payload for squads-cli")] #[clap(about = "Get set upgrade authority payload for squads-cli")]
GetSetUpgradeAuthorityPayload { GetSetUpgradeAuthorityPayload {
#[clap(short, long, help = "Current authority")] #[clap(short, long, help = "Current authority")]
current: Pubkey, current: Pubkey,
#[clap(short, long, help = "New authority")] #[clap(short, long, help = "New authority")]
new: Pubkey, new: Pubkey,
#[clap(short, long, help = "Program id")] #[clap(short, long, help = "Program id")]
program_id: Pubkey, program_id: Pubkey,
}, },
#[clap(about = "Get upgrade program payload for squads-cli")] #[clap(about = "Get upgrade program payload for squads-cli")]
GetUpgradeProgramPayload { GetUpgradeProgramPayload {
#[clap(short, long, help = "Current authority")] #[clap(short, long, help = "Current authority")]
authority: Pubkey, authority: Pubkey,
#[clap(short, long, help = "Program id")] #[clap(short, long, help = "Program id")]
program_id: Pubkey, program_id: Pubkey,
#[clap(short, long, help = "New buffer")] #[clap(short, long, help = "New buffer")]
new_buffer: Pubkey, new_buffer: Pubkey,
#[clap(short, long, help = "Spill address")] #[clap(short, long, help = "Spill address")]
spill: Pubkey, spill: Pubkey,
}, },
#[clap(about = "Map solana key to pythnet key")] #[clap(about = "Map solana key to pythnet key")]
MapKey { MapKey {

View File

@ -1,69 +1,70 @@
#![deny(warnings)] #![deny(warnings)]
pub mod cli; pub mod cli;
use std::str::FromStr; use {
anchor_client::{
use anchor_client::{ anchor_lang::{
anchor_lang::{ AccountDeserialize,
AccountDeserialize, AnchorDeserialize,
AnchorDeserialize, AnchorSerialize,
AnchorSerialize, InstructionData as AnchorInstructionData,
InstructionData as AnchorInstructionData, Owner,
Owner, ToAccountMetas,
ToAccountMetas, },
solana_sdk::bpf_loader_upgradeable,
}, },
solana_sdk::bpf_loader_upgradeable, anyhow::Result,
}; clap::Parser,
use clap::Parser; cli::{
use cli::{ Action,
Action, Cli,
Cli,
};
use anyhow::Result;
use remote_executor::{
accounts::ExecutePostedVaa,
state::governance_payload::InstructionData,
EXECUTOR_KEY_SEED,
ID,
};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
instruction::{
AccountMeta,
Instruction,
}, },
pubkey::Pubkey, remote_executor::{
signature::{ accounts::ExecutePostedVaa,
read_keypair_file, state::{
Keypair, governance_payload::{
ExecutorPayload,
GovernanceHeader,
InstructionData,
},
posted_vaa::AnchorVaa,
},
EXECUTOR_KEY_SEED,
ID,
}, },
signer::Signer, solana_client::rpc_client::RpcClient,
system_instruction, solana_sdk::{
system_instruction::transfer, instruction::{
transaction::Transaction, AccountMeta,
}; Instruction,
use wormhole_solana::{ },
instructions::{ pubkey::Pubkey,
post_message, signature::{
post_vaa, read_keypair_file,
verify_signatures_txs, Keypair,
PostVAAData, },
signer::Signer,
system_instruction::{
self,
transfer,
},
transaction::Transaction,
}, },
Account, std::str::FromStr,
Config, wormhole::VAA,
FeeCollector, wormhole_solana::{
GuardianSet, instructions::{
VAA as PostedVAA, post_message,
}; post_vaa,
verify_signatures_txs,
use remote_executor::state::{ PostVAAData,
governance_payload::{ },
ExecutorPayload, Account,
GovernanceHeader, Config,
FeeCollector,
GuardianSet,
VAA as PostedVAA,
}, },
posted_vaa::AnchorVaa,
}; };
use wormhole::VAA;
fn main() -> Result<()> { fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
@ -130,15 +131,15 @@ fn main() -> Result<()> {
// Post VAA // Post VAA
let post_vaa_data = PostVAAData { let post_vaa_data = PostVAAData {
version: vaa.version, version: vaa.version,
guardian_set_index: vaa.guardian_set_index, guardian_set_index: vaa.guardian_set_index,
timestamp: vaa.timestamp, timestamp: vaa.timestamp,
nonce: vaa.nonce, nonce: vaa.nonce,
emitter_chain: vaa.emitter_chain.into(), emitter_chain: vaa.emitter_chain.into(),
emitter_address: vaa.emitter_address, emitter_address: vaa.emitter_address,
sequence: vaa.sequence, sequence: vaa.sequence,
consistency_level: vaa.consistency_level, consistency_level: vaa.consistency_level,
payload: vaa.payload, payload: vaa.payload,
}; };
process_transaction( process_transaction(
@ -184,7 +185,7 @@ fn main() -> Result<()> {
Config::try_from_slice(&rpc_client.get_account_data(&wormhole_config)?)?; Config::try_from_slice(&rpc_client.get_account_data(&wormhole_config)?)?;
let payload = ExecutorPayload { let payload = ExecutorPayload {
header: GovernanceHeader::executor_governance_header(), header: GovernanceHeader::executor_governance_header(),
instructions: vec![], instructions: vec![],
} }
.try_to_vec()?; .try_to_vec()?;
@ -212,7 +213,7 @@ fn main() -> Result<()> {
} }
Action::GetTestPayload {} => { Action::GetTestPayload {} => {
let payload = ExecutorPayload { let payload = ExecutorPayload {
header: GovernanceHeader::executor_governance_header(), header: GovernanceHeader::executor_governance_header(),
instructions: vec![], instructions: vec![],
} }
.try_to_vec()?; .try_to_vec()?;
@ -239,7 +240,7 @@ fn main() -> Result<()> {
instruction.accounts[2].is_signer = true; // Require signature of new authority for safety instruction.accounts[2].is_signer = true; // Require signature of new authority for safety
println!("New authority : {:}", instruction.accounts[2].pubkey); println!("New authority : {:}", instruction.accounts[2].pubkey);
let payload = ExecutorPayload { let payload = ExecutorPayload {
header: GovernanceHeader::executor_governance_header(), header: GovernanceHeader::executor_governance_header(),
instructions: vec![InstructionData::from(&instruction)], instructions: vec![InstructionData::from(&instruction)],
} }
.try_to_vec()?; .try_to_vec()?;
@ -261,7 +262,7 @@ fn main() -> Result<()> {
instruction.accounts[3].pubkey instruction.accounts[3].pubkey
); );
let payload = ExecutorPayload { let payload = ExecutorPayload {
header: GovernanceHeader::executor_governance_header(), header: GovernanceHeader::executor_governance_header(),
instructions: vec![InstructionData::from(&instruction)], instructions: vec![InstructionData::from(&instruction)],
} }
.try_to_vec()?; .try_to_vec()?;
@ -310,8 +311,8 @@ pub fn get_execute_instruction(
.0; .0;
account_metas.push(AccountMeta { account_metas.push(AccountMeta {
pubkey: executor_key, pubkey: executor_key,
is_signer: false, is_signer: false,
is_writable: true, is_writable: true,
}); });
@ -319,8 +320,8 @@ pub fn get_execute_instruction(
for instruction in executor_payload.instructions { for instruction in executor_payload.instructions {
// Push program_id // Push program_id
account_metas.push(AccountMeta { account_metas.push(AccountMeta {
pubkey: instruction.program_id, pubkey: instruction.program_id,
is_signer: false, is_signer: false,
is_writable: false, is_writable: false,
}); });
// Push other accounts // Push other accounts
@ -333,7 +334,7 @@ pub fn get_execute_instruction(
Ok(Instruction { Ok(Instruction {
program_id: ID, program_id: ID,
accounts: account_metas, accounts: account_metas,
data: remote_executor::instruction::ExecutePostedVaa.data(), data: remote_executor::instruction::ExecutePostedVaa.data(),
}) })
} }

View File

@ -1,19 +1,21 @@
#![deny(warnings)] #![deny(warnings)]
#![allow(clippy::result_large_err)] #![allow(clippy::result_large_err)]
use anchor_lang::{ use {
prelude::*, anchor_lang::{
solana_program::borsh::get_packed_len, prelude::*,
system_program, solana_program::borsh::get_packed_len,
}; system_program,
use error::ExecutorError; },
use state::{ error::ExecutorError,
claim_record::ClaimRecord, state::{
posted_vaa::AnchorVaa, claim_record::ClaimRecord,
}; posted_vaa::AnchorVaa,
use wormhole::Chain::{ },
self, wormhole::Chain::{
Solana, self,
Solana,
},
}; };
mod error; mod error;
@ -27,15 +29,15 @@ declare_id!("exe6S3AxPVNmy46L4Nj6HrnnAVQUhwyYzMSNcnRn3qq");
#[program] #[program]
pub mod remote_executor { pub mod remote_executor {
use anchor_lang::solana_program::{ use {
instruction::Instruction, super::*,
program::invoke_signed, crate::state::governance_payload::ExecutorPayload,
anchor_lang::solana_program::{
instruction::Instruction,
program::invoke_signed,
},
}; };
use crate::state::governance_payload::ExecutorPayload;
use super::*;
pub fn execute_posted_vaa(ctx: Context<ExecutePostedVaa>) -> Result<()> { pub fn execute_posted_vaa(ctx: Context<ExecutePostedVaa>) -> Result<()> {
let posted_vaa = &ctx.accounts.posted_vaa; let posted_vaa = &ctx.accounts.posted_vaa;
let claim_record = &mut ctx.accounts.claim_record; let claim_record = &mut ctx.accounts.claim_record;
@ -71,12 +73,12 @@ pub const CLAIM_RECORD_SEED: &str = "CLAIM_RECORD";
#[derive(Accounts)] #[derive(Accounts)]
pub struct ExecutePostedVaa<'info> { pub struct ExecutePostedVaa<'info> {
#[account(mut)] #[account(mut)]
pub payer: Signer<'info>, pub payer: Signer<'info>,
#[account(constraint = Chain::from(posted_vaa.emitter_chain) == Solana @ ExecutorError::EmitterChainNotSolana, constraint = posted_vaa.sequence > claim_record.sequence @ExecutorError::NonIncreasingSequence, constraint = (&posted_vaa.magic == b"vaa" || &posted_vaa.magic == b"msg" || &posted_vaa.magic == b"msu") @ExecutorError::PostedVaaHeaderWrongMagicNumber )] #[account(constraint = Chain::from(posted_vaa.emitter_chain) == Solana @ ExecutorError::EmitterChainNotSolana, constraint = posted_vaa.sequence > claim_record.sequence @ExecutorError::NonIncreasingSequence, constraint = (&posted_vaa.magic == b"vaa" || &posted_vaa.magic == b"msg" || &posted_vaa.magic == b"msu") @ExecutorError::PostedVaaHeaderWrongMagicNumber )]
pub posted_vaa: Account<'info, AnchorVaa>, pub posted_vaa: Account<'info, AnchorVaa>,
/// The reason claim_record has different seeds than executor_key is that executor key might need to pay in the CPI, so we want it to be a native wallet /// The reason claim_record has different seeds than executor_key is that executor key might need to pay in the CPI, so we want it to be a native wallet
#[account(init_if_needed, space = 8 + get_packed_len::<ClaimRecord>(), payer=payer, seeds = [CLAIM_RECORD_SEED.as_bytes(), &posted_vaa.emitter_address], bump)] #[account(init_if_needed, space = 8 + get_packed_len::<ClaimRecord>(), payer=payer, seeds = [CLAIM_RECORD_SEED.as_bytes(), &posted_vaa.emitter_address], bump)]
pub claim_record: Account<'info, ClaimRecord>, pub claim_record: Account<'info, ClaimRecord>,
pub system_program: Program<'info, System>, pub system_program: Program<'info, System>,
// Additional accounts passed to the instruction will be passed down to the CPIs. Very importantly executor_key needs to be passed as it will be the signer of the CPIs. // Additional accounts passed to the instruction will be passed down to the CPIs. Very importantly executor_key needs to be passed as it will be the signer of the CPIs.
// Below is the "anchor specification" of that account // Below is the "anchor specification" of that account

View File

@ -1,18 +1,18 @@
use std::{ use {
io::ErrorKind, crate::error::ExecutorError,
mem::size_of, anchor_lang::{
ops::Deref, prelude::*,
solana_program::instruction::Instruction,
},
boolinator::Boolinator,
std::{
io::ErrorKind,
mem::size_of,
ops::Deref,
},
wormhole::Chain,
}; };
use anchor_lang::{
prelude::*,
solana_program::instruction::Instruction,
};
use boolinator::Boolinator;
use wormhole::Chain;
use crate::error::ExecutorError;
pub const MAGIC_NUMBER: u32 = 0x4d475450; // Reverse order of the solidity contract because borsh uses little endian numbers (the solidity contract uses 0x5054474d) pub const MAGIC_NUMBER: u32 = 0x4d475450; // Reverse order of the solidity contract because borsh uses little endian numbers (the solidity contract uses 0x5054474d)
#[derive(AnchorDeserialize, AnchorSerialize, Debug, PartialEq, Eq)] #[derive(AnchorDeserialize, AnchorSerialize, Debug, PartialEq, Eq)]
@ -41,9 +41,9 @@ pub enum Action {
#[derive(AnchorDeserialize, AnchorSerialize, Eq, PartialEq, Debug)] #[derive(AnchorDeserialize, AnchorSerialize, Eq, PartialEq, Debug)]
pub struct GovernanceHeader { pub struct GovernanceHeader {
pub magic_number: u32, pub magic_number: u32,
pub module: Module, pub module: Module,
pub action: Action, pub action: Action,
pub chain: BigEndianU16, pub chain: BigEndianU16,
} }
impl GovernanceHeader { impl GovernanceHeader {
@ -51,9 +51,9 @@ impl GovernanceHeader {
pub fn executor_governance_header() -> Self { pub fn executor_governance_header() -> Self {
Self { Self {
magic_number: MAGIC_NUMBER, magic_number: MAGIC_NUMBER,
module: Module::Executor, module: Module::Executor,
action: Action::ExecutePostedVaa, action: Action::ExecutePostedVaa,
chain: BigEndianU16 { chain: BigEndianU16 {
value: Chain::Pythnet.try_into().unwrap(), value: Chain::Pythnet.try_into().unwrap(),
}, },
} }
@ -100,18 +100,18 @@ pub struct InstructionData {
/// Pubkey of the instruction processor that executes this instruction /// Pubkey of the instruction processor that executes this instruction
pub program_id: Pubkey, pub program_id: Pubkey,
/// Metadata for what accounts should be passed to the instruction processor /// Metadata for what accounts should be passed to the instruction processor
pub accounts: Vec<AccountMetaData>, pub accounts: Vec<AccountMetaData>,
/// Opaque data passed to the instruction processor /// Opaque data passed to the instruction processor
pub data: Vec<u8>, pub data: Vec<u8>,
} }
/// Account metadata used to define Instructions /// Account metadata used to define Instructions
#[derive(Clone, Debug, PartialEq, Eq, AnchorDeserialize, AnchorSerialize)] #[derive(Clone, Debug, PartialEq, Eq, AnchorDeserialize, AnchorSerialize)]
pub struct AccountMetaData { pub struct AccountMetaData {
/// An account's public key /// An account's public key
pub pubkey: Pubkey, pub pubkey: Pubkey,
/// True if an Instruction requires a Transaction signature matching `pubkey`. /// True if an Instruction requires a Transaction signature matching `pubkey`.
pub is_signer: bool, pub is_signer: bool,
/// True if the `pubkey` can be loaded as a read-write account. /// True if the `pubkey` can be loaded as a read-write account.
pub is_writable: bool, pub is_writable: bool,
} }
@ -120,16 +120,16 @@ impl From<&InstructionData> for Instruction {
fn from(instruction: &InstructionData) -> Self { fn from(instruction: &InstructionData) -> Self {
Instruction { Instruction {
program_id: instruction.program_id, program_id: instruction.program_id,
accounts: instruction accounts: instruction
.accounts .accounts
.iter() .iter()
.map(|a| AccountMeta { .map(|a| AccountMeta {
pubkey: a.pubkey, pubkey: a.pubkey,
is_signer: a.is_signer, is_signer: a.is_signer,
is_writable: a.is_writable, is_writable: a.is_writable,
}) })
.collect(), .collect(),
data: instruction.data.clone(), data: instruction.data.clone(),
} }
} }
} }
@ -138,16 +138,16 @@ impl From<&Instruction> for InstructionData {
fn from(instruction: &Instruction) -> Self { fn from(instruction: &Instruction) -> Self {
InstructionData { InstructionData {
program_id: instruction.program_id, program_id: instruction.program_id,
accounts: instruction accounts: instruction
.accounts .accounts
.iter() .iter()
.map(|a| AccountMetaData { .map(|a| AccountMetaData {
pubkey: a.pubkey, pubkey: a.pubkey,
is_signer: a.is_signer, is_signer: a.is_signer,
is_writable: a.is_writable, is_writable: a.is_writable,
}) })
.collect(), .collect(),
data: instruction.data.clone(), data: instruction.data.clone(),
} }
} }
} }
@ -170,24 +170,25 @@ impl ExecutorPayload {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use crate::{ use {
error, super::ExecutorPayload,
error::ExecutorError, crate::{
state::governance_payload::InstructionData, error,
}; error::ExecutorError,
state::governance_payload::InstructionData,
use super::ExecutorPayload; },
use anchor_lang::{ anchor_lang::{
prelude::Pubkey, prelude::Pubkey,
AnchorDeserialize, AnchorDeserialize,
AnchorSerialize, AnchorSerialize,
},
}; };
#[test] #[test]
fn test_check_deserialization_serialization() { fn test_check_deserialization_serialization() {
// No instructions // No instructions
let payload = ExecutorPayload { let payload = ExecutorPayload {
header: super::GovernanceHeader::executor_governance_header(), header: super::GovernanceHeader::executor_governance_header(),
instructions: vec![], instructions: vec![],
}; };

View File

@ -1,10 +1,12 @@
use anchor_lang::prelude::*; use {
use std::{ anchor_lang::prelude::*,
io::Write, std::{
ops::Deref, io::Write,
str::FromStr, ops::Deref,
str::FromStr,
},
wormhole_solana::VAA,
}; };
use wormhole_solana::VAA;
// The current chain's wormhole bridge owns the VAA accounts // The current chain's wormhole bridge owns the VAA accounts
impl Owner for AnchorVaa { impl Owner for AnchorVaa {
@ -50,5 +52,5 @@ impl Deref for AnchorVaa {
#[derive(Clone, AnchorDeserialize, AnchorSerialize)] #[derive(Clone, AnchorDeserialize, AnchorSerialize)]
pub struct AnchorVaa { pub struct AnchorVaa {
pub magic: [u8; 3], pub magic: [u8; 3],
pub vaa: VAA, pub vaa: VAA,
} }

View File

@ -1,68 +1,69 @@
use anchor_lang::{ use {
prelude::{ crate::{
AccountMeta, error::ExecutorError,
ProgramError, state::{
Pubkey, claim_record::ClaimRecord,
Rent, governance_payload::{
UpgradeableLoaderState, ExecutorPayload,
}, GovernanceHeader,
solana_program::hash::Hash, InstructionData,
AccountDeserialize, },
AnchorDeserialize, posted_vaa::AnchorVaa,
AnchorSerialize,
InstructionData as AnchorInstructionData,
Key,
Owner,
ToAccountMetas,
};
use solana_program_test::{
find_file,
read_file,
BanksClient,
BanksClientError,
ProgramTest,
ProgramTestBanksClientExt,
};
use solana_sdk::{
account::Account,
bpf_loader_upgradeable,
instruction::{
Instruction,
InstructionError,
},
signature::Keypair,
signer::Signer,
stake_history::Epoch,
system_instruction,
transaction::{
Transaction,
TransactionError,
},
};
use std::collections::HashMap;
use wormhole::Chain;
use wormhole_solana::VAA;
use crate::{
error::ExecutorError,
state::{
claim_record::ClaimRecord,
governance_payload::{
ExecutorPayload,
GovernanceHeader,
InstructionData,
}, },
posted_vaa::AnchorVaa, CLAIM_RECORD_SEED,
EXECUTOR_KEY_SEED,
}, },
CLAIM_RECORD_SEED, anchor_lang::{
EXECUTOR_KEY_SEED, prelude::{
AccountMeta,
ProgramError,
Pubkey,
Rent,
UpgradeableLoaderState,
},
solana_program::hash::Hash,
AccountDeserialize,
AnchorDeserialize,
AnchorSerialize,
InstructionData as AnchorInstructionData,
Key,
Owner,
ToAccountMetas,
},
solana_program_test::{
find_file,
read_file,
BanksClient,
BanksClientError,
ProgramTest,
ProgramTestBanksClientExt,
},
solana_sdk::{
account::Account,
bpf_loader_upgradeable,
instruction::{
Instruction,
InstructionError,
},
signature::Keypair,
signer::Signer,
stake_history::Epoch,
system_instruction,
transaction::{
Transaction,
TransactionError,
},
},
std::collections::HashMap,
wormhole::Chain,
wormhole_solana::VAA,
}; };
/// Bench for the tests, the goal of this struct is to be able to setup solana accounts before starting the local validator /// Bench for the tests, the goal of this struct is to be able to setup solana accounts before starting the local validator
pub struct ExecutorBench { pub struct ExecutorBench {
program_test: ProgramTest, program_test: ProgramTest,
program_id: Pubkey, program_id: Pubkey,
seqno: HashMap<Pubkey, u64>, seqno: HashMap<Pubkey, u64>,
} }
/// When passed to `add_vaa_account` modify the posted vaa in a way that makes the vaa invalid /// When passed to `add_vaa_account` modify the posted vaa in a way that makes the vaa invalid
@ -94,7 +95,7 @@ impl ExecutorBench {
programdata_address: programdata_key, programdata_address: programdata_key,
}; };
let programdata_deserialized = UpgradeableLoaderState::ProgramData { let programdata_deserialized = UpgradeableLoaderState::ProgramData {
slot: 1, slot: 1,
upgrade_authority_address: Some(upgrade_authority_keypair.pubkey()), upgrade_authority_address: Some(upgrade_authority_keypair.pubkey()),
}; };
@ -105,16 +106,16 @@ impl ExecutorBench {
programdata_vec.append(&mut bpf_data); programdata_vec.append(&mut bpf_data);
let program_account = Account { let program_account = Account {
lamports: Rent::default().minimum_balance(program_vec.len()), lamports: Rent::default().minimum_balance(program_vec.len()),
data: program_vec, data: program_vec,
owner: bpf_loader_upgradeable::ID, owner: bpf_loader_upgradeable::ID,
executable: true, executable: true,
rent_epoch: Epoch::default(), rent_epoch: Epoch::default(),
}; };
let programdata_account = Account { let programdata_account = Account {
lamports: Rent::default().minimum_balance(programdata_vec.len()), lamports: Rent::default().minimum_balance(programdata_vec.len()),
data: programdata_vec, data: programdata_vec,
owner: bpf_loader_upgradeable::ID, owner: bpf_loader_upgradeable::ID,
executable: false, executable: false,
rent_epoch: Epoch::default(), rent_epoch: Epoch::default(),
}; };
@ -166,7 +167,7 @@ impl ExecutorBench {
}; };
let payload = ExecutorPayload { let payload = ExecutorPayload {
header: GovernanceHeader::executor_governance_header(), header: GovernanceHeader::executor_governance_header(),
instructions: instructions instructions: instructions
.iter() .iter()
.map(|x| InstructionData::from(x)) .map(|x| InstructionData::from(x))
@ -177,7 +178,7 @@ impl ExecutorBench {
let vaa = AnchorVaa { let vaa = AnchorVaa {
magic: *vaa_magic, magic: *vaa_magic,
vaa: VAA { vaa: VAA {
vaa_version: 0, vaa_version: 0,
consistency_level: 0, consistency_level: 0,
vaa_time: 0, vaa_time: 0,
@ -232,10 +233,10 @@ impl ExecutorBench {
} }
} }
pub struct ExecutorSimulator { pub struct ExecutorSimulator {
banks_client: BanksClient, banks_client: BanksClient,
payer: Keypair, payer: Keypair,
last_blockhash: Hash, last_blockhash: Hash,
program_id: Pubkey, program_id: Pubkey,
} }
/// When passed to execute_posted_vaa, try to impersonate some of the accounts /// When passed to execute_posted_vaa, try to impersonate some of the accounts
@ -333,8 +334,8 @@ impl ExecutorSimulator {
// We need to add `executor_key` to the list of accounts // We need to add `executor_key` to the list of accounts
account_metas.push(AccountMeta { account_metas.push(AccountMeta {
pubkey: executor_key, pubkey: executor_key,
is_signer: false, is_signer: false,
is_writable: true, is_writable: true,
}); });
@ -342,8 +343,8 @@ impl ExecutorSimulator {
for instruction in executor_payload.instructions { for instruction in executor_payload.instructions {
// Push program_id // Push program_id
account_metas.push(AccountMeta { account_metas.push(AccountMeta {
pubkey: instruction.program_id, pubkey: instruction.program_id,
is_signer: false, is_signer: false,
is_writable: false, is_writable: false,
}); });
// Push other accounts // Push other accounts
@ -356,8 +357,8 @@ impl ExecutorSimulator {
let instruction = Instruction { let instruction = Instruction {
program_id: self.program_id, program_id: self.program_id,
accounts: account_metas, accounts: account_metas,
data: crate::instruction::ExecutePostedVaa.data(), data: crate::instruction::ExecutePostedVaa.data(),
}; };
self.process_ix(instruction, signers).await self.process_ix(instruction, signers).await

View File

@ -1,21 +1,22 @@
use crate::error::ExecutorError; use {
super::executor_simulator::{
use super::executor_simulator::{ ExecutorAttack,
ExecutorAttack, ExecutorBench,
ExecutorBench, VaaAttack,
VaaAttack, },
}; crate::error::ExecutorError,
use anchor_lang::prelude::{ anchor_lang::prelude::{
ErrorCode, ErrorCode,
ProgramError, ProgramError,
Pubkey, Pubkey,
Rent, Rent,
}; },
use solana_sdk::{ solana_sdk::{
instruction::InstructionError, instruction::InstructionError,
native_token::LAMPORTS_PER_SOL, native_token::LAMPORTS_PER_SOL,
system_instruction::transfer, system_instruction::transfer,
transaction::TransactionError, transaction::TransactionError,
},
}; };
#[tokio::test] #[tokio::test]

View File

@ -1,28 +1,29 @@
use crate::{ use {
error::ExecutorError, super::executor_simulator::ExecutorBench,
tests::executor_simulator::{ crate::{
ExecutorAttack, error::ExecutorError,
VaaAttack, tests::executor_simulator::{
ExecutorAttack,
VaaAttack,
},
}, },
}; anchor_lang::{
prelude::{
use super::executor_simulator::ExecutorBench; Pubkey,
use anchor_lang::{ Rent,
prelude::{ },
Pubkey, solana_program::{
Rent, system_instruction::create_account,
system_program,
},
}, },
solana_program::{ solana_sdk::{
system_instruction::create_account, native_token::LAMPORTS_PER_SOL,
system_program, signature::Keypair,
signer::Signer,
system_instruction::transfer,
}, },
}; };
use solana_sdk::{
native_token::LAMPORTS_PER_SOL,
signature::Keypair,
signer::Signer,
system_instruction::transfer,
};
#[tokio::test] #[tokio::test]
/// This test file tests that the executor can : /// This test file tests that the executor can :

View File

@ -1,11 +0,0 @@
# Merge similar crates together to avoid multiple use statements.
imports_granularity = "Crate"
# Consistency in formatting makes tool based searching/editing better.
empty_item_single_line = false
# Easier editing when arbitrary mixed use statements do not collapse.
imports_layout = "Vertical"
# Default rustfmt formatting of match arms with branches is awful.
match_arm_leading_pipes = "Preserve"

17
rustfmt.toml Normal file
View File

@ -0,0 +1,17 @@
# Merge all imports into a clean vertical list of module imports.
imports_granularity = "One"
group_imports = "One"
imports_layout = "Vertical"
# Better grep-ability.
empty_item_single_line = false
# Consistent pipe layout.
match_arm_leading_pipes = "Preserve"
# Align Fields
enum_discrim_align_threshold = 80
struct_field_align_threshold = 80
# Allow up to two blank lines for visual grouping.
blank_lines_upper_bound = 2

View File

@ -1,39 +1,38 @@
use std::{ use {
collections::{ crate::BatchState,
HashMap, log::info,
HashSet, serde::{
de::Error,
Deserialize,
Deserializer,
Serialize,
Serializer,
},
solana_program::pubkey::Pubkey,
std::{
collections::{
HashMap,
HashSet,
},
iter,
str::FromStr,
}, },
iter,
str::FromStr,
}; };
use log::info;
use serde::{
de::Error,
Deserialize,
Deserializer,
Serialize,
Serializer,
};
use solana_program::pubkey::Pubkey;
use crate::BatchState;
/// Pyth2wormhole config specific to attestation requests /// Pyth2wormhole config specific to attestation requests
#[derive(Clone, Debug, Hash, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Hash, Deserialize, Serialize, PartialEq)]
pub struct AttestationConfig { pub struct AttestationConfig {
#[serde(default = "default_min_msg_reuse_interval_ms")] #[serde(default = "default_min_msg_reuse_interval_ms")]
pub min_msg_reuse_interval_ms: u64, pub min_msg_reuse_interval_ms: u64,
#[serde(default = "default_max_msg_accounts")] #[serde(default = "default_max_msg_accounts")]
pub max_msg_accounts: u64, pub max_msg_accounts: u64,
/// Optionally, we take a mapping account to add remaining symbols from a Pyth deployments. These symbols are processed under attestation conditions for the `default` symbol group. /// Optionally, we take a mapping account to add remaining symbols from a Pyth deployments. These symbols are processed under attestation conditions for the `default` symbol group.
#[serde( #[serde(
deserialize_with = "opt_pubkey_string_de", deserialize_with = "opt_pubkey_string_de",
serialize_with = "opt_pubkey_string_ser", serialize_with = "opt_pubkey_string_ser",
default // Uses Option::default() which is None default // Uses Option::default() which is None
)] )]
pub mapping_addr: Option<Pubkey>, pub mapping_addr: Option<Pubkey>,
/// The known symbol list will be reloaded based off this /// The known symbol list will be reloaded based off this
/// interval, to account for mapping changes. Note: This interval /// interval, to account for mapping changes. Note: This interval
/// will only work if the mapping address is defined. Whenever /// will only work if the mapping address is defined. Whenever
@ -45,8 +44,8 @@ pub struct AttestationConfig {
pub mapping_reload_interval_mins: u64, pub mapping_reload_interval_mins: u64,
#[serde(default = "default_min_rpc_interval_ms")] #[serde(default = "default_min_rpc_interval_ms")]
/// Rate-limiting minimum delay between RPC requests in milliseconds" /// Rate-limiting minimum delay between RPC requests in milliseconds"
pub min_rpc_interval_ms: u64, pub min_rpc_interval_ms: u64,
pub symbol_groups: Vec<SymbolGroup>, pub symbol_groups: Vec<SymbolGroup>,
} }
impl AttestationConfig { impl AttestationConfig {
@ -75,9 +74,9 @@ impl AttestationConfig {
.map(|(prod, prices)| iter::zip(iter::repeat(prod), prices)) // Convert to iterator over flat (prod, price) tuples .map(|(prod, prices)| iter::zip(iter::repeat(prod), prices)) // Convert to iterator over flat (prod, price) tuples
.flatten() // Flatten the tuple iterators .flatten() // Flatten the tuple iterators
.map(|(prod, price)| P2WSymbol { .map(|(prod, price)| P2WSymbol {
name: None, name: None,
product_addr: prod, product_addr: prod,
price_addr: price, price_addr: price,
}) })
.collect::<Vec<P2WSymbol>>(); .collect::<Vec<P2WSymbol>>();
@ -129,7 +128,7 @@ pub struct SymbolGroup {
pub group_name: String, pub group_name: String,
/// Attestation conditions applied to all symbols in this group /// Attestation conditions applied to all symbols in this group
pub conditions: AttestationConditions, pub conditions: AttestationConditions,
pub symbols: Vec<P2WSymbol>, pub symbols: Vec<P2WSymbol>,
} }
pub const fn default_max_msg_accounts() -> u64 { pub const fn default_max_msg_accounts() -> u64 {
@ -202,9 +201,9 @@ impl AttestationConditions {
impl Default for AttestationConditions { impl Default for AttestationConditions {
fn default() -> Self { fn default() -> Self {
Self { Self {
min_interval_secs: default_min_interval_secs(), min_interval_secs: default_min_interval_secs(),
max_batch_jobs: default_max_batch_jobs(), max_batch_jobs: default_max_batch_jobs(),
price_changed_bps: None, price_changed_bps: None,
publish_time_min_delta_secs: None, publish_time_min_delta_secs: None,
} }
} }
@ -225,7 +224,7 @@ pub struct P2WSymbol {
deserialize_with = "pubkey_string_de", deserialize_with = "pubkey_string_de",
serialize_with = "pubkey_string_ser" serialize_with = "pubkey_string_ser"
)] )]
pub price_addr: Pubkey, pub price_addr: Pubkey,
} }
impl ToString for P2WSymbol { impl ToString for P2WSymbol {
@ -275,9 +274,10 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use {
super::*,
use solitaire::ErrBox; solitaire::ErrBox,
};
#[test] #[test]
fn test_sanity() -> Result<(), ErrBox> { fn test_sanity() -> Result<(), ErrBox> {
@ -287,7 +287,7 @@ mod tests {
min_interval_secs: 5, min_interval_secs: 5,
..Default::default() ..Default::default()
}, },
symbols: vec![ symbols: vec![
P2WSymbol { P2WSymbol {
name: Some("ETHUSD".to_owned()), name: Some("ETHUSD".to_owned()),
..Default::default() ..Default::default()
@ -305,7 +305,7 @@ mod tests {
min_interval_secs: 200, min_interval_secs: 200,
..Default::default() ..Default::default()
}, },
symbols: vec![ symbols: vec![
P2WSymbol { P2WSymbol {
name: Some("CNYAUD".to_owned()), name: Some("CNYAUD".to_owned()),
..Default::default() ..Default::default()
@ -318,12 +318,12 @@ mod tests {
}; };
let cfg = AttestationConfig { let cfg = AttestationConfig {
min_msg_reuse_interval_ms: 1000, min_msg_reuse_interval_ms: 1000,
max_msg_accounts: 100_000, max_msg_accounts: 100_000,
min_rpc_interval_ms: 2123, min_rpc_interval_ms: 2123,
mapping_addr: None, mapping_addr: None,
mapping_reload_interval_mins: 42, mapping_reload_interval_mins: 42,
symbol_groups: vec![fastbois, slowbois], symbol_groups: vec![fastbois, slowbois],
}; };
let serialized = serde_yaml::to_string(&cfg)?; let serialized = serde_yaml::to_string(&cfg)?;
@ -338,12 +338,12 @@ mod tests {
#[test] #[test]
fn test_add_symbols_works() -> Result<(), ErrBox> { fn test_add_symbols_works() -> Result<(), ErrBox> {
let empty_config = AttestationConfig { let empty_config = AttestationConfig {
min_msg_reuse_interval_ms: 1000, min_msg_reuse_interval_ms: 1000,
max_msg_accounts: 100, max_msg_accounts: 100,
min_rpc_interval_ms: 42422, min_rpc_interval_ms: 42422,
mapping_addr: None, mapping_addr: None,
mapping_reload_interval_mins: 42, mapping_reload_interval_mins: 42,
symbol_groups: vec![], symbol_groups: vec![],
}; };
let mock_new_symbols = (0..255) let mock_new_symbols = (0..255)

View File

@ -1,30 +1,29 @@
use log::{ use {
debug, crate::{
warn, AttestationConditions,
}; P2WSymbol,
use solana_client::nonblocking::rpc_client::RpcClient; },
log::{
use pyth_sdk_solana::state::PriceAccount; debug,
warn,
use std::time::{ },
Duration, pyth_sdk_solana::state::PriceAccount,
Instant, solana_client::nonblocking::rpc_client::RpcClient,
}; std::time::{
Duration,
use crate::{ Instant,
AttestationConditions, },
P2WSymbol,
}; };
/// Runtime representation of a batch. It refers to the original group /// Runtime representation of a batch. It refers to the original group
/// from the config. /// from the config.
#[derive(Debug)] #[derive(Debug)]
pub struct BatchState { pub struct BatchState {
pub group_name: String, pub group_name: String,
pub symbols: Vec<P2WSymbol>, pub symbols: Vec<P2WSymbol>,
pub last_known_symbol_states: Vec<Option<PriceAccount>>, pub last_known_symbol_states: Vec<Option<PriceAccount>>,
pub conditions: AttestationConditions, pub conditions: AttestationConditions,
pub last_job_finished_at: Instant, pub last_job_finished_at: Instant,
} }
impl<'a> BatchState { impl<'a> BatchState {

View File

@ -1,12 +1,13 @@
//! CLI options //! CLI options
use solana_program::pubkey::Pubkey; use {
use solana_sdk::commitment_config::CommitmentConfig; clap::{
use std::path::PathBuf; Parser,
Subcommand,
use clap::{ },
Parser, solana_program::pubkey::Pubkey,
Subcommand, solana_sdk::commitment_config::CommitmentConfig,
std::path::PathBuf,
}; };
#[derive(Parser)] #[derive(Parser)]
@ -20,15 +21,15 @@ pub struct Cli {
help = "Identity JSON file for the entity meant to cover transaction costs", help = "Identity JSON file for the entity meant to cover transaction costs",
default_value = "~/.config/solana/id.json" default_value = "~/.config/solana/id.json"
)] )]
pub payer: String, pub payer: String,
#[clap(short, long, default_value = "http://localhost:8899")] #[clap(short, long, default_value = "http://localhost:8899")]
pub rpc_url: String, pub rpc_url: String,
#[clap(long, default_value = "confirmed")] #[clap(long, default_value = "confirmed")]
pub commitment: CommitmentConfig, pub commitment: CommitmentConfig,
#[clap(long)] #[clap(long)]
pub p2w_addr: Pubkey, pub p2w_addr: Pubkey,
#[clap(subcommand)] #[clap(subcommand)]
pub action: Action, pub action: Action,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -37,16 +38,16 @@ pub enum Action {
Init { Init {
/// The bridge program account /// The bridge program account
#[clap(short = 'w', long = "wh-prog")] #[clap(short = 'w', long = "wh-prog")]
wh_prog: Pubkey, wh_prog: Pubkey,
#[clap(short = 'o', long = "owner")] #[clap(short = 'o', long = "owner")]
owner_addr: Pubkey, owner_addr: Pubkey,
#[clap(short = 'p', long = "pyth-owner")] #[clap(short = 'p', long = "pyth-owner")]
pyth_owner_addr: Pubkey, pyth_owner_addr: Pubkey,
/// Option<> makes sure not specifying this flag does not imply "false" /// Option<> makes sure not specifying this flag does not imply "false"
#[clap(long = "is-active")] #[clap(long = "is-active")]
is_active: Option<bool>, is_active: Option<bool>,
#[clap(long = "ops-owner")] #[clap(long = "ops-owner")]
ops_owner_addr: Option<Pubkey>, ops_owner_addr: Option<Pubkey>,
}, },
#[clap( #[clap(
about = "Use an existing pyth2wormhole program to attest product price information to another chain" about = "Use an existing pyth2wormhole program to attest product price information to another chain"
@ -54,14 +55,14 @@ pub enum Action {
// Note: defaults target SOL mainnet-beta conditions at implementation time // Note: defaults target SOL mainnet-beta conditions at implementation time
Attest { Attest {
#[clap(short = 'f', long = "--config", help = "Attestation YAML config")] #[clap(short = 'f', long = "--config", help = "Attestation YAML config")]
attestation_cfg: PathBuf, attestation_cfg: PathBuf,
#[clap( #[clap(
short = 'n', short = 'n',
long = "--n-retries", long = "--n-retries",
help = "How many times to retry send_transaction() on each batch before flagging a failure. Only active outside daemon mode", help = "How many times to retry send_transaction() on each batch before flagging a failure. Only active outside daemon mode",
default_value = "5" default_value = "5"
)] )]
n_retries: usize, n_retries: usize,
#[clap( #[clap(
short = 'i', short = 'i',
long = "--retry-interval", long = "--retry-interval",
@ -69,13 +70,13 @@ pub enum Action {
retries. Only active outside daemon mode", retries. Only active outside daemon mode",
default_value = "5" default_value = "5"
)] )]
retry_interval_secs: u64, retry_interval_secs: u64,
#[clap( #[clap(
short = 'd', short = 'd',
long = "--daemon", long = "--daemon",
help = "Do not stop attesting. In this mode, this program will behave more like a daemon and continuously attest the specified symbols." help = "Do not stop attesting. In this mode, this program will behave more like a daemon and continuously attest the specified symbols."
)] )]
daemon: bool, daemon: bool,
#[clap( #[clap(
short = 't', short = 't',
long = "--timeout", long = "--timeout",
@ -94,20 +95,20 @@ pub enum Action {
default_value = "~/.config/solana/id.json", default_value = "~/.config/solana/id.json",
help = "Keypair file for the current config owner" help = "Keypair file for the current config owner"
)] )]
owner: String, owner: String,
/// New owner to set /// New owner to set
#[clap(long = "new-owner")] #[clap(long = "new-owner")]
new_owner_addr: Option<Pubkey>, new_owner_addr: Option<Pubkey>,
#[clap(long = "new-wh-prog")] #[clap(long = "new-wh-prog")]
new_wh_prog: Option<Pubkey>, new_wh_prog: Option<Pubkey>,
#[clap(long = "new-pyth-owner")] #[clap(long = "new-pyth-owner")]
new_pyth_owner_addr: Option<Pubkey>, new_pyth_owner_addr: Option<Pubkey>,
#[clap(long = "is-active")] #[clap(long = "is-active")]
is_active: Option<bool>, is_active: Option<bool>,
#[clap(long = "ops-owner")] #[clap(long = "ops-owner")]
ops_owner_addr: Option<Pubkey>, ops_owner_addr: Option<Pubkey>,
#[clap(long = "remove-ops-owner", conflicts_with = "ops-owner-addr")] #[clap(long = "remove-ops-owner", conflicts_with = "ops-owner-addr")]
remove_ops_owner: bool, remove_ops_owner: bool,
}, },
#[clap( #[clap(
about = "Migrate existing pyth2wormhole program settings to a newer format version. Client version must match the deployed contract." about = "Migrate existing pyth2wormhole program settings to a newer format version. Client version must match the deployed contract."
@ -131,7 +132,7 @@ pub enum Action {
default_value = "~/.config/solana/id.json", default_value = "~/.config/solana/id.json",
help = "Keypair file for the current ops owner" help = "Keypair file for the current ops owner"
)] )]
ops_owner: String, ops_owner: String,
#[clap( #[clap(
index = 1, index = 1,
possible_values = ["true", "false"], possible_values = ["true", "false"],

View File

@ -8,9 +8,9 @@ pub struct Config {
pub struct P2WSymbol { pub struct P2WSymbol {
/// Optional human-readable name, never used on-chain; makes /// Optional human-readable name, never used on-chain; makes
/// attester logs and the config easier to understand /// attester logs and the config easier to understand
name: Option<String>, name: Option<String>,
product: Pubkey, product: Pubkey,
price: Pubkey, price: Pubkey,
} }
#[testmod] #[testmod]

View File

@ -3,91 +3,88 @@ pub mod batch_state;
pub mod message; pub mod message;
pub mod util; pub mod util;
use borsh::{ pub use {
BorshDeserialize, attestation_cfg::{
BorshSerialize, AttestationConditions,
}; AttestationConfig,
use log::{ P2WSymbol,
debug,
trace,
warn,
};
use pyth_sdk_solana::state::{
load_mapping_account,
load_price_account,
load_product_account,
};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_program::{
hash::Hash,
instruction::{
AccountMeta,
Instruction,
}, },
pubkey::Pubkey, batch_state::BatchState,
system_program, message::P2WMessageQueue,
sysvar::{ pyth2wormhole::Pyth2WormholeConfig,
clock, util::{
rent, RLMutex,
RLMutexGuard,
}, },
}; };
use solana_sdk::{ use {
signer::{ borsh::{
keypair::Keypair, BorshDeserialize,
Signer, BorshSerialize,
}, },
transaction::Transaction, bridge::{
}; accounts::{
use solitaire::{ Bridge,
processors::seeded::Seeded, FeeCollector,
AccountState, Sequence,
ErrBox, SequenceDerivationData,
}; },
types::ConsistencyLevel,
use bridge::{
accounts::{
Bridge,
FeeCollector,
Sequence,
SequenceDerivationData,
}, },
types::ConsistencyLevel, log::{
}; debug,
trace,
use std::collections::{ warn,
HashMap,
HashSet,
};
use p2w_sdk::P2WEmitter;
use pyth2wormhole::{
config::{
OldP2WConfigAccount,
P2WConfigAccount,
}, },
message::{ p2w_sdk::P2WEmitter,
P2WMessage, pyth2wormhole::{
P2WMessageDrvData, config::{
OldP2WConfigAccount,
P2WConfigAccount,
},
message::{
P2WMessage,
P2WMessageDrvData,
},
AttestData,
},
pyth_sdk_solana::state::{
load_mapping_account,
load_price_account,
load_product_account,
},
solana_client::nonblocking::rpc_client::RpcClient,
solana_program::{
hash::Hash,
instruction::{
AccountMeta,
Instruction,
},
pubkey::Pubkey,
system_program,
sysvar::{
clock,
rent,
},
},
solana_sdk::{
signer::{
keypair::Keypair,
Signer,
},
transaction::Transaction,
},
solitaire::{
processors::seeded::Seeded,
AccountState,
ErrBox,
},
std::collections::{
HashMap,
HashSet,
}, },
AttestData,
}; };
pub use pyth2wormhole::Pyth2WormholeConfig;
pub use attestation_cfg::{
AttestationConditions,
AttestationConfig,
P2WSymbol,
};
pub use batch_state::BatchState;
pub use util::{
RLMutex,
RLMutexGuard,
};
pub use message::P2WMessageQueue;
/// Future-friendly version of solitaire::ErrBox /// Future-friendly version of solitaire::ErrBox
pub type ErrBoxSend = Box<dyn std::error::Error + Send + Sync>; pub type ErrBoxSend = Box<dyn std::error::Error + Send + Sync>;
@ -362,8 +359,8 @@ pub fn gen_attest_tx(
AccountMeta::new( AccountMeta::new(
P2WMessage::key( P2WMessage::key(
&P2WMessageDrvData { &P2WMessageDrvData {
id: wh_msg_id, id: wh_msg_id,
batch_size: symbols.len() as u16, batch_size: symbols.len() as u16,
message_owner: payer.pubkey(), message_owner: payer.pubkey(),
}, },
&p2w_addr, &p2w_addr,
@ -384,7 +381,7 @@ pub fn gen_attest_tx(
let ix_data = ( let ix_data = (
pyth2wormhole::instruction::Instruction::Attest, pyth2wormhole::instruction::Instruction::Attest,
AttestData { AttestData {
consistency_level: ConsistencyLevel::Confirmed, consistency_level: ConsistencyLevel::Confirmed,
message_account_id: wh_msg_id, message_account_id: wh_msg_id,
}, },
); );

View File

@ -1,67 +1,65 @@
pub mod cli; pub mod cli;
use std::{ use {
fs::File, clap::Parser,
sync::Arc, cli::{
time::{ Action,
Duration, Cli,
Instant, },
futures::future::{
Future,
TryFutureExt,
},
generic_array::GenericArray,
log::{
debug,
error,
info,
warn,
LevelFilter,
},
p2w_sdk::P2WEmitter,
pyth2wormhole::{
attest::P2W_MAX_BATCH_SIZE,
Pyth2WormholeConfig,
},
pyth2wormhole_client::*,
sha3::{
Digest,
Sha3_256,
},
solana_client::{
nonblocking::rpc_client::RpcClient,
rpc_config::RpcTransactionConfig,
},
solana_program::pubkey::Pubkey,
solana_sdk::{
commitment_config::CommitmentConfig,
signature::read_keypair_file,
signer::keypair::Keypair,
},
solana_transaction_status::UiTransactionEncoding,
solitaire::{
processors::seeded::Seeded,
ErrBox,
},
std::{
fs::File,
sync::Arc,
time::{
Duration,
Instant,
},
},
tokio::{
sync::{
Mutex,
Semaphore,
},
task::JoinHandle,
}, },
}; };
use clap::Parser;
use futures::future::{
Future,
TryFutureExt,
};
use generic_array::GenericArray;
use log::{
debug,
error,
info,
warn,
LevelFilter,
};
use sha3::{
Digest,
Sha3_256,
};
use solana_client::{
nonblocking::rpc_client::RpcClient,
rpc_config::RpcTransactionConfig,
};
use solana_program::pubkey::Pubkey;
use solana_sdk::{
commitment_config::CommitmentConfig,
signature::read_keypair_file,
signer::keypair::Keypair,
};
use solana_transaction_status::UiTransactionEncoding;
use solitaire::{
processors::seeded::Seeded,
ErrBox,
};
use tokio::{
sync::{
Mutex,
Semaphore,
},
task::JoinHandle,
};
use cli::{
Action,
Cli,
};
use p2w_sdk::P2WEmitter;
use pyth2wormhole::{
attest::P2W_MAX_BATCH_SIZE,
Pyth2WormholeConfig,
};
use pyth2wormhole_client::*;
pub const SEQNO_PREFIX: &'static str = "Program log: Sequence: "; pub const SEQNO_PREFIX: &'static str = "Program log: Sequence: ";
#[tokio::main(flavor = "multi_thread")] #[tokio::main(flavor = "multi_thread")]
@ -143,12 +141,12 @@ async fn main() -> Result<(), ErrBox> {
p2w_addr, p2w_addr,
read_keypair_file(&*shellexpand::tilde(&owner))?, read_keypair_file(&*shellexpand::tilde(&owner))?,
Pyth2WormholeConfig { Pyth2WormholeConfig {
owner: new_owner_addr.unwrap_or(old_config.owner), owner: new_owner_addr.unwrap_or(old_config.owner),
wh_prog: new_wh_prog.unwrap_or(old_config.wh_prog), wh_prog: new_wh_prog.unwrap_or(old_config.wh_prog),
pyth_owner: new_pyth_owner_addr.unwrap_or(old_config.pyth_owner), pyth_owner: new_pyth_owner_addr.unwrap_or(old_config.pyth_owner),
is_active: is_active.unwrap_or(old_config.is_active), is_active: is_active.unwrap_or(old_config.is_active),
max_batch_size: P2W_MAX_BATCH_SIZE, max_batch_size: P2W_MAX_BATCH_SIZE,
ops_owner: new_ops_owner, ops_owner: new_ops_owner,
}, },
latest_blockhash, latest_blockhash,
)?; )?;
@ -195,8 +193,8 @@ async fn main() -> Result<(), ErrBox> {
// between RPC accesses. // between RPC accesses.
let rpc_cfg = Arc::new(RLMutex::new( let rpc_cfg = Arc::new(RLMutex::new(
RpcCfg { RpcCfg {
url: cli.rpc_url, url: cli.rpc_url,
timeout: Duration::from_secs(confirmation_timeout_secs), timeout: Duration::from_secs(confirmation_timeout_secs),
commitment: cli.commitment.clone(), commitment: cli.commitment.clone(),
}, },
Duration::from_millis(attestation_cfg.min_rpc_interval_ms), Duration::from_millis(attestation_cfg.min_rpc_interval_ms),
@ -374,8 +372,8 @@ async fn handle_attest_daemon_mode(
#[derive(Clone)] #[derive(Clone)]
pub struct RpcCfg { pub struct RpcCfg {
pub url: String, pub url: String,
pub timeout: Duration, pub timeout: Duration,
pub commitment: CommitmentConfig, pub commitment: CommitmentConfig,
} }
@ -511,13 +509,13 @@ fn prepare_attestation_sched_jobs(
/// The argument count on attestation_sched_job got out of hand. This /// The argument count on attestation_sched_job got out of hand. This
/// helps keep the correct order in check. /// helps keep the correct order in check.
pub struct AttestationSchedJobArgs { pub struct AttestationSchedJobArgs {
pub batch: BatchState, pub batch: BatchState,
pub batch_no: usize, pub batch_no: usize,
pub batch_count: usize, pub batch_count: usize,
pub rpc_cfg: Arc<RLMutex<RpcCfg>>, pub rpc_cfg: Arc<RLMutex<RpcCfg>>,
pub p2w_addr: Pubkey, pub p2w_addr: Pubkey,
pub config: Pyth2WormholeConfig, pub config: Pyth2WormholeConfig,
pub payer: Keypair, pub payer: Keypair,
pub message_q_mtx: Arc<Mutex<P2WMessageQueue>>, pub message_q_mtx: Arc<Mutex<P2WMessageQueue>>,
} }
@ -621,17 +619,17 @@ async fn attestation_sched_job(args: AttestationSchedJobArgs) -> Result<(), ErrB
} }
pub struct AttestationRetryJobArgs { pub struct AttestationRetryJobArgs {
pub batch_no: usize, pub batch_no: usize,
pub batch_count: usize, pub batch_count: usize,
pub group_name: String, pub group_name: String,
pub symbols: Vec<P2WSymbol>, pub symbols: Vec<P2WSymbol>,
pub n_retries: usize, pub n_retries: usize,
pub retry_interval: Duration, pub retry_interval: Duration,
pub rpc_cfg: Arc<RLMutex<RpcCfg>>, pub rpc_cfg: Arc<RLMutex<RpcCfg>>,
pub p2w_addr: Pubkey, pub p2w_addr: Pubkey,
pub p2w_config: Pyth2WormholeConfig, pub p2w_config: Pyth2WormholeConfig,
pub payer: Keypair, pub payer: Keypair,
pub message_q_mtx: Arc<Mutex<P2WMessageQueue>>, pub message_q_mtx: Arc<Mutex<P2WMessageQueue>>,
} }
/// A future that cranks a batch up to n_retries times, pausing for /// A future that cranks a batch up to n_retries times, pausing for
@ -685,14 +683,14 @@ async fn attestation_retry_job(args: AttestationRetryJobArgs) -> Result<(), ErrB
/// Arguments for attestation_job(). This struct rules out same-type /// Arguments for attestation_job(). This struct rules out same-type
/// ordering errors due to the large argument count /// ordering errors due to the large argument count
pub struct AttestationJobArgs { pub struct AttestationJobArgs {
pub rlmtx: Arc<RLMutex<RpcCfg>>, pub rlmtx: Arc<RLMutex<RpcCfg>>,
pub batch_no: usize, pub batch_no: usize,
pub batch_count: usize, pub batch_count: usize,
pub group_name: String, pub group_name: String,
pub p2w_addr: Pubkey, pub p2w_addr: Pubkey,
pub config: Pyth2WormholeConfig, pub config: Pyth2WormholeConfig,
pub payer: Keypair, pub payer: Keypair,
pub symbols: Vec<P2WSymbol>, pub symbols: Vec<P2WSymbol>,
pub max_jobs_sema: Arc<Semaphore>, pub max_jobs_sema: Arc<Semaphore>,
pub message_q_mtx: Arc<Mutex<P2WMessageQueue>>, pub message_q_mtx: Arc<Mutex<P2WMessageQueue>>,
} }
@ -743,8 +741,8 @@ async fn attestation_job(args: AttestationJobArgs) -> Result<(), ErrBoxSend> {
.get_transaction_with_config( .get_transaction_with_config(
&sig, &sig,
RpcTransactionConfig { RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::Json), encoding: Some(UiTransactionEncoding::Json),
commitment: Some(rpc.commitment()), commitment: Some(rpc.commitment()),
max_supported_transaction_version: None, max_supported_transaction_version: None,
}, },
) )

View File

@ -1,21 +1,22 @@
//! Re-usable message scheme for pyth2wormhole //! Re-usable message scheme for pyth2wormhole
use log::debug; use {
use std::{ crate::ErrBoxSend,
collections::VecDeque, log::debug,
time::{ std::{
Duration, collections::VecDeque,
Instant, time::{
Duration,
Instant,
},
}, },
}; };
use crate::ErrBoxSend;
/// One of the accounts tracked by the attestation client. /// One of the accounts tracked by the attestation client.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct P2WMessageAccount { pub struct P2WMessageAccount {
/// Unique ID that lets us derive unique accounts for use on-chain /// Unique ID that lets us derive unique accounts for use on-chain
pub id: u64, pub id: u64,
/// Last time we've posted a message to wormhole with this account /// Last time we've posted a message to wormhole with this account
pub last_used: Instant, pub last_used: Instant,
} }
@ -24,7 +25,7 @@ pub struct P2WMessageAccount {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct P2WMessageQueue { pub struct P2WMessageQueue {
/// The tracked accounts. Sorted from oldest to newest, as guaranteed by get_account() /// The tracked accounts. Sorted from oldest to newest, as guaranteed by get_account()
accounts: VecDeque<P2WMessageAccount>, accounts: VecDeque<P2WMessageAccount>,
/// How much time needs to pass between reuses /// How much time needs to pass between reuses
grace_period: Duration, grace_period: Duration,
/// A hard cap on how many accounts will be created. /// A hard cap on how many accounts will be created.
@ -72,13 +73,13 @@ impl P2WMessageQueue {
// Use a new account instead // Use a new account instead
P2WMessageAccount { P2WMessageAccount {
id: self.accounts.len() as u64, id: self.accounts.len() as u64,
last_used: Instant::now(), last_used: Instant::now(),
} }
} }
// Base case: Queue is empty, use a new account // Base case: Queue is empty, use a new account
None => P2WMessageAccount { None => P2WMessageAccount {
id: self.accounts.len() as u64, id: self.accounts.len() as u64,
last_used: Instant::now(), last_used: Instant::now(),
}, },
}; };

View File

@ -1,23 +1,24 @@
use log::trace; use {
log::trace,
use std::{ std::{
ops::{ ops::{
Deref, Deref,
DerefMut, DerefMut,
},
time::{
Duration,
Instant,
},
}, },
time::{ tokio::sync::{
Duration, Mutex,
Instant, MutexGuard,
}, },
}; };
use tokio::sync::{
Mutex,
MutexGuard,
};
/// Rate-limited mutex. Ensures there's a period of minimum rl_interval between lock acquisitions /// Rate-limited mutex. Ensures there's a period of minimum rl_interval between lock acquisitions
pub struct RLMutex<T> { pub struct RLMutex<T> {
mtx: Mutex<RLMutexState<T>>, mtx: Mutex<RLMutexState<T>>,
rl_interval: Duration, rl_interval: Duration,
} }
@ -25,7 +26,7 @@ pub struct RLMutex<T> {
pub struct RLMutexState<T> { pub struct RLMutexState<T> {
/// Helps make sure regular passage of time is subtracted from sleep duration /// Helps make sure regular passage of time is subtracted from sleep duration
last_released: Instant, last_released: Instant,
val: T, val: T,
} }
impl<T> Deref for RLMutexState<T> { impl<T> Deref for RLMutexState<T> {

View File

@ -1,12 +1,13 @@
//! Trivial program for mocking other programs easily //! Trivial program for mocking other programs easily
use solana_program::{ use {
account_info::AccountInfo, solana_program::{
msg, account_info::AccountInfo,
program_error::ProgramError, msg,
program_error::ProgramError,
},
solana_program_test::*,
solana_sdk::pubkey::Pubkey,
}; };
use solana_sdk::pubkey::Pubkey;
use solana_program_test::*;
pub fn passthrough_entrypoint( pub fn passthrough_entrypoint(
program_id: &Pubkey, program_id: &Pubkey,

View File

@ -1,21 +1,21 @@
//! This module contains test fixtures for instantiating plausible //! This module contains test fixtures for instantiating plausible
//! Pyth accounts for testing purposes. //! Pyth accounts for testing purposes.
use solana_program_test::*; use {
pyth_client::{
use solana_sdk::{ AccKey,
account::Account, AccountType,
pubkey::Pubkey, Price,
rent::Rent, Product,
}; MAGIC,
PROD_ATTR_SIZE,
use pyth_client::{ VERSION,
AccKey, },
AccountType, solana_program_test::*,
Price, solana_sdk::{
Product, account::Account,
MAGIC, pubkey::Pubkey,
PROD_ATTR_SIZE, rent::Rent,
VERSION, },
}; };
/// Create a pair of brand new product/price accounts that point at each other /// Create a pair of brand new product/price accounts that point at each other
@ -27,14 +27,14 @@ pub fn add_test_symbol(pt: &mut ProgramTest, owner: &Pubkey) -> (Pubkey, Pubkey)
// Instantiate // Instantiate
let prod = { let prod = {
Product { Product {
magic: MAGIC, magic: MAGIC,
ver: VERSION, ver: VERSION,
atype: AccountType::Product as u32, atype: AccountType::Product as u32,
size: 0, size: 0,
px_acc: AccKey { px_acc: AccKey {
val: price_id.to_bytes(), val: price_id.to_bytes(),
}, },
attr: [0u8; PROD_ATTR_SIZE], attr: [0u8; PROD_ATTR_SIZE],
} }
}; };
@ -66,16 +66,16 @@ pub fn add_test_symbol(pt: &mut ProgramTest, owner: &Pubkey) -> (Pubkey, Pubkey)
// Populate the accounts // Populate the accounts
let mut prod_acc = Account { let mut prod_acc = Account {
lamports: prod_lamports, lamports: prod_lamports,
data: (*prod_bytes).to_vec(), data: (*prod_bytes).to_vec(),
owner: owner.clone(), owner: owner.clone(),
rent_epoch: 0, rent_epoch: 0,
executable: false, executable: false,
}; };
let mut price_acc = Account { let mut price_acc = Account {
lamports: price_lamports, lamports: price_lamports,
data: (*price_bytes).to_vec(), data: (*price_bytes).to_vec(),
owner: owner.clone(), owner: owner.clone(),
rent_epoch: 0, rent_epoch: 0,
executable: false, executable: false,
}; };

View File

@ -1,41 +1,39 @@
pub mod fixtures; pub mod fixtures;
use solana_program_test::*; use {
use solana_sdk::{ bridge::accounts::{
account::Account, Bridge,
instruction::{ BridgeConfig,
AccountMeta, BridgeData,
Instruction,
}, },
pubkey::Pubkey, fixtures::{
rent::Rent, passthrough,
signature::Signer, pyth,
signer::keypair::Keypair, },
transaction::Transaction, pyth2wormhole::config::{
}; P2WConfigAccount,
Pyth2WormholeConfig,
use bridge::accounts::{ },
Bridge, pyth2wormhole_client as p2wc,
BridgeConfig, solana_program_test::*,
BridgeData, solana_sdk::{
}; account::Account,
instruction::{
use pyth2wormhole::config::{ AccountMeta,
P2WConfigAccount, Instruction,
Pyth2WormholeConfig, },
}; pubkey::Pubkey,
use pyth2wormhole_client as p2wc; rent::Rent,
use solitaire::{ signature::Signer,
processors::seeded::Seeded, signer::keypair::Keypair,
AccountState, transaction::Transaction,
BorshSerialize, },
}; solitaire::{
processors::seeded::Seeded,
use std::time::Duration; AccountState,
BorshSerialize,
use fixtures::{ },
passthrough, std::time::Duration,
pyth,
}; };
#[tokio::test] #[tokio::test]
@ -77,9 +75,9 @@ async fn test_happy_path() -> Result<(), p2wc::ErrBoxSend> {
// Plant a filled config account // Plant a filled config account
let p2w_config_bytes = p2w_config.try_to_vec()?; let p2w_config_bytes = p2w_config.try_to_vec()?;
let p2w_config_account = Account { let p2w_config_account = Account {
lamports: Rent::default().minimum_balance(p2w_config_bytes.len()), lamports: Rent::default().minimum_balance(p2w_config_bytes.len()),
data: p2w_config_bytes, data: p2w_config_bytes,
owner: p2w_program_id, owner: p2w_program_id,
executable: false, executable: false,
rent_epoch: 0, rent_epoch: 0,
}; };
@ -91,9 +89,9 @@ async fn test_happy_path() -> Result<(), p2wc::ErrBoxSend> {
// Plant a bridge config // Plant a bridge config
let bridge_config_bytes = bridge_config.try_to_vec()?; let bridge_config_bytes = bridge_config.try_to_vec()?;
let wh_bridge_config_account = Account { let wh_bridge_config_account = Account {
lamports: Rent::default().minimum_balance(bridge_config_bytes.len()), lamports: Rent::default().minimum_balance(bridge_config_bytes.len()),
data: bridge_config_bytes, data: bridge_config_bytes,
owner: wh_fixture_program_id, owner: wh_fixture_program_id,
executable: false, executable: false,
rent_epoch: 0, rent_epoch: 0,
}; };
@ -109,9 +107,9 @@ async fn test_happy_path() -> Result<(), p2wc::ErrBoxSend> {
let mut ctx = p2w_test.start_with_context().await; let mut ctx = p2w_test.start_with_context().await;
let symbols = vec![p2wc::P2WSymbol { let symbols = vec![p2wc::P2WSymbol {
name: Some("Mock symbol".to_owned()), name: Some("Mock symbol".to_owned()),
product_addr: prod_id, product_addr: prod_id,
price_addr: price_id, price_addr: price_id,
}]; }];
let attest_tx = p2wc::gen_attest_tx( let attest_tx = p2wc::gen_attest_tx(

View File

@ -2,45 +2,43 @@
pub mod fixtures; pub mod fixtures;
use solana_program::system_program; use {
use solana_program_test::*; bridge::accounts::{
use solana_sdk::{ Bridge,
account::Account, BridgeConfig,
instruction::{ BridgeData,
AccountMeta, },
Instruction, fixtures::{
passthrough,
pyth,
},
log::info,
pyth2wormhole::config::{
OldP2WConfigAccount,
OldPyth2WormholeConfig,
P2WConfigAccount,
Pyth2WormholeConfig,
},
pyth2wormhole_client as p2wc,
solana_program::system_program,
solana_program_test::*,
solana_sdk::{
account::Account,
instruction::{
AccountMeta,
Instruction,
},
pubkey::Pubkey,
rent::Rent,
signature::Signer,
signer::keypair::Keypair,
transaction::Transaction,
},
solitaire::{
processors::seeded::Seeded,
AccountState,
BorshSerialize,
}, },
pubkey::Pubkey,
rent::Rent,
signature::Signer,
signer::keypair::Keypair,
transaction::Transaction,
};
use bridge::accounts::{
Bridge,
BridgeConfig,
BridgeData,
};
use log::info;
use pyth2wormhole::config::{
OldP2WConfigAccount,
OldPyth2WormholeConfig,
P2WConfigAccount,
Pyth2WormholeConfig,
};
use pyth2wormhole_client as p2wc;
use solitaire::{
processors::seeded::Seeded,
AccountState,
BorshSerialize,
};
use fixtures::{
passthrough,
pyth,
}; };
#[tokio::test] #[tokio::test]
@ -75,9 +73,9 @@ async fn test_migrate_works() -> Result<(), solitaire::ErrBox> {
// Plant filled config accounts // Plant filled config accounts
let old_p2w_config_bytes = old_p2w_config.try_to_vec()?; let old_p2w_config_bytes = old_p2w_config.try_to_vec()?;
let old_p2w_config_account = Account { let old_p2w_config_account = Account {
lamports: Rent::default().minimum_balance(old_p2w_config_bytes.len()), lamports: Rent::default().minimum_balance(old_p2w_config_bytes.len()),
data: old_p2w_config_bytes, data: old_p2w_config_bytes,
owner: p2w_program_id, owner: p2w_program_id,
executable: false, executable: false,
rent_epoch: 0, rent_epoch: 0,
}; };
@ -147,9 +145,9 @@ async fn test_migrate_already_migrated() -> Result<(), solitaire::ErrBox> {
// Plant filled config accounts // Plant filled config accounts
let old_p2w_config_bytes = old_p2w_config.try_to_vec()?; let old_p2w_config_bytes = old_p2w_config.try_to_vec()?;
let old_p2w_config_account = Account { let old_p2w_config_account = Account {
lamports: Rent::default().minimum_balance(old_p2w_config_bytes.len()), lamports: Rent::default().minimum_balance(old_p2w_config_bytes.len()),
data: old_p2w_config_bytes, data: old_p2w_config_bytes,
owner: p2w_program_id, owner: p2w_program_id,
executable: false, executable: false,
rent_epoch: 0, rent_epoch: 0,
}; };
@ -157,9 +155,9 @@ async fn test_migrate_already_migrated() -> Result<(), solitaire::ErrBox> {
let new_p2w_config_bytes = new_p2w_config.try_to_vec()?; let new_p2w_config_bytes = new_p2w_config.try_to_vec()?;
let new_p2w_config_account = Account { let new_p2w_config_account = Account {
lamports: Rent::default().minimum_balance(new_p2w_config_bytes.len()), lamports: Rent::default().minimum_balance(new_p2w_config_bytes.len()),
data: new_p2w_config_bytes, data: new_p2w_config_bytes,
owner: p2w_program_id, owner: p2w_program_id,
executable: false, executable: false,
rent_epoch: 0, rent_epoch: 0,
}; };

View File

@ -1,25 +1,26 @@
pub mod fixtures; pub mod fixtures;
use borsh::BorshDeserialize; use {
use p2wc::get_config_account; borsh::BorshDeserialize,
use solana_program_test::*; p2wc::get_config_account,
use solana_sdk::{ pyth2wormhole::config::{
account::Account, P2WConfigAccount,
pubkey::Pubkey, Pyth2WormholeConfig,
rent::Rent, },
signature::Signer, pyth2wormhole_client as p2wc,
signer::keypair::Keypair, solana_program_test::*,
}; solana_sdk::{
account::Account,
use pyth2wormhole::config::{ pubkey::Pubkey,
P2WConfigAccount, rent::Rent,
Pyth2WormholeConfig, signature::Signer,
}; signer::keypair::Keypair,
use pyth2wormhole_client as p2wc; },
use solitaire::{ solitaire::{
processors::seeded::Seeded, processors::seeded::Seeded,
AccountState, AccountState,
BorshSerialize, BorshSerialize,
},
}; };
fn clone_keypair(keypair: &Keypair) -> Keypair { fn clone_keypair(keypair: &Keypair) -> Keypair {
@ -58,9 +59,9 @@ async fn test_setting_is_active_works() -> Result<(), p2wc::ErrBoxSend> {
// Plant a filled config account // Plant a filled config account
let p2w_config_bytes = p2w_config.try_to_vec()?; let p2w_config_bytes = p2w_config.try_to_vec()?;
let p2w_config_account = Account { let p2w_config_account = Account {
lamports: Rent::default().minimum_balance(p2w_config_bytes.len()), lamports: Rent::default().minimum_balance(p2w_config_bytes.len()),
data: p2w_config_bytes, data: p2w_config_bytes,
owner: p2w_program_id, owner: p2w_program_id,
executable: false, executable: false,
rent_epoch: 0, rent_epoch: 0,
}; };
@ -145,12 +146,12 @@ async fn test_setting_is_active_does_not_work_without_ops_owner() -> Result<(),
// On-chain state // On-chain state
let p2w_config = Pyth2WormholeConfig { let p2w_config = Pyth2WormholeConfig {
owner: p2w_owner, owner: p2w_owner,
wh_prog: wh_fixture_program_id, wh_prog: wh_fixture_program_id,
max_batch_size: pyth2wormhole::attest::P2W_MAX_BATCH_SIZE, max_batch_size: pyth2wormhole::attest::P2W_MAX_BATCH_SIZE,
pyth_owner: pyth_owner.pubkey(), pyth_owner: pyth_owner.pubkey(),
is_active: true, is_active: true,
ops_owner: None, ops_owner: None,
}; };
// Populate test environment // Populate test environment
@ -163,9 +164,9 @@ async fn test_setting_is_active_does_not_work_without_ops_owner() -> Result<(),
// Plant a filled config account // Plant a filled config account
let p2w_config_bytes = p2w_config.try_to_vec()?; let p2w_config_bytes = p2w_config.try_to_vec()?;
let p2w_config_account = Account { let p2w_config_account = Account {
lamports: Rent::default().minimum_balance(p2w_config_bytes.len()), lamports: Rent::default().minimum_balance(p2w_config_bytes.len()),
data: p2w_config_bytes, data: p2w_config_bytes,
owner: p2w_program_id, owner: p2w_program_id,
executable: false, executable: false,
rent_epoch: 0, rent_epoch: 0,
}; };

View File

@ -1,11 +1,12 @@
//! CLI options //! CLI options
use clap::{ use {
Parser, clap::{
Subcommand, Parser,
Subcommand,
},
solana_sdk::pubkey::Pubkey,
}; };
use solana_sdk::pubkey::Pubkey;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap( #[clap(
about = "A cli for the remote executor", about = "A cli for the remote executor",
@ -21,33 +22,33 @@ pub enum Action {
#[clap(about = "Get set upgrade authority payload for squads-cli")] #[clap(about = "Get set upgrade authority payload for squads-cli")]
GetSetConfig { GetSetConfig {
#[clap(long, help = "Program id")] #[clap(long, help = "Program id")]
program_id: Pubkey, program_id: Pubkey,
#[clap(long, help = "Current owner")] #[clap(long, help = "Current owner")]
owner: Pubkey, owner: Pubkey,
#[clap(long, help = "Payer")] #[clap(long, help = "Payer")]
payer: Pubkey, payer: Pubkey,
#[clap(long, help = "Config : New owner")] #[clap(long, help = "Config : New owner")]
new_owner: Pubkey, new_owner: Pubkey,
#[clap(long, help = "Config : Wormhole program id")] #[clap(long, help = "Config : Wormhole program id")]
wormhole: Pubkey, wormhole: Pubkey,
#[clap(long, help = "Config : Pyth program id")] #[clap(long, help = "Config : Pyth program id")]
pyth_owner: Pubkey, pyth_owner: Pubkey,
#[clap(long, help = "Config : Max batch size")] #[clap(long, help = "Config : Max batch size")]
max_batch_size: u16, max_batch_size: u16,
#[clap(long, help = "Config : Is active")] #[clap(long, help = "Config : Is active")]
is_active: bool, is_active: bool,
#[clap(long, help = "Config : Ops owner")] #[clap(long, help = "Config : Ops owner")]
ops_owner: Option<Pubkey>, ops_owner: Option<Pubkey>,
}, },
#[clap(about = "Get upgrade program payload for squads-cli")] #[clap(about = "Get upgrade program payload for squads-cli")]
GetSetIsActive { GetSetIsActive {
#[clap(long, help = "Program id")] #[clap(long, help = "Program id")]
program_id: Pubkey, program_id: Pubkey,
#[clap(long, help = "Current ops owner")] #[clap(long, help = "Current ops owner")]
ops_owner: Pubkey, ops_owner: Pubkey,
#[clap(long, help = "Payer")] #[clap(long, help = "Payer")]
payer: Pubkey, payer: Pubkey,
#[clap(long, help = "Config : Is active")] #[clap(long, help = "Config : Is active")]
is_active: bool, is_active: bool,
}, },
} }

View File

@ -1,20 +1,21 @@
use anyhow::Result; use {
use clap::Parser; anyhow::Result,
use cli::{ borsh::BorshSerialize,
Action, clap::Parser,
Cli, cli::{
}; Action,
use pyth2wormhole_client::{ Cli,
get_set_config_ix, },
Pyth2WormholeConfig, pyth2wormhole_client::{
}; get_set_config_ix,
get_set_is_active_ix,
use borsh::BorshSerialize; Pyth2WormholeConfig,
use pyth2wormhole_client::get_set_is_active_ix; },
use remote_executor::state::governance_payload::{ remote_executor::state::governance_payload::{
ExecutorPayload, ExecutorPayload,
GovernanceHeader, GovernanceHeader,
InstructionData, InstructionData,
},
}; };
mod cli; mod cli;
@ -43,7 +44,7 @@ fn main() -> Result<()> {
}; };
let ix = get_set_config_ix(&program_id, &owner, &payer, new_config).unwrap(); let ix = get_set_config_ix(&program_id, &owner, &payer, new_config).unwrap();
let payload = ExecutorPayload { let payload = ExecutorPayload {
header: GovernanceHeader::executor_governance_header(), header: GovernanceHeader::executor_governance_header(),
instructions: vec![InstructionData::from(&ix)], instructions: vec![InstructionData::from(&ix)],
} }
.try_to_vec()?; .try_to_vec()?;
@ -58,7 +59,7 @@ fn main() -> Result<()> {
} => { } => {
let ix = get_set_is_active_ix(&program_id, &ops_owner, &payer, is_active).unwrap(); let ix = get_set_is_active_ix(&program_id, &ops_owner, &payer, is_active).unwrap();
let payload = ExecutorPayload { let payload = ExecutorPayload {
header: GovernanceHeader::executor_governance_header(), header: GovernanceHeader::executor_governance_header(),
instructions: vec![InstructionData::from(&ix)], instructions: vec![InstructionData::from(&ix)],
} }
.try_to_vec()?; .try_to_vec()?;

View File

@ -1,60 +1,59 @@
use crate::{ use {
config::P2WConfigAccount, crate::{
message::{ config::P2WConfigAccount,
P2WMessage, message::{
P2WMessageDrvData, P2WMessage,
P2WMessageDrvData,
},
}, },
}; borsh::{
use borsh::{ BorshDeserialize,
BorshDeserialize, BorshSerialize,
BorshSerialize,
};
use solana_program::{
clock::Clock,
instruction::{
AccountMeta,
Instruction,
}, },
program::{ bridge::{
invoke, accounts::BridgeData,
invoke_signed, types::ConsistencyLevel,
PostMessageData,
},
p2w_sdk::{
BatchPriceAttestation,
Identifier,
P2WEmitter,
PriceAttestation,
},
solana_program::{
clock::Clock,
instruction::{
AccountMeta,
Instruction,
},
program::{
invoke,
invoke_signed,
},
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
system_instruction,
sysvar::Sysvar as SolanaSysvar,
},
solitaire::{
invoke_seeded,
trace,
AccountState,
Derive,
ExecutionContext,
FromAccounts,
Info,
Keyed,
Mut,
Peel,
Result as SoliResult,
Seeded,
Signer,
SolitaireError,
Sysvar,
}, },
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
system_instruction,
sysvar::Sysvar as SolanaSysvar,
};
use p2w_sdk::{
BatchPriceAttestation,
Identifier,
P2WEmitter,
PriceAttestation,
};
use bridge::{
accounts::BridgeData,
types::ConsistencyLevel,
PostMessageData,
};
use solitaire::{
invoke_seeded,
trace,
AccountState,
Derive,
ExecutionContext,
FromAccounts,
Info,
Keyed,
Mut,
Peel,
Result as SoliResult,
Seeded,
Signer,
SolitaireError,
Sysvar,
}; };
/// Important: must be manually maintained until native Solitaire /// Important: must be manually maintained until native Solitaire
@ -69,26 +68,26 @@ pub const P2W_MAX_BATCH_SIZE: u16 = 5;
#[derive(FromAccounts)] #[derive(FromAccounts)]
pub struct Attest<'b> { pub struct Attest<'b> {
// Payer also used for wormhole // Payer also used for wormhole
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 }>,
// Hardcoded product/price pairs, bypassing Solitaire's variable-length limitations // Hardcoded product/price pairs, bypassing Solitaire's variable-length limitations
// Any change to the number of accounts must include an appropriate change to P2W_MAX_BATCH_SIZE // Any change to the number of accounts must include an appropriate change to P2W_MAX_BATCH_SIZE
pub pyth_product: Info<'b>, pub pyth_product: Info<'b>,
pub pyth_price: Info<'b>, pub pyth_price: Info<'b>,
pub pyth_product2: Option<Info<'b>>, pub pyth_product2: Option<Info<'b>>,
pub pyth_price2: Option<Info<'b>>, pub pyth_price2: Option<Info<'b>>,
pub pyth_product3: Option<Info<'b>>, pub pyth_product3: Option<Info<'b>>,
pub pyth_price3: Option<Info<'b>>, pub pyth_price3: Option<Info<'b>>,
pub pyth_product4: Option<Info<'b>>, pub pyth_product4: Option<Info<'b>>,
pub pyth_price4: Option<Info<'b>>, pub pyth_price4: Option<Info<'b>>,
pub pyth_product5: Option<Info<'b>>, pub pyth_product5: Option<Info<'b>>,
pub pyth_price5: Option<Info<'b>>, pub pyth_price5: Option<Info<'b>>,
// Did you read the comment near `pyth_product`? // Did you read the comment near `pyth_product`?
// pub pyth_product6: Option<Info<'b>>, // pub pyth_product6: Option<Info<'b>>,
@ -139,7 +138,7 @@ pub struct Attest<'b> {
#[derive(BorshDeserialize, BorshSerialize)] #[derive(BorshDeserialize, BorshSerialize)]
pub struct AttestData { pub struct AttestData {
pub consistency_level: ConsistencyLevel, pub consistency_level: ConsistencyLevel,
pub message_account_id: u64, pub message_account_id: u64,
} }
@ -266,8 +265,8 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
let wh_msg_drv_data = P2WMessageDrvData { let wh_msg_drv_data = P2WMessageDrvData {
message_owner: accs.payer.key.clone(), message_owner: accs.payer.key.clone(),
batch_size: batch_attestation.price_attestations.len() as u16, batch_size: batch_attestation.price_attestations.len() as u16,
id: data.message_account_id, id: data.message_account_id,
}; };
if !P2WMessage::key(&wh_msg_drv_data, ctx.program_id).eq(accs.wh_message.info().key) { if !P2WMessage::key(&wh_msg_drv_data, ctx.program_id).eq(accs.wh_message.info().key) {

View File

@ -20,17 +20,19 @@
//! 6. (optional) Remove/comment out config structs and aliases from //! 6. (optional) Remove/comment out config structs and aliases from
//! before version Y. //! before version Y.
use borsh::{ use {
BorshDeserialize, borsh::{
BorshSerialize, BorshDeserialize,
}; BorshSerialize,
use solana_program::pubkey::Pubkey; },
use solitaire::{ solana_program::pubkey::Pubkey,
processors::seeded::AccountOwner, solitaire::{
AccountState, processors::seeded::AccountOwner,
Data, AccountState,
Derive, Data,
Owned, Derive,
Owned,
},
}; };
/// Aliases for current config schema (to migrate into) /// Aliases for current config schema (to migrate into)
@ -59,11 +61,11 @@ impl Owned for OldPyth2WormholeConfig {
#[cfg_attr(feature = "client", derive(Debug))] #[cfg_attr(feature = "client", derive(Debug))]
pub struct Pyth2WormholeConfigV1 { pub struct Pyth2WormholeConfigV1 {
/// Authority owning this contract /// Authority owning this contract
pub owner: Pubkey, pub owner: Pubkey,
/// Wormhole bridge program /// Wormhole bridge program
pub wh_prog: Pubkey, pub wh_prog: Pubkey,
/// Authority owning Pyth price data /// Authority owning Pyth price data
pub pyth_owner: Pubkey, pub pyth_owner: Pubkey,
pub max_batch_size: u16, pub max_batch_size: u16,
} }
@ -75,11 +77,11 @@ pub type P2WConfigAccountV1<'b, const IsInitialized: AccountState> =
#[cfg_attr(feature = "client", derive(Debug))] #[cfg_attr(feature = "client", derive(Debug))]
pub struct Pyth2WormholeConfigV2 { pub struct Pyth2WormholeConfigV2 {
/// Authority owning this contract /// Authority owning this contract
pub owner: Pubkey, pub owner: Pubkey,
/// Wormhole bridge program /// Wormhole bridge program
pub wh_prog: Pubkey, pub wh_prog: Pubkey,
/// Authority owning Pyth price data /// Authority owning Pyth price data
pub pyth_owner: Pubkey, pub pyth_owner: Pubkey,
/// How many product/price pairs can be sent and attested at once /// How many product/price pairs can be sent and attested at once
/// ///
/// Important: Whenever the corresponding logic in attest.rs /// Important: Whenever the corresponding logic in attest.rs
@ -123,11 +125,11 @@ impl From<Pyth2WormholeConfigV1> for Pyth2WormholeConfigV2 {
#[cfg_attr(feature = "client", derive(Debug))] #[cfg_attr(feature = "client", derive(Debug))]
pub struct Pyth2WormholeConfigV3 { pub struct Pyth2WormholeConfigV3 {
/// Authority owning this contract /// Authority owning this contract
pub owner: Pubkey, pub owner: Pubkey,
/// Wormhole bridge program /// Wormhole bridge program
pub wh_prog: Pubkey, pub wh_prog: Pubkey,
/// Authority owning Pyth price data /// Authority owning Pyth price data
pub pyth_owner: Pubkey, pub pyth_owner: Pubkey,
/// How many product/price pairs can be sent and attested at once /// How many product/price pairs can be sent and attested at once
/// ///
/// Important: Whenever the corresponding logic in attest.rs /// Important: Whenever the corresponding logic in attest.rs

View File

@ -1,33 +1,34 @@
use solana_program::{ use {
program::invoke, crate::config::{
pubkey::Pubkey, P2WConfigAccount,
rent::Rent, Pyth2WormholeConfig,
system_instruction, },
sysvar::Sysvar, solana_program::{
}; program::invoke,
use solitaire::{ pubkey::Pubkey,
trace, rent::Rent,
AccountState, system_instruction,
CreationLamports, sysvar::Sysvar,
ExecutionContext, },
FromAccounts, solitaire::{
Info, trace,
Keyed, AccountState,
Mut, CreationLamports,
Peel, ExecutionContext,
Result as SoliResult, FromAccounts,
Signer, Info,
}; Keyed,
Mut,
use crate::config::{ Peel,
P2WConfigAccount, Result as SoliResult,
Pyth2WormholeConfig, Signer,
},
}; };
#[derive(FromAccounts)] #[derive(FromAccounts)]
pub struct Initialize<'b> { pub struct Initialize<'b> {
pub new_config: Mut<P2WConfigAccount<'b, { AccountState::Uninitialized }>>, pub new_config: Mut<P2WConfigAccount<'b, { AccountState::Uninitialized }>>,
pub payer: Mut<Signer<Info<'b>>>, pub payer: Mut<Signer<Info<'b>>>,
pub system_program: Info<'b>, pub system_program: Info<'b>,
} }

View File

@ -8,32 +8,31 @@ pub mod set_config;
pub mod set_is_active; pub mod set_is_active;
use solitaire::solitaire; use solitaire::solitaire;
pub use {
pub use attest::{ attest::{
attest, attest,
Attest, Attest,
AttestData, AttestData,
},
config::Pyth2WormholeConfig,
initialize::{
initialize,
Initialize,
},
migrate::{
migrate,
Migrate,
},
pyth_client,
set_config::{
set_config,
SetConfig,
},
set_is_active::{
set_is_active,
SetIsActive,
},
}; };
pub use config::Pyth2WormholeConfig;
pub use initialize::{
initialize,
Initialize,
};
pub use migrate::{
migrate,
Migrate,
};
pub use set_config::{
set_config,
SetConfig,
};
pub use set_is_active::{
set_is_active,
SetIsActive,
};
pub use pyth_client;
solitaire! { solitaire! {
Attest => attest, Attest => attest,

View File

@ -7,19 +7,21 @@
//! derived with their address as message_owner in //! derived with their address as message_owner in
//! `P2WMessageDrvData`. //! `P2WMessageDrvData`.
use borsh::{ use {
BorshDeserialize, borsh::{
BorshSerialize, BorshDeserialize,
}; BorshSerialize,
use bridge::PostedMessageUnreliable; },
use solana_program::pubkey::Pubkey; bridge::PostedMessageUnreliable,
use solitaire::{ solana_program::pubkey::Pubkey,
processors::seeded::Seeded, solitaire::{
AccountState, processors::seeded::Seeded,
Data, AccountState,
Info, Data,
Mut, Info,
Signer, Mut,
Signer,
},
}; };
pub type P2WMessage<'a> = Mut<PostedMessageUnreliable<'a, { AccountState::MaybeInitialized }>>; pub type P2WMessage<'a> = Mut<PostedMessageUnreliable<'a, { AccountState::MaybeInitialized }>>;
@ -34,9 +36,9 @@ pub struct P2WMessageDrvData {
/// Currently wormhole does not resize accounts if they have different /// Currently wormhole does not resize accounts if they have different
/// payload sizes; this (along with versioning the seed literal below) is /// payload sizes; this (along with versioning the seed literal below) is
/// a workaround to have different PDAs for different batch sizes. /// a workaround to have different PDAs for different batch sizes.
pub batch_size: u16, pub batch_size: u16,
/// Index for keeping many accounts per owner /// Index for keeping many accounts per owner
pub id: u64, pub id: u64,
} }
impl<'a> Seeded<&P2WMessageDrvData> for P2WMessage<'a> { impl<'a> Seeded<&P2WMessageDrvData> for P2WMessage<'a> {

View File

@ -1,36 +1,36 @@
//! Instruction used to migrate on-chain configuration from an older format //! Instruction used to migrate on-chain configuration from an older format
use solana_program::{ use {
program::invoke, crate::config::{
program_error::ProgramError, OldP2WConfigAccount,
pubkey::Pubkey, OldPyth2WormholeConfig,
rent::Rent, P2WConfigAccount,
system_instruction, Pyth2WormholeConfig,
system_program, },
sysvar::Sysvar, solana_program::{
}; program::invoke,
program_error::ProgramError,
use solitaire::{ pubkey::Pubkey,
trace, rent::Rent,
AccountSize, system_instruction,
AccountState, system_program,
CreationLamports, sysvar::Sysvar,
ExecutionContext, },
FromAccounts, solitaire::{
Info, trace,
Keyed, AccountSize,
Mut, AccountState,
Peel, CreationLamports,
Result as SoliResult, ExecutionContext,
Signer, FromAccounts,
SolitaireError, Info,
}; Keyed,
Mut,
use crate::config::{ Peel,
OldP2WConfigAccount, Result as SoliResult,
OldPyth2WormholeConfig, Signer,
P2WConfigAccount, SolitaireError,
Pyth2WormholeConfig, },
}; };
/// Migration accounts meant to evolve with subsequent config accounts /// Migration accounts meant to evolve with subsequent config accounts
@ -40,13 +40,13 @@ use crate::config::{
#[derive(FromAccounts)] #[derive(FromAccounts)]
pub struct Migrate<'b> { pub struct Migrate<'b> {
/// New config account to be populated. Must be unused. /// New config account to be populated. Must be unused.
pub new_config: Mut<P2WConfigAccount<'b, { AccountState::Uninitialized }>>, pub new_config: Mut<P2WConfigAccount<'b, { AccountState::Uninitialized }>>,
/// Old config using the previous format. /// Old config using the previous format.
pub old_config: Mut<OldP2WConfigAccount<'b>>, pub old_config: Mut<OldP2WConfigAccount<'b>>,
/// Current owner authority of the program /// Current owner authority of the program
pub current_owner: Mut<Signer<Info<'b>>>, pub current_owner: Mut<Signer<Info<'b>>>,
/// Payer account for updating the account data /// Payer account for updating the account data
pub payer: Mut<Signer<Info<'b>>>, pub payer: Mut<Signer<Info<'b>>>,
/// For creating the new config account /// For creating the new config account
pub system_program: Info<'b>, pub system_program: Info<'b>,
} }

View File

@ -1,42 +1,41 @@
use borsh::BorshSerialize; use {
crate::config::{
use solana_program::{ P2WConfigAccount,
program::invoke, Pyth2WormholeConfig,
program_error::ProgramError, },
pubkey::Pubkey, borsh::BorshSerialize,
rent::Rent, solana_program::{
system_instruction, program::invoke,
sysvar::Sysvar, program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
system_instruction,
sysvar::Sysvar,
},
solitaire::{
trace,
AccountState,
ExecutionContext,
FromAccounts,
Info,
Keyed,
Mut,
Peel,
Result as SoliResult,
Signer,
SolitaireError,
},
std::cmp::Ordering,
}; };
use solitaire::{
trace,
AccountState,
ExecutionContext,
FromAccounts,
Info,
Keyed,
Mut,
Peel,
Result as SoliResult,
Signer,
SolitaireError,
};
use crate::config::{
P2WConfigAccount,
Pyth2WormholeConfig,
};
use std::cmp::Ordering;
#[derive(FromAccounts)] #[derive(FromAccounts)]
pub struct SetConfig<'b> { pub struct SetConfig<'b> {
/// Current config used by the program /// Current config used by the program
pub config: Mut<P2WConfigAccount<'b, { AccountState::Initialized }>>, pub config: Mut<P2WConfigAccount<'b, { AccountState::Initialized }>>,
/// Current owner authority of the program /// Current owner authority of the program
pub current_owner: Mut<Signer<Info<'b>>>, pub current_owner: Mut<Signer<Info<'b>>>,
/// Payer account for updating the account data /// Payer account for updating the account data
pub payer: Mut<Signer<Info<'b>>>, pub payer: Mut<Signer<Info<'b>>>,
/// Used for rent adjustment transfer /// Used for rent adjustment transfer
pub system_program: Info<'b>, pub system_program: Info<'b>,
} }

View File

@ -1,30 +1,31 @@
use solitaire::{ use {
trace, crate::config::{
AccountState, P2WConfigAccount,
ExecutionContext, Pyth2WormholeConfig,
FromAccounts, },
Info, solitaire::{
Keyed, trace,
Mut, AccountState,
Peel, ExecutionContext,
Result as SoliResult, FromAccounts,
Signer, Info,
SolitaireError, Keyed,
}; Mut,
Peel,
use crate::config::{ Result as SoliResult,
P2WConfigAccount, Signer,
Pyth2WormholeConfig, SolitaireError,
},
}; };
#[derive(FromAccounts)] #[derive(FromAccounts)]
pub struct SetIsActive<'b> { pub struct SetIsActive<'b> {
/// Current config used by the program /// Current config used by the program
pub config: Mut<P2WConfigAccount<'b, { AccountState::Initialized }>>, pub config: Mut<P2WConfigAccount<'b, { AccountState::Initialized }>>,
/// Current owner authority of the program /// Current owner authority of the program
pub ops_owner: Mut<Signer<Info<'b>>>, pub ops_owner: Mut<Signer<Info<'b>>>,
/// Payer account for updating the account data /// Payer account for updating the account data
pub payer: Mut<Signer<Info<'b>>>, pub payer: Mut<Signer<Info<'b>>>,
} }
/// Alters the current settings of pyth2wormhole /// Alters the current settings of pyth2wormhole

View File

@ -1,11 +0,0 @@
# Merge similar crates together to avoid multiple use statements.
imports_granularity = "Crate"
# Consistency in formatting makes tool based searching/editing better.
empty_item_single_line = false
# Easier editing when arbitrary mixed use statements do not collapse.
imports_layout = "Vertical"
# Default rustfmt formatting of match arms with branches is awful.
match_arm_leading_pipes = "Preserve"

View File

@ -1,18 +0,0 @@
# Merge similar crates together to avoid multiple use statements.
imports_granularity = "Module"
# Consistency in formatting makes tool based searching/editing better.
empty_item_single_line = false
# Easier editing when arbitrary mixed use statements do not collapse.
imports_layout = "Vertical"
# Default rustfmt formatting of match arms with branches is awful.
match_arm_leading_pipes = "Preserve"
# Align Fields
enum_discrim_align_threshold = 80
struct_field_align_threshold = 80
# Allow up to two blank lines for grouping.
blank_lines_upper_bound = 2

View File

@ -6,29 +6,30 @@
//! similar human-readable names and provide a failsafe for some of //! similar human-readable names and provide a failsafe for some of
//! the probable adversarial scenarios. //! the probable adversarial scenarios.
use serde::{
Deserialize,
Serialize,
Serializer,
};
use std::borrow::Borrow;
use std::convert::TryInto;
use std::io::Read;
use std::iter::Iterator;
use std::mem;
pub use pyth_sdk::{ pub use pyth_sdk::{
Identifier, Identifier,
PriceStatus, PriceStatus,
UnixTimestamp, UnixTimestamp,
}; };
#[cfg(feature = "solana")] #[cfg(feature = "solana")]
use solitaire::{ use solitaire::{
Derive, Derive,
Info, Info,
}; };
use {
serde::{
Deserialize,
Serialize,
Serializer,
},
std::{
borrow::Borrow,
convert::TryInto,
io::Read,
iter::Iterator,
mem,
},
};
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
@ -472,8 +473,10 @@ impl PriceAttestation {
/// using `cargo test -- --nocapture`. /// using `cargo test -- --nocapture`.
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use {
use pyth_sdk_solana::state::PriceStatus; super::*,
pyth_sdk_solana::state::PriceStatus,
};
fn mock_attestation(prod: Option<[u8; 32]>, price: Option<[u8; 32]>) -> PriceAttestation { fn mock_attestation(prod: Option<[u8; 32]>, price: Option<[u8; 32]>) -> PriceAttestation {
let product_id_bytes = prod.unwrap_or([21u8; 32]); let product_id_bytes = prod.unwrap_or([21u8; 32]);

View File

@ -1,13 +1,13 @@
use solana_program::pubkey::Pubkey; use {
use solitaire::Seeded; crate::{
use std::str::FromStr; BatchPriceAttestation,
use wasm_bindgen::prelude::*; P2WEmitter,
PriceAttestation,
},
use crate::{ solana_program::pubkey::Pubkey,
BatchPriceAttestation, solitaire::Seeded,
P2WEmitter, std::str::FromStr,
PriceAttestation, wasm_bindgen::prelude::*,
}; };
#[wasm_bindgen] #[wasm_bindgen]