222 lines
8.7 KiB
Plaintext
222 lines
8.7 KiB
Plaintext
|
/// Token Bridge VAA utilities
|
||
|
module token_bridge::vaa {
|
||
|
use std::option;
|
||
|
use sui::tx_context::{TxContext};
|
||
|
|
||
|
use wormhole::myvaa::{Self as corevaa, VAA};
|
||
|
use wormhole::state::{State as WormholeState};
|
||
|
use wormhole::external_address::{ExternalAddress};
|
||
|
|
||
|
use token_bridge::bridge_state::{Self as bridge_state, BridgeState};
|
||
|
|
||
|
//friend token_bridge::contract_upgrade;
|
||
|
friend token_bridge::register_chain;
|
||
|
friend token_bridge::wrapped;
|
||
|
friend token_bridge::complete_transfer;
|
||
|
|
||
|
#[test_only]
|
||
|
friend token_bridge::token_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(bridge_state: &mut BridgeState, vaa: &VAA) {
|
||
|
// this calls set::add which aborts if the element already exists
|
||
|
bridge_state::store_consumed_vaa(bridge_state, corevaa::get_hash(vaa));
|
||
|
}
|
||
|
|
||
|
/// Asserts that the VAA is from a known token bridge.
|
||
|
public fun assert_known_emitter(state: &BridgeState, vm: &VAA) {
|
||
|
let maybe_emitter = bridge_state::get_registered_emitter(state, &corevaa::get_emitter_chain(vm));
|
||
|
assert!(option::is_some<ExternalAddress>(&maybe_emitter), E_UNKNOWN_CHAIN);
|
||
|
|
||
|
let emitter = option::extract(&mut maybe_emitter);
|
||
|
assert!(emitter == corevaa::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(
|
||
|
wormhole_state: &mut WormholeState,
|
||
|
bridge_state: &mut BridgeState,
|
||
|
vaa: vector<u8>,
|
||
|
ctx: &mut TxContext
|
||
|
): VAA {
|
||
|
let vaa = parse_and_verify(wormhole_state, bridge_state, vaa, ctx);
|
||
|
replay_protect(bridge_state, &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(wormhole_state: &mut WormholeState, bridge_state: &BridgeState, vaa: vector<u8>, ctx:&mut TxContext): VAA {
|
||
|
let vaa = corevaa::parse_and_verify(wormhole_state, vaa, ctx);
|
||
|
assert_known_emitter(bridge_state, &vaa);
|
||
|
vaa
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[test_only]
|
||
|
module token_bridge::token_bridge_vaa_test{
|
||
|
use sui::test_scenario::{Self, Scenario, next_tx, ctx, take_shared, return_shared};
|
||
|
|
||
|
use wormhole::state::{State};
|
||
|
use wormhole::myvaa::{Self as corevaa};
|
||
|
use wormhole::myu16::{Self as u16};
|
||
|
use wormhole::external_address::{Self};
|
||
|
|
||
|
use token_bridge::bridge_state::{Self, BridgeState};
|
||
|
use token_bridge::vaa::{Self};
|
||
|
use token_bridge::bridge_state_test::{set_up_wormhole_core_and_token_bridges};
|
||
|
|
||
|
fun scenario(): Scenario { test_scenario::begin(@0x123233) }
|
||
|
fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) }
|
||
|
|
||
|
/// VAA sent from the ethereum token bridge 0xdeadbeef
|
||
|
const VAA: vector<u8> = x"01000000000100102d399190fa61daccb11c2ea4f7a3db3a9365e5936bcda4cded87c1b9eeb095173514f226256d5579af71d4089eb89496befb998075ba94cd1d4460c5c57b84000000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef0000000002634973000200000000000000000000000000000000000000000000000000000000beefface00020c0000000000000000000000000000000000000000000000000000000042454546000000000000000000000000000000000042656566206661636520546f6b656e";
|
||
|
|
||
|
#[test]
|
||
|
#[expected_failure(abort_code = vaa::E_UNKNOWN_CHAIN)]
|
||
|
fun test_unknown_chain() {
|
||
|
let (admin, _, _) = people();
|
||
|
let test = scenario();
|
||
|
test = set_up_wormhole_core_and_token_bridges(admin, test);
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
let w_state = take_shared<State>(&test);
|
||
|
let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test));
|
||
|
corevaa::destroy(vaa);
|
||
|
return_shared<BridgeState>(state);
|
||
|
return_shared<State>(w_state);
|
||
|
};
|
||
|
test_scenario::end(test);
|
||
|
}
|
||
|
|
||
|
|
||
|
#[test]
|
||
|
#[expected_failure(abort_code = vaa::E_UNKNOWN_EMITTER)]
|
||
|
fun test_unknown_emitter() {
|
||
|
let (admin, _, _) = people();
|
||
|
let test = scenario();
|
||
|
test = set_up_wormhole_core_and_token_bridges(admin, test);
|
||
|
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
bridge_state::set_registered_emitter(
|
||
|
&mut state,
|
||
|
u16::from_u64(2),
|
||
|
external_address::from_bytes(x"deadbeed"), // not deadbeef
|
||
|
);
|
||
|
return_shared<BridgeState>(state);
|
||
|
};
|
||
|
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
let w_state = take_shared<State>(&test);
|
||
|
let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test));
|
||
|
corevaa::destroy(vaa);
|
||
|
return_shared<BridgeState>(state);
|
||
|
return_shared<State>(w_state);
|
||
|
};
|
||
|
test_scenario::end(test);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fun test_known_emitter() {
|
||
|
let (admin, _, _) = people();
|
||
|
let test = scenario();
|
||
|
test = set_up_wormhole_core_and_token_bridges(admin, test);
|
||
|
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
bridge_state::set_registered_emitter(
|
||
|
&mut state,
|
||
|
u16::from_u64(2),
|
||
|
external_address::from_bytes(x"deadbeef"),
|
||
|
);
|
||
|
return_shared<BridgeState>(state);
|
||
|
};
|
||
|
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
let w_state = take_shared<State>(&test);
|
||
|
let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test));
|
||
|
corevaa::destroy(vaa);
|
||
|
return_shared<BridgeState>(state);
|
||
|
return_shared<State>(w_state);
|
||
|
};
|
||
|
test_scenario::end(test);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
#[expected_failure(abort_code = 0, location=0000000000000000000000000000000000000002::dynamic_field)]
|
||
|
fun test_replay_protection_works() {
|
||
|
let (admin, _, _) = people();
|
||
|
let test = scenario();
|
||
|
test = set_up_wormhole_core_and_token_bridges(admin, test);
|
||
|
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
bridge_state::set_registered_emitter(
|
||
|
&mut state,
|
||
|
u16::from_u64(2),
|
||
|
external_address::from_bytes(x"deadbeef"),
|
||
|
);
|
||
|
return_shared<BridgeState>(state);
|
||
|
};
|
||
|
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
let w_state = take_shared<State>(&test);
|
||
|
|
||
|
// try to use the VAA twice
|
||
|
let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test));
|
||
|
corevaa::destroy(vaa);
|
||
|
let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test));
|
||
|
corevaa::destroy(vaa);
|
||
|
return_shared<BridgeState>(state);
|
||
|
return_shared<State>(w_state);
|
||
|
};
|
||
|
test_scenario::end(test);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fun test_can_verify_after_replay_protect() {
|
||
|
let (admin, _, _) = people();
|
||
|
let test = scenario();
|
||
|
test = set_up_wormhole_core_and_token_bridges(admin, test);
|
||
|
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
bridge_state::set_registered_emitter(
|
||
|
&mut state,
|
||
|
u16::from_u64(2),
|
||
|
external_address::from_bytes(x"deadbeef"),
|
||
|
);
|
||
|
return_shared<BridgeState>(state);
|
||
|
};
|
||
|
|
||
|
next_tx(&mut test, admin); {
|
||
|
let state = take_shared<BridgeState>(&test);
|
||
|
let w_state = take_shared<State>(&test);
|
||
|
|
||
|
// parse and verify and replay protect VAA the first time, don't replay protect the second time
|
||
|
let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test));
|
||
|
corevaa::destroy(vaa);
|
||
|
let vaa = vaa::parse_and_verify(&mut w_state, &mut state, VAA, ctx(&mut test));
|
||
|
corevaa::destroy(vaa);
|
||
|
return_shared<BridgeState>(state);
|
||
|
return_shared<State>(w_state);
|
||
|
};
|
||
|
test_scenario::end(test);
|
||
|
}
|
||
|
|
||
|
}
|