examples: add solana sdk examples
Change-Id: I2f0cc982c880ba1830ad1e1c4a4a409876c4ad66
This commit is contained in:
parent
7f1c7ec62f
commit
ca1f0264b0
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "wormhole-messenger-common"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "messenger_common"
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[dependencies]
|
||||
borsh = "0.8.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
|
@ -0,0 +1,13 @@
|
|||
use borsh::{
|
||||
BorshDeserialize,
|
||||
BorshSerialize,
|
||||
};
|
||||
|
||||
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq)]
|
||||
pub struct Message {
|
||||
/// Messenger/DM username.
|
||||
pub nick: String,
|
||||
|
||||
/// Message text to be output on the target networks node logs.
|
||||
pub text: String,
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Uses 2018 for maximum compatibiilty with Solana.
|
||||
edition = "2018"
|
||||
|
||||
# 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
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "wormhole-messenger-solana"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "messenger"
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
borsh = { version="=0.8.1" }
|
||||
solana-program = { version="=1.7.0" }
|
||||
nom = { version="7", default-features=false, features=["alloc"] }
|
||||
wormhole-sdk = { path = "../../../sdk/rust/sdk", features = ["devnet", "solana"] }
|
||||
wormhole-messenger-common = { path = "../common" }
|
||||
|
||||
[dev-dependencies]
|
||||
borsh = "=0.8.1"
|
||||
solana-program-test = "=1.7.0"
|
||||
solana-sdk = "=1.7.0"
|
||||
rand = "0.7.3"
|
||||
libsecp256k1 = "0.3.5"
|
||||
hex = "*"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
Binary file not shown.
|
@ -0,0 +1,108 @@
|
|||
use borsh::BorshSerialize;
|
||||
use solana_program::instruction::{
|
||||
AccountMeta,
|
||||
Instruction,
|
||||
};
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use solana_program::system_program;
|
||||
use solana_program::sysvar::rent;
|
||||
use solana_program::sysvar::clock;
|
||||
|
||||
use wormhole_sdk::{
|
||||
id,
|
||||
config,
|
||||
fee_collector,
|
||||
sequence,
|
||||
};
|
||||
|
||||
use messenger_common::Message;
|
||||
|
||||
use crate::Instruction::{
|
||||
RecvMessage,
|
||||
SendMessage,
|
||||
};
|
||||
|
||||
|
||||
/// Create a RecvMessage instruction.
|
||||
pub fn recv_message(program_id: Pubkey, payer: Pubkey, vaa: Pubkey) -> Instruction {
|
||||
Instruction {
|
||||
program_id,
|
||||
data: RecvMessage.try_to_vec().unwrap(),
|
||||
accounts: vec![
|
||||
AccountMeta::new_readonly(payer, true),
|
||||
AccountMeta::new_readonly(vaa, false),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Create a SendMessage instruction.
|
||||
pub fn send_message(
|
||||
program_id: Pubkey,
|
||||
payer: Pubkey,
|
||||
emitter: Pubkey,
|
||||
message: Pubkey,
|
||||
payload: Message,
|
||||
nonce: u32,
|
||||
) -> Instruction {
|
||||
let wormhole = id();
|
||||
let config = config(&wormhole);
|
||||
let fee_collector = fee_collector(&wormhole);
|
||||
let sequence = sequence(&wormhole, &emitter);
|
||||
|
||||
// Note that accounts are passed in in order of useful-ness. The payer and message accounts are
|
||||
// used to invoke Wormhole. Many of the example send_message* instruction handlers will only
|
||||
// pop off as many accounts as required.
|
||||
Instruction {
|
||||
program_id,
|
||||
accounts: vec![
|
||||
AccountMeta::new(payer, true),
|
||||
AccountMeta::new(message, true),
|
||||
AccountMeta::new(fee_collector, false),
|
||||
AccountMeta::new(config, false),
|
||||
AccountMeta::new_readonly(emitter, false),
|
||||
AccountMeta::new(sequence, false),
|
||||
AccountMeta::new_readonly(clock::id(), false),
|
||||
AccountMeta::new_readonly(rent::id(), false),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(wormhole, false),
|
||||
],
|
||||
data: SendMessage(payload, nonce).try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a SendMessageRaw instruction. This does the same as SendMessage however the instruction
|
||||
/// handler does not use the Wormhole SDK helper API.
|
||||
pub fn send_message_raw(
|
||||
program_id: Pubkey,
|
||||
payer: Pubkey,
|
||||
emitter: Pubkey,
|
||||
message: Pubkey,
|
||||
payload: Message,
|
||||
nonce: u32,
|
||||
) -> Instruction {
|
||||
let wormhole = id();
|
||||
let config = config(&wormhole);
|
||||
let fee_collector = fee_collector(&wormhole);
|
||||
let sequence = sequence(&wormhole, &emitter);
|
||||
|
||||
// Note that accounts are passed in in order of useful-ness. The payer and message accounts are
|
||||
// used to invoke Wormhole. Many of the example send_message* instruction handlers will only
|
||||
// pop off as many accounts as required.
|
||||
Instruction {
|
||||
program_id,
|
||||
accounts: vec![
|
||||
AccountMeta::new(payer, true),
|
||||
AccountMeta::new(message, true),
|
||||
AccountMeta::new(fee_collector, false),
|
||||
AccountMeta::new(config, false),
|
||||
AccountMeta::new_readonly(emitter, false),
|
||||
AccountMeta::new(sequence, false),
|
||||
AccountMeta::new_readonly(clock::id(), false),
|
||||
AccountMeta::new_readonly(rent::id(), false),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(wormhole, false),
|
||||
],
|
||||
data: SendMessage(payload, nonce).try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
#![deny(unused_must_use)]
|
||||
|
||||
// A common serialization library used in the blockchain space, which we'll use to serialize our
|
||||
// cross chain message payloads.
|
||||
use borsh::{
|
||||
BorshDeserialize,
|
||||
BorshSerialize,
|
||||
};
|
||||
|
||||
// Solana SDK imports to interact with the solana runtime.
|
||||
use solana_program::account_info::{
|
||||
next_account_info,
|
||||
AccountInfo,
|
||||
};
|
||||
use solana_program::entrypoint::ProgramResult;
|
||||
use solana_program::program::invoke_signed;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use solana_program::{
|
||||
entrypoint,
|
||||
msg,
|
||||
};
|
||||
|
||||
// Import Solana Wormhole SDK.
|
||||
use wormhole_sdk::{
|
||||
instructions::post_message,
|
||||
ConsistencyLevel,
|
||||
VAA,
|
||||
};
|
||||
|
||||
// Our Payload, defined in a common library.
|
||||
pub use messenger_common::Message;
|
||||
|
||||
pub mod instruction;
|
||||
|
||||
#[derive(BorshSerialize, BorshDeserialize, Clone)]
|
||||
pub enum Instruction {
|
||||
/// This instruction is used to send a message to another chain by emitting it as a wormhole
|
||||
/// message targetting another users key.
|
||||
///
|
||||
/// 0: Payer [Signer]
|
||||
/// 1: Message [Signer]
|
||||
/// 2: Worm Config [PDA]
|
||||
/// 3: Worm Fee [PDA]
|
||||
/// 4: Worm Sequence [PDA]
|
||||
/// 5: Emitter [PDA, Signer]
|
||||
/// 6: Clock [Program] -- Needed for wormhole to take block times.
|
||||
/// 7: Rent [Program] -- Needed for wormhole fee calculation on the message account.
|
||||
/// 8: System [Program] -- Needed for wormhole to take fees.
|
||||
/// 9: Wormhole [Program] -- Needed for wormhole invoke_signed.
|
||||
SendMessage(Message, u32),
|
||||
|
||||
/// This is the same as the above message, but the example handler is more low level.
|
||||
SendMessageRaw(Message, u32),
|
||||
|
||||
/// This instruction receives a message by processing an incoming VAA containing a message
|
||||
/// intended for a receiver on Solana. Note that the simple existence of the VAA account is
|
||||
/// enough to verify it as the account is only created by the bridge if the guardians had
|
||||
/// successfull signed it.
|
||||
///
|
||||
/// 0: VAA [PDA]
|
||||
RecvMessage,
|
||||
}
|
||||
|
||||
|
||||
entrypoint!(process_instruction);
|
||||
|
||||
/// The Solana entrypoint, here we deserialize our Borsh encoded Instruction and dispatch to our
|
||||
/// program handlers.
|
||||
pub fn process_instruction(id: &Pubkey, accs: &[AccountInfo], data: &[u8]) -> ProgramResult {
|
||||
match BorshDeserialize::try_from_slice(data).unwrap() {
|
||||
// Send Message Variants. Check the source of each to see various ways to invoke Wormhole.
|
||||
Instruction::SendMessage(msg, nonce) => send_message(id, accs, msg, nonce),
|
||||
Instruction::SendMessageRaw(msg, nonce) => send_message_raw(id, accs, msg, nonce),
|
||||
|
||||
// RecvMessage shows an example of safely processing a VAA.
|
||||
Instruction::RecvMessage => recv_message(id, accs),
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a Message from this chain to a user on a remote target chain.
|
||||
///
|
||||
/// This method is a reference example of emitting messages via Wormhole using the ergonomic API
|
||||
/// methods. This is the easiest way to use Wormhole.
|
||||
fn send_message(id: &Pubkey, accounts: &[AccountInfo], payload: Message, nonce: u32) -> ProgramResult {
|
||||
let iter = &mut accounts.iter();
|
||||
let payer = next_account_info(iter)?;
|
||||
let message = next_account_info(iter)?;
|
||||
|
||||
// This helper method will take care of all of the following for you:
|
||||
//
|
||||
// - Derives a reasonable emitter PDA for your program.
|
||||
// - Pays the Bridge (Payer Key)
|
||||
// - Emits a Message
|
||||
wormhole_sdk::post_message(
|
||||
*id,
|
||||
*payer.key,
|
||||
*message.key,
|
||||
payload.try_to_vec()?,
|
||||
ConsistencyLevel::Finalized,
|
||||
None,
|
||||
accounts,
|
||||
nonce,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a Message from this chain to a user on a remote target chain.
|
||||
///
|
||||
/// This method is a reference example of emitting messages via Wormhole using the most low level
|
||||
/// interface provided by the SDK. You must handle the emitter, payment, and invoking yourself.
|
||||
fn send_message_raw(id: &Pubkey, accs: &[AccountInfo], payload: Message, nonce: u32) -> ProgramResult {
|
||||
let accounts = &mut accs.iter();
|
||||
let payer = next_account_info(accounts)?;
|
||||
let message = next_account_info(accounts)?;
|
||||
let fee_collector = next_account_info(accounts)?;
|
||||
let config = next_account_info(accounts)?;
|
||||
|
||||
// Deserialize Bridge Config, used to figure out what the fee is so we can pay the bridge
|
||||
// programatically.
|
||||
let config = wormhole_sdk::read_config(config).unwrap();
|
||||
|
||||
// Pay Fee to the Wormhole.
|
||||
invoke_signed(
|
||||
&solana_program::system_instruction::transfer(payer.key, fee_collector.key, config.fee),
|
||||
accs,
|
||||
&[],
|
||||
)?;
|
||||
|
||||
// Create an Emitter to emit messages from, this helper method is producing the emitter from
|
||||
// the _current_ program's ID.
|
||||
let (emitter, mut seeds, bump) = wormhole_sdk::emitter(id);
|
||||
let bump = &[bump];
|
||||
seeds.push(bump);
|
||||
|
||||
// Invoke the Wormhole post_message endpoint to create an on-chain message.
|
||||
invoke_signed(
|
||||
&post_message(
|
||||
wormhole_sdk::id(),
|
||||
*payer.key,
|
||||
emitter,
|
||||
*message.key,
|
||||
nonce,
|
||||
payload.try_to_vec()?,
|
||||
ConsistencyLevel::Finalized,
|
||||
)
|
||||
.unwrap(),
|
||||
accs,
|
||||
&[&seeds],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Receives a VAA containing a message from a foreign chain, and parses/verifies the VAA to
|
||||
/// validate the message has been safely attested by the guardian set. Prints the message in
|
||||
/// validator logs.
|
||||
fn recv_message(id: &Pubkey, accs: &[AccountInfo]) -> ProgramResult {
|
||||
// We must verify the VAA is legitimately signed by the guardians. We do this by deriving the
|
||||
// expected PDA derived by the bridge, as long as we produce the same account we can trust the
|
||||
// contents of the VAA.
|
||||
let accounts = &mut accs.iter();
|
||||
let payer = next_account_info(accounts)?;
|
||||
let vaa = next_account_info(accounts)?;
|
||||
|
||||
// If we want to avoid processing a message twice we need to track whether we have already
|
||||
// processed a VAA manually. There are several ways to do this in Solana but in this example
|
||||
// we will simply reprocess VAA's.
|
||||
let vaa = wormhole_sdk::read_vaa(vaa).unwrap();
|
||||
let msg = Message::try_from_slice(&vaa.payload)?;
|
||||
msg!("{}: {}", msg.nick, msg.text);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
//! Test the Messenger program.
|
||||
//!
|
||||
//! You will need a `bridge.so` somewhere in the path of the tests. The easiest way to do this is
|
||||
//! to build the Wormhole bridge and copy the `.so` into the current directory:
|
||||
//!
|
||||
//! ```
|
||||
//! $ pushd <BRIDGE>
|
||||
//! $ EMITTER_ADDRESS="0000000000000000000000000000000000000000000000000000000000000004" cargo build-bpf
|
||||
//! $ popd
|
||||
//! $ cp <BRIDGE>/target/deploy/bridge.so .
|
||||
//! ```
|
||||
//!
|
||||
//! This will give you a BPF ELF that the ProgramTest framework can deploy, if you don't do this
|
||||
//! ProgramTest will try and compile the processor in native mode which cannot currently handle
|
||||
//! accounts that change size, which Wormhole relies on.
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
|
||||
// Solana Requirements
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use solana_program_test::{
|
||||
processor,
|
||||
tokio,
|
||||
ProgramTest,
|
||||
ProgramTestContext,
|
||||
};
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::signer::Signer;
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use solana_sdk::secp256k1_instruction::new_secp256k1_instruction;
|
||||
|
||||
// Import necessary components from the Messenger Program so we can test them.
|
||||
use messenger::Message;
|
||||
use messenger::process_instruction;
|
||||
use messenger::instruction::{
|
||||
send_message,
|
||||
recv_message,
|
||||
send_message_raw,
|
||||
};
|
||||
|
||||
// We utilise the bridge_endpoint, which is re-exposed by the SDK, to run instructions against
|
||||
// within the Solana program test framework.
|
||||
use wormhole_sdk::Chain;
|
||||
use wormhole_sdk::MessageData;
|
||||
use wormhole_sdk::VAA;
|
||||
use wormhole_sdk::PostVAAData;
|
||||
use wormhole_sdk::VerifySignaturesData;
|
||||
use wormhole_sdk::bridge_entrypoint;
|
||||
|
||||
// Borsh to deserialise the Wormhole Message account for asserting data.
|
||||
use borsh::BorshDeserialize;
|
||||
|
||||
// Secp256k1 so we can produce a Guardian secret key to test with.
|
||||
use secp256k1::SecretKey;
|
||||
|
||||
|
||||
/// We need an address to deploy our Messenger program at, we just hardcode one for use throughout
|
||||
/// the tests.
|
||||
const ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
|
||||
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_publish_message() {
|
||||
// Guardian
|
||||
let guardian = hex::decode("B7f0900393F869eE15E00e01Dc71E7ba8590E51f").unwrap();
|
||||
let guardian = &guardian.try_into().unwrap();
|
||||
|
||||
// Initialize Test Environment with instruction processors. This lets us load the wormhole
|
||||
// processor into scope so we can inspect whether the messages it emits are in fact as we
|
||||
// expect them to be.
|
||||
let mut context = {
|
||||
let mut test = ProgramTest::default();
|
||||
test.add_program("bridge", wormhole_sdk::id(), processor!(bridge_entrypoint));
|
||||
test.add_program("messenger", ID, processor!(process_instruction));
|
||||
test.start_with_context().await
|
||||
};
|
||||
|
||||
// Initialize Wormhole
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(Transaction::new_signed_with_payer(
|
||||
&[wormhole_sdk::instructions::initialize(
|
||||
wormhole_sdk::id(),
|
||||
context.payer.pubkey(),
|
||||
50,
|
||||
2_000_000_000,
|
||||
&[*guardian],
|
||||
).unwrap()],
|
||||
Some(&context.payer.pubkey()),
|
||||
&[&context.payer],
|
||||
context.last_blockhash,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Message & Emitter Account Keys
|
||||
let message = Keypair::new();
|
||||
let emitter = wormhole_sdk::emitter(&ID);
|
||||
|
||||
// Submit a cross-chain message via Wormhole.
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(Transaction::new_signed_with_payer(
|
||||
&[send_message(
|
||||
ID,
|
||||
context.payer.pubkey(),
|
||||
emitter.0,
|
||||
message.pubkey(),
|
||||
Message {
|
||||
nick: "Alice".to_string(),
|
||||
text: "Hello from Bob!".to_string(),
|
||||
},
|
||||
0,
|
||||
)],
|
||||
Some(&context.payer.pubkey()),
|
||||
&[&context.payer, &message],
|
||||
context.last_blockhash,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// We should now be able to find a Message on chain emitted by our contract. Let's verify it
|
||||
// contains the expected data.
|
||||
let message = MessageData::try_from_slice(
|
||||
&context
|
||||
.banks_client
|
||||
.get_account(message.pubkey())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.data[3..]
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(message.vaa_version, 0);
|
||||
assert_eq!(message.consistency_level, 32);
|
||||
assert_eq!(message.vaa_time, 0);
|
||||
assert_eq!(message.nonce, 0);
|
||||
assert_eq!(message.emitter_chain, 1);
|
||||
assert_eq!(message.emitter_address, emitter.0.to_bytes());
|
||||
assert_eq!(
|
||||
Message::try_from_slice(&message.payload).unwrap(),
|
||||
Message {
|
||||
nick: "Alice".to_string(),
|
||||
text: "Hello from Bob!".to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
// Simulate Guardian behaviour: detecting message, signing, posting VAA.
|
||||
let vaa = simulate_guardians(&mut context, &message).await;
|
||||
|
||||
// We can now test the recv_message endpoint by submitting the signed VAA.
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(Transaction::new_signed_with_payer(
|
||||
&[recv_message(
|
||||
ID,
|
||||
context.payer.pubkey(),
|
||||
vaa,
|
||||
)],
|
||||
Some(&context.payer.pubkey()),
|
||||
&[&context.payer],
|
||||
context.last_blockhash,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_publish_message_raw() {
|
||||
// Guardian
|
||||
let guardian = hex::decode("966745cb54d907a93272dd154e1d1bb94b38c69b").unwrap();
|
||||
let guardian = &guardian.try_into().unwrap();
|
||||
|
||||
// Initialize Test Environment with instruction processors. This lets us load the wormhole
|
||||
// processor into scope so we can inspect whether the messages it emits are in fact as we
|
||||
// expect them to be.
|
||||
let mut context = {
|
||||
let mut test = ProgramTest::default();
|
||||
test.add_program("bridge", wormhole_sdk::id(), processor!(bridge_entrypoint));
|
||||
test.add_program("messenger", ID, processor!(process_instruction));
|
||||
test.start_with_context().await
|
||||
};
|
||||
|
||||
// Initialize Wormhole
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(Transaction::new_signed_with_payer(
|
||||
&[wormhole_sdk::instructions::initialize(
|
||||
wormhole_sdk::id(),
|
||||
context.payer.pubkey(),
|
||||
50,
|
||||
2_000_000_000,
|
||||
&[*guardian],
|
||||
).unwrap()],
|
||||
Some(&context.payer.pubkey()),
|
||||
&[&context.payer],
|
||||
context.last_blockhash,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Message & Emitter Account Keys
|
||||
let message = Keypair::new();
|
||||
let emitter = wormhole_sdk::emitter(&ID);
|
||||
|
||||
// Submit a cross-chain message via Wormhole.
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(Transaction::new_signed_with_payer(
|
||||
&[send_message_raw(
|
||||
ID,
|
||||
context.payer.pubkey(),
|
||||
emitter.0,
|
||||
message.pubkey(),
|
||||
Message {
|
||||
nick: "Alice".to_string(),
|
||||
text: "Hello from Bob!".to_string(),
|
||||
},
|
||||
1,
|
||||
)],
|
||||
Some(&context.payer.pubkey()),
|
||||
&[&context.payer, &message],
|
||||
context.last_blockhash,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Check Resulting Message on Chain
|
||||
let message = MessageData::try_from_slice(
|
||||
&context
|
||||
.banks_client
|
||||
.get_account(message.pubkey())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.data[3..]
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(message.vaa_version, 0);
|
||||
assert_eq!(message.consistency_level, 32);
|
||||
assert_eq!(message.vaa_time, 0);
|
||||
assert_eq!(message.nonce, 1);
|
||||
assert_eq!(message.emitter_chain, 1);
|
||||
assert_eq!(message.emitter_address, emitter.0.to_bytes());
|
||||
assert_eq!(
|
||||
Message::try_from_slice(&message.payload).unwrap(),
|
||||
Message {
|
||||
nick: "Alice".to_string(),
|
||||
text: "Hello from Bob!".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn simulate_guardians(context: &mut ProgramTestContext, message: &MessageData) -> Pubkey {
|
||||
// Emulate Guardian signatures by signing manually. First we produce a VAA.
|
||||
let vaa = {
|
||||
let mut vaa = VAA::default();
|
||||
vaa.timestamp = message.submission_time;
|
||||
vaa.nonce = message.nonce;
|
||||
vaa.emitter_chain = Chain::Solana;
|
||||
vaa.emitter_address = message.emitter_address;
|
||||
vaa.sequence = message.sequence;
|
||||
vaa.consistency_level = message.consistency_level;
|
||||
vaa.payload = message.payload.clone();
|
||||
vaa
|
||||
};
|
||||
|
||||
// Hash the body to produce our message to sign.
|
||||
let body = vaa.digest().unwrap();
|
||||
|
||||
// Place to store Signatures on Solana.
|
||||
let signatures = Keypair::new();
|
||||
let mut signers = [-1; 19];
|
||||
signers[0] = 0;
|
||||
|
||||
// Verify Signatures
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(Transaction::new_signed_with_payer(
|
||||
&[
|
||||
new_secp256k1_instruction(
|
||||
&SecretKey::parse(
|
||||
(&*hex::decode("ff2f9d893e5c12618c442b34a98cfa3f646c402bf5e2a180ce761a7d8a43d452")
|
||||
.unwrap())
|
||||
.try_into()
|
||||
.unwrap()
|
||||
).unwrap(),
|
||||
&body
|
||||
),
|
||||
wormhole_sdk::instructions::verify_signatures(
|
||||
wormhole_sdk::id(),
|
||||
context.payer.pubkey(),
|
||||
0,
|
||||
signatures.pubkey(),
|
||||
VerifySignaturesData { signers },
|
||||
).unwrap(),
|
||||
],
|
||||
Some(&context.payer.pubkey()),
|
||||
&[&context.payer, &signatures],
|
||||
context.last_blockhash,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
context
|
||||
.banks_client
|
||||
.process_transaction(Transaction::new_signed_with_payer(
|
||||
&[
|
||||
wormhole_sdk::instructions::post_vaa(
|
||||
wormhole_sdk::id(),
|
||||
context.payer.pubkey(),
|
||||
signatures.pubkey(),
|
||||
PostVAAData {
|
||||
version: 0,
|
||||
guardian_set_index: 0,
|
||||
timestamp: vaa.timestamp,
|
||||
nonce: vaa.nonce,
|
||||
emitter_chain: vaa.emitter_chain as u16,
|
||||
emitter_address: vaa.emitter_address,
|
||||
sequence: vaa.sequence,
|
||||
consistency_level: vaa.consistency_level,
|
||||
payload: vaa.payload,
|
||||
},
|
||||
),
|
||||
],
|
||||
Some(&context.payer.pubkey()),
|
||||
&[&context.payer],
|
||||
context.last_blockhash,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Derive VAA Destination.
|
||||
Pubkey::find_program_address(
|
||||
&[b"PostedVAA", &body],
|
||||
&wormhole_sdk::id(),
|
||||
).0
|
||||
}
|
Loading…
Reference in New Issue