141 lines
5.2 KiB
Plaintext
141 lines
5.2 KiB
Plaintext
/// Token Bridge VAA utilities
|
|
module nft_bridge::vaa {
|
|
use std::option;
|
|
use wormhole::vaa::{Self, VAA};
|
|
use nft_bridge::state;
|
|
|
|
friend nft_bridge::complete_transfer;
|
|
friend nft_bridge::contract_upgrade;
|
|
friend nft_bridge::register_chain;
|
|
friend nft_bridge::wrapped;
|
|
|
|
#[test_only]
|
|
friend nft_bridge::vaa_test;
|
|
|
|
/// We have no registration for this chain
|
|
const E_UNKNOWN_CHAIN: u64 = 0;
|
|
/// We have a registration, but it's different from what's given
|
|
const E_UNKNOWN_EMITTER: u64 = 1;
|
|
|
|
/// Aborts if the VAA has already been consumed. Marks the VAA as consumed
|
|
/// the first time around.
|
|
public(friend) fun replay_protect(vaa: &VAA) {
|
|
// this calls set::add which aborts if the element already exists
|
|
state::set_vaa_consumed(vaa::get_hash(vaa));
|
|
}
|
|
|
|
/// Asserts that the VAA is from a known token bridge.
|
|
public fun assert_known_emitter(vm: &VAA) {
|
|
let maybe_emitter = state::get_registered_emitter(vaa::get_emitter_chain(vm));
|
|
assert!(option::is_some(&maybe_emitter), E_UNKNOWN_CHAIN);
|
|
|
|
let emitter = option::extract(&mut maybe_emitter);
|
|
assert!(emitter == vaa::get_emitter_address(vm), E_UNKNOWN_EMITTER);
|
|
}
|
|
|
|
/// Parses, verifies, and replay protects a token bridge VAA.
|
|
/// Aborts if the VAA is not from a known token bridge emitter.
|
|
///
|
|
/// Has a 'friend' visibility so that it's only callable by the token bridge
|
|
/// (otherwise the replay protection could be abused to DoS the bridge)
|
|
public(friend) fun parse_verify_and_replay_protect(vaa: vector<u8>): VAA {
|
|
let vaa = parse_and_verify(vaa);
|
|
replay_protect(&vaa);
|
|
vaa
|
|
}
|
|
|
|
/// Parses, and verifies a token bridge VAA.
|
|
/// Aborts if the VAA is not from a known token bridge emitter.
|
|
public fun parse_and_verify(vaa: vector<u8>): VAA {
|
|
let vaa = vaa::parse_and_verify(vaa);
|
|
assert_known_emitter(&vaa);
|
|
vaa
|
|
}
|
|
}
|
|
|
|
#[test_only]
|
|
module nft_bridge::vaa_test {
|
|
use nft_bridge::vaa;
|
|
use nft_bridge::state;
|
|
use nft_bridge::nft_bridge;
|
|
|
|
use wormhole::vaa as core_vaa;
|
|
use wormhole::wormhole;
|
|
use wormhole::u16;
|
|
use wormhole::external_address;
|
|
|
|
/// VAA sent from the ethereum token bridge 0xdeadbeef
|
|
const VAA: vector<u8> = x"01000000000100102d399190fa61daccb11c2ea4f7a3db3a9365e5936bcda4cded87c1b9eeb095173514f226256d5579af71d4089eb89496befb998075ba94cd1d4460c5c57b84000000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef0000000002634973000200000000000000000000000000000000000000000000000000000000beefface00020c0000000000000000000000000000000000000000000000000000000042454546000000000000000000000000000000000042656566206661636520546f6b656e";
|
|
|
|
fun setup(deployer: &signer) {
|
|
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
|
|
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
|
|
wormhole::init_test(
|
|
22,
|
|
1,
|
|
x"0000000000000000000000000000000000000000000000000000000000000004",
|
|
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
|
|
0
|
|
);
|
|
nft_bridge::init_test(deployer);
|
|
}
|
|
|
|
#[test(deployer = @deployer)]
|
|
#[expected_failure(abort_code = 0, location = nft_bridge::vaa)] // E_UNKNOWN_CHAIN
|
|
public fun test_unknown_chain(deployer: &signer) {
|
|
setup(deployer);
|
|
let vaa = vaa::parse_verify_and_replay_protect(VAA);
|
|
core_vaa::destroy(vaa);
|
|
}
|
|
|
|
#[test(deployer = @deployer)]
|
|
#[expected_failure(abort_code = 1, location = nft_bridge::vaa)] // E_UNKNOWN_EMITTER
|
|
public fun test_unknown_emitter(deployer: &signer) {
|
|
setup(deployer);
|
|
state::set_registered_emitter(
|
|
u16::from_u64(2),
|
|
external_address::from_bytes(x"deadbeed"), // not deadbeef
|
|
);
|
|
let vaa = vaa::parse_verify_and_replay_protect(VAA);
|
|
core_vaa::destroy(vaa);
|
|
}
|
|
|
|
#[test(deployer = @deployer)]
|
|
public fun test_known_emitter(deployer: &signer) {
|
|
setup(deployer);
|
|
state::set_registered_emitter(
|
|
u16::from_u64(2),
|
|
external_address::from_bytes(x"deadbeef"),
|
|
);
|
|
let vaa = vaa::parse_verify_and_replay_protect(VAA);
|
|
core_vaa::destroy(vaa);
|
|
}
|
|
|
|
#[test(deployer = @deployer)]
|
|
#[expected_failure(abort_code = 25607, location = 0x1::table)] // add_box error
|
|
public fun test_replay_protect(deployer: &signer) {
|
|
setup(deployer);
|
|
state::set_registered_emitter(
|
|
u16::from_u64(2),
|
|
external_address::from_bytes(x"deadbeef"),
|
|
);
|
|
let vaa = vaa::parse_verify_and_replay_protect(VAA);
|
|
core_vaa::destroy(vaa);
|
|
let vaa = vaa::parse_verify_and_replay_protect(VAA);
|
|
core_vaa::destroy(vaa);
|
|
}
|
|
|
|
#[test(deployer = @deployer)]
|
|
public fun test_can_verify_after_replay_protect(deployer: &signer) {
|
|
setup(deployer);
|
|
state::set_registered_emitter(
|
|
u16::from_u64(2),
|
|
external_address::from_bytes(x"deadbeef"),
|
|
);
|
|
let vaa = vaa::parse_verify_and_replay_protect(VAA);
|
|
core_vaa::destroy(vaa);
|
|
let vaa = vaa::parse_and_verify(VAA);
|
|
core_vaa::destroy(vaa);
|
|
}
|
|
}
|