348 lines
11 KiB
Rust
348 lines
11 KiB
Rust
mod helpers;
|
|
|
|
use accountant::state::{transfer, TokenAddress};
|
|
use cosmwasm_std::{from_binary, to_binary, Binary, Event, Uint256};
|
|
use global_accountant::msg::{Observation, ObservationStatus, SubmitObservationResponse};
|
|
use helpers::*;
|
|
use serde_wormhole::RawMessage;
|
|
use wormhole::{
|
|
token::Message,
|
|
vaa::{Body, Header, Vaa},
|
|
Address, Amount, Chain,
|
|
};
|
|
use wormhole_bindings::fake::WormholeKeeper;
|
|
|
|
fn create_transfer_vaas(wh: &WormholeKeeper, count: usize) -> (Vec<Vaa<Message>>, Vec<Binary>) {
|
|
let mut vaas = Vec::with_capacity(count);
|
|
let mut payloads = Vec::with_capacity(count);
|
|
|
|
for i in 0..count {
|
|
let (v, data) = sign_vaa_body(wh, create_vaa_body(i));
|
|
vaas.push(v);
|
|
payloads.push(data);
|
|
}
|
|
|
|
(vaas, payloads)
|
|
}
|
|
|
|
fn create_vaa_body(i: usize) -> Body<Message> {
|
|
Body {
|
|
timestamp: i as u32,
|
|
nonce: i as u32,
|
|
emitter_chain: (i as u16).into(),
|
|
emitter_address: Address([(i as u8); 32]),
|
|
sequence: i as u64,
|
|
consistency_level: 32,
|
|
payload: Message::Transfer {
|
|
amount: Amount(Uint256::from(i as u128).to_be_bytes()),
|
|
token_address: Address([(i + 1) as u8; 32]),
|
|
token_chain: (i as u16).into(),
|
|
recipient: Address([i as u8; 32]),
|
|
recipient_chain: ((i + 2) as u16).into(),
|
|
fee: Amount([0u8; 32]),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn transfer_data_from_token_message<P>(msg: Message<P>) -> transfer::Data {
|
|
match msg {
|
|
Message::Transfer {
|
|
amount,
|
|
token_address,
|
|
token_chain,
|
|
recipient_chain,
|
|
..
|
|
}
|
|
| Message::TransferWithPayload {
|
|
amount,
|
|
token_address,
|
|
token_chain,
|
|
recipient_chain,
|
|
..
|
|
} => transfer::Data {
|
|
amount: Uint256::from_be_bytes(amount.0),
|
|
token_address: TokenAddress::new(token_address.0),
|
|
token_chain: token_chain.into(),
|
|
recipient_chain: recipient_chain.into(),
|
|
},
|
|
_ => panic!("not a transfer payload"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn basic() {
|
|
const COUNT: usize = 7;
|
|
|
|
let (wh, mut contract) = proper_instantiate();
|
|
|
|
let (vaas, payloads) = create_transfer_vaas(&wh, COUNT);
|
|
|
|
let resp = contract.submit_vaas(payloads).unwrap();
|
|
|
|
for v in vaas {
|
|
let key = transfer::Key::new(
|
|
v.emitter_chain.into(),
|
|
TokenAddress::new(v.emitter_address.0),
|
|
v.sequence,
|
|
);
|
|
let (_, body) = v.into();
|
|
let digest = body.digest().unwrap().secp256k_hash;
|
|
let data = transfer_data_from_token_message(body.payload);
|
|
let tx = contract.query_transfer(key.clone()).unwrap();
|
|
assert_eq!(data, tx.data);
|
|
assert_eq!(&digest[..], &*tx.digest);
|
|
resp.assert_event(
|
|
&Event::new("wasm-Transfer")
|
|
.add_attribute("key", serde_json_wasm::to_string(&key).unwrap())
|
|
.add_attribute("data", serde_json_wasm::to_string(&data).unwrap()),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_transfer() {
|
|
let (wh, mut contract) = proper_instantiate();
|
|
|
|
let mut body = create_vaa_body(1);
|
|
match body.payload {
|
|
Message::Transfer {
|
|
ref mut token_chain,
|
|
recipient_chain,
|
|
..
|
|
}
|
|
| Message::TransferWithPayload {
|
|
ref mut token_chain,
|
|
recipient_chain,
|
|
..
|
|
} => *token_chain = recipient_chain,
|
|
_ => panic!("not a transfer payload"),
|
|
}
|
|
|
|
let (_, data) = sign_vaa_body(&wh, body);
|
|
contract
|
|
.submit_vaas(vec![data])
|
|
.expect_err("successfully submitted VAA containing invalid transfer");
|
|
}
|
|
|
|
#[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 (mut v, _) = sign_vaa_body(&wh, create_vaa_body(3));
|
|
v.signatures.truncate(quorum - 1);
|
|
|
|
let data = serde_wormhole::to_vec(&v).map(From::from).unwrap();
|
|
|
|
contract
|
|
.submit_vaas(vec![data])
|
|
.expect_err("successfully submitted VAA without a quorum of signatures");
|
|
}
|
|
|
|
#[test]
|
|
fn bad_serialization() {
|
|
let (wh, mut contract) = proper_instantiate();
|
|
|
|
let (v, _) = sign_vaa_body(&wh, create_vaa_body(3));
|
|
|
|
// Rather than using the wormhole wire format use cosmwasm json.
|
|
let data = to_binary(&v).unwrap();
|
|
|
|
contract
|
|
.submit_vaas(vec![data])
|
|
.expect_err("successfully submitted VAA with bad serialization");
|
|
}
|
|
|
|
#[test]
|
|
fn bad_signature() {
|
|
let (wh, mut contract) = proper_instantiate();
|
|
let (mut v, _) = sign_vaa_body(&wh, create_vaa_body(3));
|
|
|
|
// Flip a bit in the first signature so it becomes invalid.
|
|
v.signatures[0].signature[0] ^= 1;
|
|
|
|
let data = serde_wormhole::to_vec(&v).map(From::from).unwrap();
|
|
contract
|
|
.submit_vaas(vec![data])
|
|
.expect_err("successfully submitted VAA with bad signature");
|
|
}
|
|
|
|
#[test]
|
|
fn non_transfer_message() {
|
|
let (wh, mut contract) = proper_instantiate();
|
|
let body: Body<Message> = Body {
|
|
timestamp: 2,
|
|
nonce: 2,
|
|
emitter_chain: Chain::Ethereum,
|
|
emitter_address: Address([2u8; 32]),
|
|
sequence: 2,
|
|
consistency_level: 32,
|
|
payload: Message::AssetMeta {
|
|
token_address: Address([
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xbe, 0xef, 0xfa, 0xce,
|
|
]),
|
|
token_chain: Chain::Ethereum,
|
|
decimals: 12,
|
|
symbol: "BEEF".into(),
|
|
name: "Beef face Token".into(),
|
|
},
|
|
};
|
|
|
|
let (_, data) = sign_vaa_body(&wh, body);
|
|
contract
|
|
.submit_vaas(vec![data])
|
|
.expect_err("successfully submitted VAA with non-transfer message");
|
|
}
|
|
|
|
#[test]
|
|
fn transfer_with_payload() {
|
|
let (wh, mut contract) = proper_instantiate();
|
|
let payload = [0x88; 17];
|
|
let body = Body {
|
|
timestamp: 2,
|
|
nonce: 2,
|
|
emitter_chain: Chain::Ethereum,
|
|
emitter_address: Address([2u8; 32]),
|
|
sequence: 2,
|
|
consistency_level: 32,
|
|
payload: Message::TransferWithPayload {
|
|
amount: Amount(Uint256::from(2u128).to_be_bytes()),
|
|
token_address: Address([3u8; 32]),
|
|
token_chain: Chain::Ethereum,
|
|
recipient: Address([2u8; 32]),
|
|
recipient_chain: Chain::Bsc,
|
|
sender_address: Address([0u8; 32]),
|
|
payload: RawMessage::new(&payload[..]),
|
|
},
|
|
};
|
|
|
|
let data = serde_wormhole::to_vec(&body).unwrap();
|
|
|
|
let signatures = wh.sign(&data);
|
|
let header = Header {
|
|
version: 1,
|
|
guardian_set_index: wh.guardian_set_index(),
|
|
signatures,
|
|
};
|
|
|
|
let v = Vaa::from((header, body));
|
|
|
|
let data = serde_wormhole::to_vec(&v).unwrap();
|
|
let resp = contract.submit_vaas(vec![data.into()]).unwrap();
|
|
|
|
let key = transfer::Key::new(
|
|
v.emitter_chain.into(),
|
|
TokenAddress::new(v.emitter_address.0),
|
|
v.sequence,
|
|
);
|
|
let (_, body) = v.into();
|
|
let digest = body.digest().unwrap().secp256k_hash;
|
|
let data = transfer_data_from_token_message(body.payload);
|
|
let tx = contract.query_transfer(key.clone()).unwrap();
|
|
assert_eq!(data, tx.data);
|
|
assert_eq!(&digest[..], &*tx.digest);
|
|
resp.assert_event(
|
|
&Event::new("wasm-Transfer")
|
|
.add_attribute("key", serde_json_wasm::to_string(&key).unwrap())
|
|
.add_attribute("data", serde_json_wasm::to_string(&data).unwrap()),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unsupported_version() {
|
|
let (wh, mut contract) = proper_instantiate();
|
|
|
|
let (mut v, _) = sign_vaa_body(&wh, create_vaa_body(6));
|
|
v.version = 0;
|
|
|
|
let data = serde_wormhole::to_vec(&v).map(From::from).unwrap();
|
|
|
|
contract
|
|
.submit_vaas(vec![data])
|
|
.expect_err("successfully submitted VAA with unsupported version");
|
|
}
|
|
|
|
#[test]
|
|
fn reobservation() {
|
|
let (wh, mut contract) = proper_instantiate();
|
|
|
|
let (v, data) = sign_vaa_body(&wh, create_vaa_body(6));
|
|
contract
|
|
.submit_vaas(vec![data])
|
|
.expect("failed to submit VAA");
|
|
|
|
// Now try submitting the same transfer as an observation. This can happen when a guardian
|
|
// re-observes a tx.
|
|
let o = Observation {
|
|
tx_hash: vec![0x55u8; 20].into(),
|
|
timestamp: v.timestamp,
|
|
nonce: v.nonce,
|
|
emitter_chain: v.emitter_chain.into(),
|
|
emitter_address: v.emitter_address.0,
|
|
sequence: v.sequence,
|
|
consistency_level: v.consistency_level,
|
|
payload: serde_wormhole::to_vec(&v.payload).unwrap().into(),
|
|
};
|
|
let key = transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
|
|
|
|
let obs = to_binary(&vec![o]).unwrap();
|
|
let index = wh.guardian_set_index();
|
|
let signatures = wh.sign(&obs);
|
|
for s in signatures {
|
|
let resp = contract.submit_observations(obs.clone(), index, s).unwrap();
|
|
let mut responses: Vec<SubmitObservationResponse> =
|
|
from_binary(&resp.data.unwrap()).unwrap();
|
|
|
|
assert_eq!(1, responses.len());
|
|
let d = responses.remove(0);
|
|
assert_eq!(key, d.key);
|
|
assert!(matches!(d.status, ObservationStatus::Committed));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn digest_mismatch() {
|
|
let (wh, mut contract) = proper_instantiate();
|
|
|
|
let (v, data) = sign_vaa_body(&wh, create_vaa_body(6));
|
|
contract
|
|
.submit_vaas(vec![data])
|
|
.expect("failed to submit VAA");
|
|
|
|
// Now try submitting an observation with the same (chain, address, sequence) tuple but with
|
|
// different details.
|
|
let o = Observation {
|
|
tx_hash: vec![0x55u8; 20].into(),
|
|
timestamp: v.timestamp,
|
|
nonce: v.nonce ^ u32::MAX,
|
|
emitter_chain: v.emitter_chain.into(),
|
|
emitter_address: v.emitter_address.0,
|
|
sequence: v.sequence,
|
|
consistency_level: v.consistency_level,
|
|
payload: serde_wormhole::to_vec(&v.payload).unwrap().into(),
|
|
};
|
|
|
|
let key = transfer::Key::new(o.emitter_chain, o.emitter_address.into(), o.sequence);
|
|
let obs = to_binary(&vec![o]).unwrap();
|
|
let index = wh.guardian_set_index();
|
|
let signatures = wh.sign(&obs);
|
|
for s in signatures {
|
|
let resp = contract.submit_observations(obs.clone(), index, s).unwrap();
|
|
let responses = from_binary::<Vec<SubmitObservationResponse>>(&resp.data.unwrap()).unwrap();
|
|
assert_eq!(key, responses[0].key);
|
|
if let ObservationStatus::Error(ref err) = responses[0].status {
|
|
assert!(err.contains("digest mismatch"));
|
|
} else {
|
|
panic!(
|
|
"unexpected status for observation with mismatched digest: {:?}",
|
|
responses[0].status
|
|
);
|
|
}
|
|
}
|
|
}
|