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