/// 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(&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, 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, 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 = 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(&test); let w_state = take_shared(&test); let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); corevaa::destroy(vaa); return_shared(state); return_shared(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(&test); bridge_state::set_registered_emitter( &mut state, u16::from_u64(2), external_address::from_bytes(x"deadbeed"), // not deadbeef ); return_shared(state); }; next_tx(&mut test, admin); { let state = take_shared(&test); let w_state = take_shared(&test); let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); corevaa::destroy(vaa); return_shared(state); return_shared(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(&test); bridge_state::set_registered_emitter( &mut state, u16::from_u64(2), external_address::from_bytes(x"deadbeef"), ); return_shared(state); }; next_tx(&mut test, admin); { let state = take_shared(&test); let w_state = take_shared(&test); let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); corevaa::destroy(vaa); return_shared(state); return_shared(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(&test); bridge_state::set_registered_emitter( &mut state, u16::from_u64(2), external_address::from_bytes(x"deadbeef"), ); return_shared(state); }; next_tx(&mut test, admin); { let state = take_shared(&test); let w_state = take_shared(&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(state); return_shared(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(&test); bridge_state::set_registered_emitter( &mut state, u16::from_u64(2), external_address::from_bytes(x"deadbeef"), ); return_shared(state); }; next_tx(&mut test, admin); { let state = take_shared(&test); let w_state = take_shared(&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(state); return_shared(w_state); }; test_scenario::end(test); } }