Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion target_chains/solana/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions target_chains/solana/cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pyth-solana-receiver-cli"
version = "0.1.0"
version = "0.2.0"
edition = "2021"

[dependencies]
Expand All @@ -10,13 +10,13 @@ shellexpand = "2.1.2"
solana-sdk = { workspace = true }
solana-client = { workspace = true }
anchor-client = { workspace = true }
clap = {version ="3.2.22", features = ["derive"]}
pyth-solana-receiver = {path = "../programs/pyth-solana-receiver" }
wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"} # Used for initializing the wormhole receiver
clap = { version = "3.2.22", features = ["derive"] }
pyth-solana-receiver = { path = "../programs/pyth-solana-receiver" }
wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana" } # Used for initializing the wormhole receiver
pythnet-sdk = { path = "../../../pythnet/pythnet_sdk", version = "2.0.0" }
wormhole-vaas-serde = { workspace = true }
serde_wormhole = { workspace = true }
hex = "0.4.3"
borsh = "0.9.3" # Old version of borsh needed for wormhole-solana
wormhole-core-bridge-solana = {workspace = true}
pyth-solana-receiver-sdk = {path = "../pyth_solana_receiver_sdk"}
borsh = "0.9.3" # Old version of borsh needed for wormhole-solana
wormhole-core-bridge-solana = { workspace = true }
pyth-solana-receiver-sdk = { path = "../pyth_solana_receiver_sdk" }
17 changes: 16 additions & 1 deletion target_chains/solana/cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,27 @@ pub enum Action {
)]
n_signatures: usize,
},
#[clap(about = "Post a TWAP update from Hermes to Solana")]
PostTwapUpdate {
#[clap(
short = 's',
long,
help = "Start base64 data from Hermes (binary.data.0)"
)]
start_payload: String,
#[clap(
short = 'e',
long,
help = "End base64 data from Hermes (binary.data.1)"
)]
end_payload: String,
},
#[clap(
about = "Initialize a wormhole receiver contract by sequentially replaying the guardian set updates"
)]
InitializeWormholeReceiver {},
InitializePythReceiver {
#[clap(short = 'f', long, help = "Fee in lmaports")]
#[clap(short = 'f', long, help = "Fee in lamports")]
fee: u64,
#[clap(short = 'e', long, parse(try_from_str = Pubkey::from_str), help = "Source emitter")]
emitter: Pubkey,
Expand Down
245 changes: 197 additions & 48 deletions target_chains/solana/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,32 @@ fn main() -> Result<()> {
&merkle_price_updates[0],
)?;
}
Action::PostTwapUpdate {
start_payload,
end_payload,
} => {
let rpc_client = RpcClient::new(url);
let payer =
read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");

let start_payload_bytes: Vec<u8> = base64::decode(start_payload)?;
let end_payload_bytes: Vec<u8> = base64::decode(end_payload)?;

let (start_vaa, start_merkle_price_updates) =
deserialize_accumulator_update_data(start_payload_bytes)?;
let (end_vaa, end_merkle_price_updates) =
deserialize_accumulator_update_data(end_payload_bytes)?;

process_write_encoded_vaa_and_post_twap_update(
&rpc_client,
&start_vaa,
&end_vaa,
wormhole,
&payer,
&start_merkle_price_updates[0],
&end_merkle_price_updates[0],
)?;
}
Action::InitializeWormholeReceiver {} => {
let rpc_client = RpcClient::new(url);
let payer =
Expand Down Expand Up @@ -367,36 +392,174 @@ pub fn process_write_encoded_vaa_and_post_price_update(
merkle_price_update: &MerklePriceUpdate,
) -> Result<Pubkey> {
let encoded_vaa_keypair = Keypair::new();

// Transaction 1: Create and initialize VAA
let init_instructions = create_and_init_encoded_vaa_instructions(
&payer.pubkey(),
vaa,
&wormhole,
&encoded_vaa_keypair,
)?;
process_transaction(
rpc_client,
init_instructions,
&vec![payer, &encoded_vaa_keypair],
)?;

// Transaction 2: Write remaining VAA data, verify VAA, and post price update
let price_update_keypair = Keypair::new();
let mut update_instructions = vec![ComputeBudgetInstruction::set_compute_unit_limit(600_000)];

update_instructions.extend(write_and_verify_vaa_instructions(
&payer.pubkey(),
vaa,
&encoded_vaa_keypair.pubkey(),
wormhole,
)?);

update_instructions.push(pyth_solana_receiver::instruction::PostUpdate::populate(
payer.pubkey(),
payer.pubkey(),
encoded_vaa_keypair.pubkey(),
price_update_keypair.pubkey(),
merkle_price_update.clone(),
get_random_treasury_id(),
));

process_transaction(
rpc_client,
update_instructions,
&vec![payer, &price_update_keypair],
)?;

Ok(price_update_keypair.pubkey())
}

/// This function verifies start & end VAAs from Hermes via Wormhole to produce encoded VAAs,
/// and then posts a TWAP update using the encoded VAAs. Returns the TwapUpdate account pubkey.
///
/// The operation is split up into 4 transactions:
/// 1. Creates and initializes the start VAA account and writes its first part
/// 2. Creates and initializes the end VAA account and writes its first part
/// 3. Writes the remaining data for both VAAs and verifies them
/// 4. Posts the TWAP update
pub fn process_write_encoded_vaa_and_post_twap_update(
rpc_client: &RpcClient,
start_vaa: &[u8],
end_vaa: &[u8],
wormhole: Pubkey,
payer: &Keypair,
start_merkle_price_update: &MerklePriceUpdate,
end_merkle_price_update: &MerklePriceUpdate,
) -> Result<Pubkey> {
// Create keypairs for both encoded VAAs
let start_encoded_vaa_keypair = Keypair::new();
let end_encoded_vaa_keypair = Keypair::new();

// Transaction 1: Create and initialize start VAA
let start_init_instructions = create_and_init_encoded_vaa_instructions(
&payer.pubkey(),
start_vaa,
&wormhole,
&start_encoded_vaa_keypair,
)?;
process_transaction(
rpc_client,
start_init_instructions,
&vec![payer, &start_encoded_vaa_keypair],
)?;

// Transaction 2: Create and initialize end VAA
let end_init_instructions = create_and_init_encoded_vaa_instructions(
&payer.pubkey(),
end_vaa,
&wormhole,
&end_encoded_vaa_keypair,
)?;
process_transaction(
rpc_client,
end_init_instructions,
&vec![payer, &end_encoded_vaa_keypair],
)?;

// Transaction 3: Write remaining VAA data and verify both VAAs
let mut verify_instructions = vec![ComputeBudgetInstruction::set_compute_unit_limit(400_000)];
verify_instructions.extend(write_and_verify_vaa_instructions(
&payer.pubkey(),
start_vaa,
&start_encoded_vaa_keypair.pubkey(),
wormhole,
)?);
verify_instructions.extend(write_and_verify_vaa_instructions(
&payer.pubkey(),
end_vaa,
&end_encoded_vaa_keypair.pubkey(),
wormhole,
)?);
process_transaction(rpc_client, verify_instructions, &vec![payer])?;

// Transaction 4: Post TWAP update
let twap_update_keypair = Keypair::new();
let post_instructions = vec![
ComputeBudgetInstruction::set_compute_unit_limit(400_000),
pyth_solana_receiver::instruction::PostTwapUpdate::populate(
payer.pubkey(),
payer.pubkey(),
start_encoded_vaa_keypair.pubkey(),
end_encoded_vaa_keypair.pubkey(),
twap_update_keypair.pubkey(),
start_merkle_price_update.clone(),
end_merkle_price_update.clone(),
get_random_treasury_id(),
),
];
process_transaction(
rpc_client,
post_instructions,
&vec![payer, &twap_update_keypair],
)?;

Ok(twap_update_keypair.pubkey())
}

/// Creates instructions to initialize an encoded VAA account and write the first part of the VAA data
pub fn create_and_init_encoded_vaa_instructions(
payer: &Pubkey,
vaa: &[u8],
wormhole: &Pubkey,
encoded_vaa_keypair: &Keypair,
) -> Result<Vec<Instruction>> {
let encoded_vaa_size: usize = vaa.len() + VAA_START;

let create_encoded_vaa = system_instruction::create_account(
&payer.pubkey(),
payer,
&encoded_vaa_keypair.pubkey(),
Rent::default().minimum_balance(encoded_vaa_size),
encoded_vaa_size as u64,
&wormhole,
wormhole,
);

let init_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::InitEncodedVaa {
write_authority: payer.pubkey(),
write_authority: *payer,
encoded_vaa: encoded_vaa_keypair.pubkey(),
}
.to_account_metas(None);

let init_encoded_vaa_instruction = Instruction {
program_id: wormhole,
program_id: *wormhole,
accounts: init_encoded_vaa_accounts,
data: wormhole_core_bridge_solana::instruction::InitEncodedVaa.data(),
};

let write_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::WriteEncodedVaa {
write_authority: payer.pubkey(),
write_authority: *payer,
draft_vaa: encoded_vaa_keypair.pubkey(),
}
.to_account_metas(None);

let write_encoded_vaa_accounts_instruction = Instruction {
program_id: wormhole,
accounts: write_encoded_vaa_accounts.clone(),
let write_encoded_vaa_instruction = Instruction {
program_id: *wormhole,
accounts: write_encoded_vaa_accounts,
data: wormhole_core_bridge_solana::instruction::WriteEncodedVaa {
args: WriteEncodedVaaArgs {
index: 0,
Expand All @@ -406,18 +569,27 @@ pub fn process_write_encoded_vaa_and_post_price_update(
.data(),
};

// 1st transaction
process_transaction(
rpc_client,
vec![
create_encoded_vaa,
init_encoded_vaa_instruction,
write_encoded_vaa_accounts_instruction,
],
&vec![payer, &encoded_vaa_keypair],
)?;
Ok(vec![
create_encoded_vaa,
init_encoded_vaa_instruction,
write_encoded_vaa_instruction,
])
}

/// Creates instructions to write remaining VAA data and verify the VAA
pub fn write_and_verify_vaa_instructions(
payer: &Pubkey,
vaa: &[u8],
encoded_vaa_keypair: &Pubkey,
wormhole: Pubkey,
) -> Result<Vec<Instruction>> {
let write_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::WriteEncodedVaa {
write_authority: *payer,
draft_vaa: *encoded_vaa_keypair,
}
.to_account_metas(None);

let write_encoded_vaa_accounts_instruction_2 = Instruction {
let write_encoded_vaa_instruction = Instruction {
program_id: wormhole,
accounts: write_encoded_vaa_accounts,
data: wormhole_core_bridge_solana::instruction::WriteEncodedVaa {
Expand All @@ -432,13 +604,10 @@ pub fn process_write_encoded_vaa_and_post_price_update(
let (header, _): (Header, Body<&RawMessage>) = serde_wormhole::from_slice(vaa).unwrap();
let guardian_set = GuardianSet::key(&wormhole, header.guardian_set_index);

let request_compute_units_instruction: Instruction =
ComputeBudgetInstruction::set_compute_unit_limit(600_000);

let verify_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::VerifyEncodedVaaV1 {
guardian_set,
write_authority: payer.pubkey(),
draft_vaa: encoded_vaa_keypair.pubkey(),
write_authority: *payer,
draft_vaa: *encoded_vaa_keypair,
}
.to_account_metas(None);

Expand All @@ -448,30 +617,10 @@ pub fn process_write_encoded_vaa_and_post_price_update(
data: wormhole_core_bridge_solana::instruction::VerifyEncodedVaaV1 {}.data(),
};

let price_update_keypair = Keypair::new();

let post_update_instructions = pyth_solana_receiver::instruction::PostUpdate::populate(
payer.pubkey(),
payer.pubkey(),
encoded_vaa_keypair.pubkey(),
price_update_keypair.pubkey(),
merkle_price_update.clone(),
get_random_treasury_id(),
);

// 2nd transaction
process_transaction(
rpc_client,
vec![
request_compute_units_instruction,
write_encoded_vaa_accounts_instruction_2,
verify_encoded_vaa_instruction,
post_update_instructions,
],
&vec![payer, &price_update_keypair],
)?;

Ok(price_update_keypair.pubkey())
Ok(vec![
write_encoded_vaa_instruction,
verify_encoded_vaa_instruction,
])
}

pub fn process_transaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ pub enum ReceiverError {
#[msg("Funds are insufficient to pay the receiving fee")]
InsufficientFunds,
#[msg("Cannot calculate TWAP, end slot must be greater than start slot")]
FeedIdMismatch,
#[msg("The start and end messages must have the same feed ID")]
ExponentMismatch,
#[msg("The start and end messages must have the same exponent")]
InvalidTwapSlots,
#[msg("Start message is not the first update for its timestamp")]
InvalidTwapStartMessage,
Expand Down
Loading
Loading