993 lines
32 KiB
Rust
993 lines
32 KiB
Rust
mod helpers;
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use accountant::state::{account, transfer, Kind, Modification, TokenAddress};
|
|
use cosmwasm_std::{from_binary, to_binary, Binary, Event, Uint256};
|
|
use cw_multi_test::AppResponse;
|
|
use global_accountant::msg::{Observation, ObservationStatus, SubmitObservationResponse};
|
|
use helpers::*;
|
|
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"));
|
|
}
|