wormhole/cosmwasm/contracts/wormchain-accounting/tests/submit_observations.rs

993 lines
32 KiB
Rust

mod helpers;
use std::collections::BTreeMap;
use accounting::state::{account, transfer, Kind, Modification, TokenAddress};
use cosmwasm_std::{from_binary, to_binary, Binary, Event, Uint256};
use cw_multi_test::AppResponse;
use helpers::*;
use wormchain_accounting::msg::{Observation, ObservationStatus, SubmitObservationResponse};
use wormhole::{
token::Message,
vaa::{Body, Header},
Address, Amount,
};
use wormhole_bindings::fake;
fn set_up(count: usize) -> (Vec<Message>, Vec<Observation>) {
let mut txs = Vec::with_capacity(count);
let mut observations = Vec::with_capacity(count);
for i in 0..count {
let tx = Message::Transfer {
amount: Amount(Uint256::from(500u128).to_be_bytes()),
token_address: Address([(i + 1) as u8; 32]),
token_chain: (i as u16).into(),
recipient: Address([(i + 2) as u8; 32]),
recipient_chain: ((i + 3) as u16).into(),
fee: Amount([0u8; 32]),
};
let payload = serde_wormhole::to_vec(&tx).map(Binary::from).unwrap();
txs.push(tx);
observations.push(Observation {
tx_hash: vec![(i + 4) as u8; 20].into(),
timestamp: i as u32,
nonce: i as u32,
emitter_chain: i as u16,
emitter_address: [i as u8; 32],
sequence: i as u64,
consistency_level: 0,
payload,
});
}
(txs, observations)
}
#[test]
fn batch() {
const COUNT: usize = 5;
let (txs, observations) = set_up(COUNT);
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, COUNT);
let index = wh.guardian_set_index();
let obs = to_binary(&observations).unwrap();
let signatures = wh.sign(&obs);
let quorum = wh
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
for (i, s) in signatures.into_iter().enumerate() {
let resp = contract.submit_observations(obs.clone(), index, s).unwrap();
let status = from_binary::<Vec<SubmitObservationResponse>>(&resp.data.unwrap())
.unwrap()
.into_iter()
.map(|resp| (resp.key, resp.status))
.collect::<BTreeMap<_, _>>();
if i < quorum {
// Once there is a quorum the pending transfers are removed.
if i < quorum - 1 {
for o in &observations {
let key =
transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
let data = contract.query_pending_transfer(key.clone()).unwrap();
let digest = o.digest().unwrap();
assert_eq!(&digest, data[0].digest());
// Make sure the transfer hasn't yet been committed.
assert!(matches!(status[&key], ObservationStatus::Pending));
contract
.query_transfer(key)
.expect_err("transfer committed without quorum");
}
} else {
for o in &observations {
let key =
transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
assert!(matches!(status[&key], ObservationStatus::Committed));
contract
.query_pending_transfer(key)
.expect_err("found pending transfer for observation with quorum");
}
}
} else {
// Submitting observations for committed transfers is not an error as long as the
// digests match.
for o in &observations {
let key = transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
assert!(matches!(status[&key], ObservationStatus::Committed));
}
}
}
for (tx, o) in txs.into_iter().zip(observations) {
let expected = if let Message::Transfer {
amount,
token_address,
token_chain,
recipient_chain,
..
} = tx
{
transfer::Data {
amount: Uint256::new(amount.0),
token_chain: token_chain.into(),
token_address: TokenAddress::new(token_address.0),
recipient_chain: recipient_chain.into(),
}
} else {
panic!("unexpected tokenbridge payload");
};
let key = transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
let actual = contract.query_transfer(key).unwrap();
assert_eq!(expected, actual.data);
assert_eq!(o.digest().unwrap(), actual.digest);
let src = contract
.query_balance(account::Key::new(
o.emitter_chain,
expected.token_chain,
expected.token_address,
))
.unwrap();
assert_eq!(expected.amount, *src);
let dst = contract
.query_balance(account::Key::new(
expected.recipient_chain,
expected.token_chain,
expected.token_address,
))
.unwrap();
assert_eq!(expected.amount, *dst);
}
}
#[test]
fn duplicates() {
const COUNT: usize = 5;
let (txs, observations) = set_up(COUNT);
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, COUNT);
let index = wh.guardian_set_index();
let obs = to_binary(&observations).unwrap();
let signatures = wh.sign(&obs);
let quorum = wh
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
for (i, s) in signatures.into_iter().enumerate() {
contract.submit_observations(obs.clone(), index, s).unwrap();
// Submitting a duplicate signature is not an error for pending transfers. Submitting any
// signature for a committed transfer is not an error as long as the digests match.
let resp = contract.submit_observations(obs.clone(), index, s).unwrap();
let status = from_binary::<Vec<SubmitObservationResponse>>(&resp.data.unwrap())
.unwrap()
.into_iter()
.map(|details| (details.key, details.status))
.collect::<BTreeMap<_, _>>();
if i < quorum - 1 {
// Resubmitting the same signature without quorum will return an error.
for o in &observations {
let key = transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
assert!(matches!(status[&key], ObservationStatus::Pending));
}
} else {
for o in &observations {
let key = transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
assert!(matches!(status[&key], ObservationStatus::Committed));
}
}
}
for (tx, o) in txs.into_iter().zip(observations) {
let expected = if let Message::Transfer {
amount,
token_address,
token_chain,
recipient_chain,
..
} = tx
{
transfer::Data {
amount: Uint256::new(amount.0),
token_chain: token_chain.into(),
token_address: TokenAddress::new(token_address.0),
recipient_chain: recipient_chain.into(),
}
} else {
panic!("unexpected tokenbridge payload");
};
let key = transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
let actual = contract.query_transfer(key).unwrap();
assert_eq!(expected, actual.data);
assert_eq!(o.digest().unwrap(), actual.digest);
let src = contract
.query_balance(account::Key::new(
o.emitter_chain,
expected.token_chain,
expected.token_address,
))
.unwrap();
assert_eq!(expected.amount, *src);
let dst = contract
.query_balance(account::Key::new(
expected.recipient_chain,
expected.token_chain,
expected.token_address,
))
.unwrap();
assert_eq!(expected.amount, *dst);
}
}
fn transfer_tokens(
wh: &fake::WormholeKeeper,
contract: &mut Contract,
key: transfer::Key,
msg: Message,
index: u32,
num_signatures: usize,
) -> anyhow::Result<(Observation, Vec<AppResponse>)> {
let payload = serde_wormhole::to_vec(&msg).map(Binary::from).unwrap();
let o = Observation {
tx_hash: vec![0xd8u8; 20].into(),
timestamp: 0xec8d03d6,
nonce: 0x4343b191,
emitter_chain: key.emitter_chain(),
emitter_address: **key.emitter_address(),
sequence: key.sequence(),
consistency_level: 0,
payload,
};
let obs = to_binary(&vec![o.clone()]).unwrap();
let signatures = wh.sign(&obs);
let responses = signatures
.into_iter()
.take(num_signatures)
.map(|s| contract.submit_observations(obs.clone(), index, s))
.collect::<anyhow::Result<Vec<_>>>()?;
Ok((o, responses))
}
#[test]
fn round_trip() {
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 15);
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
let emitter_chain = 2;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2u16.into();
let recipient_chain = 14u16.into();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
let (o, _) =
transfer_tokens(&wh, &mut contract, key.clone(), msg, index, num_guardians).unwrap();
let expected = transfer::Data {
amount: Uint256::new(amount.0),
token_chain: token_chain.into(),
token_address: TokenAddress::new(token_address.0),
recipient_chain: recipient_chain.into(),
};
let actual = contract.query_transfer(key).unwrap();
assert_eq!(expected, actual.data);
assert_eq!(o.digest().unwrap(), actual.digest);
// Now send the tokens back.
let key = transfer::Key::new(
recipient_chain.into(),
[u16::from(recipient_chain) as u8; 32].into(),
91156748,
);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xe4u8; 32]),
recipient_chain: emitter_chain.into(),
fee: Amount([0u8; 32]),
};
let (o, _) =
transfer_tokens(&wh, &mut contract, key.clone(), msg, index, num_guardians).unwrap();
let expected = transfer::Data {
amount: Uint256::new(amount.0),
token_chain: token_chain.into(),
token_address: TokenAddress::new(token_address.0),
recipient_chain: emitter_chain,
};
let actual = contract.query_transfer(key).unwrap();
assert_eq!(expected, actual.data);
assert_eq!(o.digest().unwrap(), actual.digest);
// Now both balances should be zero.
let src = contract
.query_balance(account::Key::new(
emitter_chain,
token_chain.into(),
expected.token_address,
))
.unwrap();
assert_eq!(Uint256::zero(), *src);
let dst = contract
.query_balance(account::Key::new(
recipient_chain.into(),
token_chain.into(),
expected.token_address,
))
.unwrap();
assert_eq!(Uint256::zero(), *dst);
}
#[test]
fn missing_guardian_set() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
let emitter_chain = 2;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let recipient_chain = 14.into();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
transfer_tokens(&wh, &mut contract, key, msg, index + 1, num_guardians)
.expect_err("successfully submitted observations with invalid guardian set");
}
#[test]
fn expired_guardian_set() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let mut block = contract.app().block_info();
let num_guardians = wh.num_guardians();
let emitter_chain = 2;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let recipient_chain = 14.into();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
// Mark the guardian set expired.
wh.set_expiration(block.height);
block.height += 1;
contract.app_mut().set_block(block);
transfer_tokens(&wh, &mut contract, key, msg, index, num_guardians)
.expect_err("successfully submitted observations with expired guardian set");
}
#[test]
fn no_quorum() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let quorum = wh
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
let emitter_chain = 2;
let emitter_address = [emitter_chain as u8; 32];
let sequence = 37;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let recipient_chain = 14.into();
let key = transfer::Key::new(emitter_chain, emitter_address.into(), sequence);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
let (o, _) = transfer_tokens(&wh, &mut contract, key.clone(), msg, index, quorum - 1).unwrap();
let data = contract.query_pending_transfer(key.clone()).unwrap();
assert_eq!(emitter_chain, data[0].emitter_chain());
assert_eq!(&o.digest().unwrap(), data[0].digest());
assert_eq!(&o.tx_hash, data[0].tx_hash());
// Make sure the transfer hasn't yet been committed.
contract
.query_transfer(key)
.expect_err("transfer committed without quorum");
}
#[test]
fn missing_wrapped_account() {
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 15);
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
let quorum = wh
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
let emitter_chain = 14;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let recipient_chain = 2.into();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
let (_, responses) =
transfer_tokens(&wh, &mut contract, key.clone(), msg, index, num_guardians).unwrap();
for mut resp in responses.into_iter().skip(quorum - 1) {
let r = from_binary::<Vec<SubmitObservationResponse>>(&resp.data.take().unwrap()).unwrap();
assert_eq!(key, r[0].key);
if let ObservationStatus::Error(ref err) = r[0].status {
assert!(
err.contains("cannot burn wrapped tokens without an existing wrapped account"),
"{err}"
);
resp.assert_event(
&Event::new("wasm-ObservationError")
.add_attribute("key", serde_json_wasm::to_string(&key).unwrap()),
);
} else {
panic!(
"unexpected response for transfer with missing wrapped account {:?}",
r[0]
);
}
}
}
#[test]
fn missing_native_account() {
let emitter_chain = 14;
let recipient_chain = 2;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = [0xccu8; 32];
let token_chain = 2;
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 15);
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
let quorum = wh
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
// We need to set up a fake wrapped account so that the initial check succeeds.
let m = to_binary(&Modification {
sequence: 0,
chain_id: emitter_chain,
token_chain,
token_address: token_address.into(),
kind: Kind::Add,
amount: Uint256::new(amount.0),
reason: "fake wrapped balance for testing".into(),
})
.unwrap();
let signatures = wh.sign(&m);
contract.modify_balance(m, index, signatures).unwrap();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address: Address(token_address),
token_chain: token_chain.into(),
recipient: Address([0xb9u8; 32]),
recipient_chain: recipient_chain.into(),
fee: Amount([0u8; 32]),
};
let (_, responses) =
transfer_tokens(&wh, &mut contract, key.clone(), msg, index, num_guardians).unwrap();
for mut resp in responses.into_iter().skip(quorum - 1) {
let r = from_binary::<Vec<SubmitObservationResponse>>(&resp.data.take().unwrap()).unwrap();
assert_eq!(key, r[0].key);
if let ObservationStatus::Error(ref err) = r[0].status {
assert!(
err.contains("cannot unlock native tokens without an existing native account"),
"{err}"
);
resp.assert_event(
&Event::new("wasm-ObservationError")
.add_attribute("key", serde_json_wasm::to_string(&key).unwrap()),
);
} else {
panic!(
"unexpected response for transfer with missing native account {:?}",
r[0]
);
}
}
}
#[test]
fn repeated() {
const ITERATIONS: usize = 10;
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 3);
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
let emitter_chain = 2;
let recipient_chain = 14;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = [0xccu8; 32];
let token_chain = 2;
let msg = Message::Transfer {
amount,
token_address: Address(token_address),
token_chain: token_chain.into(),
recipient: Address([0xb9u8; 32]),
recipient_chain: recipient_chain.into(),
fee: Amount([0u8; 32]),
};
for i in 0..ITERATIONS {
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), i as u64);
transfer_tokens(
&wh,
&mut contract,
key.clone(),
msg.clone(),
index,
num_guardians,
)
.unwrap();
}
let expected = Uint256::new(amount.0) * Uint256::from(ITERATIONS as u128);
let src = contract
.query_balance(account::Key::new(
emitter_chain,
token_chain,
token_address.into(),
))
.unwrap();
assert_eq!(expected, *src);
let dst = contract
.query_balance(account::Key::new(
recipient_chain,
token_chain,
token_address.into(),
))
.unwrap();
assert_eq!(expected, *dst);
}
#[test]
fn wrapped_to_wrapped() {
let emitter_chain = 14;
let recipient_chain = 2;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = [0xccu8; 32];
let token_chain = 5;
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 15);
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
// We need an initial fake wrapped account.
let m = to_binary(&Modification {
sequence: 0,
chain_id: emitter_chain,
token_chain,
token_address: token_address.into(),
kind: Kind::Add,
amount: Uint256::new(amount.0),
reason: "fake wrapped balance for testing".into(),
})
.unwrap();
let signatures = wh.sign(&m);
contract.modify_balance(m, index, signatures).unwrap();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address: Address(token_address),
token_chain: token_chain.into(),
recipient: Address([0xb9u8; 32]),
recipient_chain: recipient_chain.into(),
fee: Amount([0u8; 32]),
};
let (o, _) =
transfer_tokens(&wh, &mut contract, key.clone(), msg, index, num_guardians).unwrap();
let expected = transfer::Data {
amount: Uint256::new(amount.0),
token_chain,
token_address: TokenAddress::new(token_address),
recipient_chain,
};
let actual = contract.query_transfer(key).unwrap();
assert_eq!(expected, actual.data);
assert_eq!(o.digest().unwrap(), actual.digest);
let src = contract
.query_balance(account::Key::new(
emitter_chain,
token_chain,
token_address.into(),
))
.unwrap();
assert_eq!(Uint256::zero(), *src);
let dst = contract
.query_balance(account::Key::new(
recipient_chain,
token_chain,
token_address.into(),
))
.unwrap();
assert_eq!(Uint256::new(amount.0), *dst);
}
#[test]
fn unknown_emitter() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
let quorum = wh
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
let emitter_chain = 14;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let recipient_chain = 2.into();
let key = transfer::Key::new(emitter_chain, [0xde; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
let (_, responses) =
transfer_tokens(&wh, &mut contract, key.clone(), msg, index, num_guardians).unwrap();
for mut resp in responses.into_iter().skip(quorum - 1) {
let r = from_binary::<Vec<SubmitObservationResponse>>(&resp.data.take().unwrap()).unwrap();
assert_eq!(key, r[0].key);
if let ObservationStatus::Error(ref err) = r[0].status {
assert!(err.contains("no registered emitter"));
resp.assert_event(
&Event::new("wasm-ObservationError")
.add_attribute("key", serde_json_wasm::to_string(&key).unwrap()),
);
} else {
panic!(
"unexpected response for transfer with unknown emitter address {:?}",
r[0]
);
}
}
}
#[test]
fn different_observations() {
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 3);
let index = wh.guardian_set_index();
let quorum = wh
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
// First submit some observations without enough signatures for quorum.
let emitter_chain = 2;
let fake_amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let fake_recipient_chain = 14.into();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let fake = Message::Transfer {
amount: fake_amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain: fake_recipient_chain,
fee: Amount([0u8; 32]),
};
transfer_tokens(&wh, &mut contract, key.clone(), fake, index, quorum - 1).unwrap();
// Make sure there is no committed transfer yet.
contract
.query_transfer(key.clone())
.expect_err("committed transfer without quorum");
// Now change the details of the transfer and resubmit with the same key.
let real_amount = Amount(Uint256::from(200u128).to_be_bytes());
let real_recipient_chain = 9.into();
let real = Message::Transfer {
amount: real_amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain: real_recipient_chain,
fee: Amount([0u8; 32]),
};
let (o, _) = transfer_tokens(&wh, &mut contract, key.clone(), real, index, quorum).unwrap();
contract
.query_pending_transfer(key.clone())
.expect_err("found pending transfer for observation with quorum");
let expected = transfer::Data {
amount: Uint256::new(real_amount.0),
token_chain: token_chain.into(),
token_address: TokenAddress::new(token_address.0),
recipient_chain: real_recipient_chain.into(),
};
let actual = contract.query_transfer(key).unwrap();
assert_eq!(expected, actual.data);
assert_eq!(o.digest().unwrap(), actual.digest);
let src = contract
.query_balance(account::Key::new(
emitter_chain,
token_chain.into(),
expected.token_address,
))
.unwrap();
assert_eq!(Uint256::new(real_amount.0), *src);
let dst = contract
.query_balance(account::Key::new(
real_recipient_chain.into(),
token_chain.into(),
expected.token_address,
))
.unwrap();
assert_eq!(Uint256::new(real_amount.0), *dst);
}
#[test]
fn emit_event_with_quorum() {
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 3);
let index = wh.guardian_set_index();
let quorum = wh
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
let num_guardians = wh.num_guardians();
let emitter_chain = 2;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let recipient_chain = 14.into();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
let (o, responses) =
transfer_tokens(&wh, &mut contract, key, msg, index, num_guardians).unwrap();
let expected = Event::new("wasm-Observation")
.add_attribute("tx_hash", serde_json_wasm::to_string(&o.tx_hash).unwrap())
.add_attribute(
"timestamp",
serde_json_wasm::to_string(&o.timestamp).unwrap(),
)
.add_attribute("nonce", serde_json_wasm::to_string(&o.nonce).unwrap())
.add_attribute(
"emitter_chain",
serde_json_wasm::to_string(&o.emitter_chain).unwrap(),
)
.add_attribute(
"emitter_address",
serde_json_wasm::to_string(&hex::encode(o.emitter_address)).unwrap(),
)
.add_attribute("sequence", serde_json_wasm::to_string(&o.sequence).unwrap())
.add_attribute(
"consistency_level",
serde_json_wasm::to_string(&o.consistency_level).unwrap(),
)
.add_attribute("payload", serde_json_wasm::to_string(&o.payload).unwrap());
assert_eq!(responses.len(), num_guardians);
for (i, r) in responses.into_iter().enumerate() {
if i < quorum - 1 || i >= quorum {
assert!(!r.has_event(&expected));
} else {
r.assert_event(&expected);
}
}
}
#[test]
fn duplicate_vaa() {
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 3);
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
let emitter_chain = 2;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let recipient_chain = 14.into();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
let (o, _) = transfer_tokens(&wh, &mut contract, key, msg, index, num_guardians).unwrap();
// Now try to submit a VAA for this transfer. This should fail since the transfer is already
// processed.
let body = Body {
timestamp: o.timestamp,
nonce: o.nonce,
emitter_chain: o.emitter_chain.into(),
emitter_address: Address(o.emitter_address),
sequence: o.sequence,
consistency_level: o.consistency_level,
payload: (),
};
let mut body_data = serde_wormhole::to_vec(&body).unwrap();
body_data.extend_from_slice(&o.payload);
let mut data = serde_wormhole::to_vec(&Header {
version: 1,
guardian_set_index: index,
signatures: wh.sign(&body_data),
})
.unwrap();
data.extend_from_slice(&body_data);
let err = contract
.submit_vaas(vec![data.into()])
.expect_err("successfully submitted duplicate VAA for committed transfer");
assert!(format!("{err:#}").contains("message already processed"));
}
#[test]
fn digest_mismatch() {
let (wh, mut contract) = proper_instantiate();
register_emitters(&wh, &mut contract, 3);
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
let emitter_chain = 2;
let amount = Amount(Uint256::from(500u128).to_be_bytes());
let token_address = Address([0xccu8; 32]);
let token_chain = 2.into();
let recipient_chain = 14.into();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
amount,
token_address,
token_chain,
recipient: Address([0xb9u8; 32]),
recipient_chain,
fee: Amount([0u8; 32]),
};
let (o, _) = transfer_tokens(&wh, &mut contract, key, msg, index, num_guardians).unwrap();
// Now try submitting a VAA with the same (chain, address, sequence) tuple but with
// different details.
let body = Body {
timestamp: o.timestamp,
nonce: o.nonce ^ u32::MAX,
emitter_chain: o.emitter_chain.into(),
emitter_address: Address(o.emitter_address),
sequence: o.sequence,
consistency_level: o.consistency_level,
payload: (),
};
let mut body_data = serde_wormhole::to_vec(&body).unwrap();
body_data.extend_from_slice(&o.payload);
let mut data = serde_wormhole::to_vec(&Header {
version: 1,
guardian_set_index: index,
signatures: wh.sign(&body_data),
})
.unwrap();
data.extend_from_slice(&body_data);
let err = contract
.submit_vaas(vec![data.into()])
.expect_err("successfully submitted duplicate VAA for committed transfer");
assert!(format!("{err:#}").contains("digest mismatch"));
}