wormhole/sui/token_bridge/sources/governance/register_chain.move

298 lines
9.7 KiB
Plaintext

// SPDX-License-Identifier: Apache 2
/// This module implements handling a governance VAA to enact registering a
/// foreign Token Bridge for a particular chain ID.
module token_bridge::register_chain {
use sui::table::{Self};
use wormhole::bytes::{Self};
use wormhole::cursor::{Self};
use wormhole::external_address::{Self, ExternalAddress};
use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt};
use token_bridge::state::{Self, State, LatestOnly};
/// Cannot register chain ID == 0.
const E_INVALID_EMITTER_CHAIN: u64 = 0;
/// Emitter already exists for a given chain ID.
const E_EMITTER_ALREADY_REGISTERED: u64 = 1;
/// Specific governance payload ID (action) for registering foreign Token
/// Bridge contract address.
const ACTION_REGISTER_CHAIN: u8 = 1;
struct GovernanceWitness has drop {}
struct RegisterChain {
chain: u16,
contract_address: ExternalAddress,
}
public fun authorize_governance(
token_bridge_state: &State
): DecreeTicket<GovernanceWitness> {
governance_message::authorize_verify_global(
GovernanceWitness {},
state::governance_chain(token_bridge_state),
state::governance_contract(token_bridge_state),
state::governance_module(),
ACTION_REGISTER_CHAIN
)
}
public fun register_chain(
token_bridge_state: &mut State,
receipt: DecreeReceipt<GovernanceWitness>
): (
u16,
ExternalAddress
) {
// This capability ensures that the current build version is used.
let latest_only = state::assert_latest_only(token_bridge_state);
let payload =
governance_message::take_payload(
state::borrow_mut_consumed_vaas(
&latest_only,
token_bridge_state
),
receipt
);
handle_register_chain(&latest_only, token_bridge_state, payload)
}
fun handle_register_chain(
latest_only: &LatestOnly,
token_bridge_state: &mut State,
governance_payload: vector<u8>
): (
u16,
ExternalAddress
) {
// Deserialize the payload as amount to change the Wormhole fee.
let RegisterChain {
chain,
contract_address
} = deserialize(governance_payload);
register_new_emitter(
latest_only,
token_bridge_state,
chain,
contract_address
);
(chain, contract_address)
}
fun deserialize(payload: vector<u8>): RegisterChain {
let cur = cursor::new(payload);
// This amount cannot be greater than max u64.
let chain = bytes::take_u16_be(&mut cur);
let contract_address = external_address::take_bytes(&mut cur);
cursor::destroy_empty(cur);
RegisterChain { chain, contract_address}
}
/// Add a new Token Bridge emitter to the registry. This method will abort
/// if an emitter is already registered for a particular chain ID.
///
/// See `register_chain` module for more info.
fun register_new_emitter(
latest_only: &LatestOnly,
token_bridge_state: &mut State,
chain: u16,
contract_address: ExternalAddress
) {
assert!(chain != 0, E_INVALID_EMITTER_CHAIN);
let registry =
state::borrow_mut_emitter_registry(latest_only, token_bridge_state);
assert!(
!table::contains(registry, chain),
E_EMITTER_ALREADY_REGISTERED
);
table::add(registry, chain, contract_address);
}
#[test_only]
public fun register_new_emitter_test_only(
token_bridge_state: &mut State,
chain: u16,
contract_address: ExternalAddress
) {
// This capability ensures that the current build version is used.
let latest_only = state::assert_latest_only(token_bridge_state);
register_new_emitter(
&latest_only,
token_bridge_state,
chain,
contract_address
);
}
#[test_only]
public fun action(): u8 {
ACTION_REGISTER_CHAIN
}
}
#[test_only]
module token_bridge::register_chain_tests {
use sui::table::{Self};
use sui::test_scenario::{Self};
use wormhole::bytes::{Self};
use wormhole::cursor::{Self};
use wormhole::external_address::{Self};
use wormhole::governance_message::{Self};
use wormhole::wormhole_scenario::{
parse_and_verify_vaa,
verify_governance_vaa
};
use token_bridge::register_chain::{Self};
use token_bridge::state::{Self};
use token_bridge::token_bridge_scenario::{
person,
return_state,
set_up_wormhole_and_token_bridge,
take_state
};
const VAA_REGISTER_CHAIN_1: vector<u8> =
x"01000000000100dd8cf046ad6dd17b2b5130d236b3545350899ac33b5c9e93e4d8c3e0da718a351c3f76cb9ddb15a0f0d7db7b1dded2b5e79c2f6e76dde6d8ed4bcb9cb461eb480100bc614e0000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000101000000000000000000000000000000000000000000546f6b656e4272696467650100000002000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
const VAA_REGISTER_SAME_CHAIN: vector<u8> =
x"01000000000100847ca782db7616135de4a835ed5b12ba7946bbd39f70ecd9912ec55bdc9cb6c6215c98d6ad5c8d7253c2bb0fb0f8df0dc6591408c366cf0c09e58abcfb8c0abe0000bc614e0000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000101000000000000000000000000000000000000000000546f6b656e427269646765010000000200000000000000000000000000000000000000000000000000000000deafbeef";
#[test]
fun test_register_chain() {
// Testing this method.
use token_bridge::register_chain::{register_chain};
// Set up.
let caller = person();
let my_scenario = test_scenario::begin(caller);
let scenario = &mut my_scenario;
// Initialize Wormhole and Token Bridge.
let wormhole_fee = 350;
set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
// Prepare test to execute `set_fee`.
test_scenario::next_tx(scenario, caller);
let token_bridge_state = take_state(scenario);
// Check that the emitter is not registered.
let expected_chain = 2;
{
let registry = state::borrow_emitter_registry(&token_bridge_state);
assert!(!table::contains(registry, expected_chain), 0);
};
let verified_vaa = parse_and_verify_vaa(scenario, VAA_REGISTER_CHAIN_1);
let ticket = register_chain::authorize_governance(&token_bridge_state);
let receipt =
verify_governance_vaa(scenario, verified_vaa, ticket);
let (
chain,
contract_address
) = register_chain(&mut token_bridge_state, receipt);
assert!(chain == expected_chain, 0);
let expected_contract =
external_address::from_address(
@0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
);
assert!(contract_address == expected_contract, 0);
{
let registry = state::borrow_emitter_registry(&token_bridge_state);
assert!(*table::borrow(registry, expected_chain) == expected_contract, 0);
};
// Clean up.
return_state(token_bridge_state);
// Done.
test_scenario::end(my_scenario);
}
#[test]
#[expected_failure(abort_code = register_chain::E_EMITTER_ALREADY_REGISTERED)]
fun test_cannot_register_chain_already_registered() {
// Testing this method.
use token_bridge::register_chain::{register_chain};
// Set up.
let caller = person();
let my_scenario = test_scenario::begin(caller);
let scenario = &mut my_scenario;
// Initialize Wormhole and Token Bridge.
let wormhole_fee = 350;
set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
// Prepare test to execute `set_fee`.
test_scenario::next_tx(scenario, caller);
let token_bridge_state = take_state(scenario);
let verified_vaa = parse_and_verify_vaa(scenario, VAA_REGISTER_CHAIN_1);
let ticket = register_chain::authorize_governance(&token_bridge_state);
let receipt =
verify_governance_vaa(scenario, verified_vaa, ticket);
let (
chain,
_
) = register_chain(&mut token_bridge_state, receipt);
// Check registry.
let expected_contract =
*table::borrow(
state::borrow_emitter_registry(&token_bridge_state),
chain
);
// Ignore effects.
test_scenario::next_tx(scenario, caller);
let verified_vaa =
parse_and_verify_vaa(scenario, VAA_REGISTER_SAME_CHAIN);
let payload =
governance_message::take_decree(
wormhole::vaa::payload(&verified_vaa)
);
let cur = cursor::new(payload);
// Show this payload is attempting to register the same chain ID.
let another_chain = bytes::take_u16_be(&mut cur);
assert!(chain == another_chain, 0);
let another_contract = external_address::take_bytes(&mut cur);
assert!(another_contract != expected_contract, 0);
// No more payload to read.
cursor::destroy_empty(cur);
// Ignore effects.
test_scenario::next_tx(scenario, caller);
let ticket = register_chain::authorize_governance(&token_bridge_state);
let receipt =
verify_governance_vaa(scenario, verified_vaa, ticket);
// You shall not pass!
register_chain(&mut token_bridge_state, receipt);
abort 42
}
}