pyth-crosschain/examples/messenger/solana/tests/test_messages.rs

340 lines
11 KiB
Rust

//! 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
}