643 lines
22 KiB
Plaintext
643 lines
22 KiB
Plaintext
// SPDX-License-Identifier: Apache 2
|
|
|
|
/// This module implements methods that create a specific coin type reflecting a
|
|
/// wrapped (foreign) asset, whose metadata is encoded in a VAA sent from
|
|
/// another network.
|
|
///
|
|
/// Wrapped assets are created in two steps.
|
|
/// 1. `prepare_registration`: This method creates a new `TreasuryCap` for a
|
|
/// given coin type and wraps an encoded asset metadata VAA. We require a
|
|
/// one-time witness (OTW) to throw an explicit error (even though it is
|
|
/// redundant with what `create_currency` requires). This coin will
|
|
/// be published using this method, meaning the `init` method in that
|
|
/// untrusted package will have the asset's decimals hard-coded for its
|
|
/// coin metadata. A `WrappedAssetSetup` object is transferred to the
|
|
/// transaction sender.
|
|
/// 2. `complete_registration`: This method destroys the `WrappedAssetSetup`
|
|
/// object by unpacking its `TreasuryCap`, which will be warehoused in the
|
|
/// `TokenRegistry`. The shared coin metadata object will be updated to
|
|
/// reflect the contents of the encoded asset metadata payload.
|
|
///
|
|
/// Wrapped asset metadata can also be updated with a new asset metadata VAA.
|
|
/// By calling `update_attestation`, Token Bridge verifies that the specific
|
|
/// coin type is registered and agrees with the encoded asset metadata's
|
|
/// canonical token info. `ForeignInfo` and the coin's metadata will be updated
|
|
/// based on the encoded asset metadata payload.
|
|
///
|
|
/// See `state` and `wrapped_asset` modules for more details.
|
|
///
|
|
/// References:
|
|
/// https://examples.sui.io/basics/one-time-witness.html
|
|
module token_bridge::create_wrapped {
|
|
use std::ascii::{Self};
|
|
use std::option::{Self};
|
|
use std::type_name::{Self};
|
|
use sui::coin::{Self, TreasuryCap, CoinMetadata};
|
|
use sui::object::{Self, UID};
|
|
use sui::package::{UpgradeCap};
|
|
use sui::transfer::{Self};
|
|
use sui::tx_context::{TxContext};
|
|
|
|
use token_bridge::asset_meta::{Self};
|
|
use token_bridge::normalized_amount::{max_decimals};
|
|
use token_bridge::state::{Self, State};
|
|
use token_bridge::token_registry::{Self};
|
|
use token_bridge::vaa::{Self, TokenBridgeMessage};
|
|
use token_bridge::wrapped_asset::{Self};
|
|
|
|
#[test_only]
|
|
use token_bridge::version_control::{Self, V__0_2_0 as V__CURRENT};
|
|
|
|
/// Failed one-time witness verification.
|
|
const E_BAD_WITNESS: u64 = 0;
|
|
/// Coin witness does not equal "COIN".
|
|
const E_INVALID_COIN_MODULE_NAME: u64 = 1;
|
|
/// Decimals value exceeds `MAX_DECIMALS` from `normalized_amount`.
|
|
const E_DECIMALS_EXCEED_WRAPPED_MAX: u64 = 2;
|
|
|
|
/// A.K.A. "coin".
|
|
const COIN_MODULE_NAME: vector<u8> = b"coin";
|
|
|
|
/// Container holding new coin type's `TreasuryCap` and encoded asset metadata
|
|
/// VAA, which are required to complete this asset's registration.
|
|
struct WrappedAssetSetup<phantom CoinType, phantom Version> has key, store {
|
|
id: UID,
|
|
treasury_cap: TreasuryCap<CoinType>
|
|
}
|
|
|
|
/// This method is executed within the `init` method of an untrusted module,
|
|
/// which defines a one-time witness (OTW) type (`CoinType`). OTW is
|
|
/// required to ensure that only one `TreasuryCap` exists for `CoinType`. This
|
|
/// is similar to how a `TreasuryCap` is created in `coin::create_currency`.
|
|
///
|
|
/// Because this method is stateless (i.e. no dependency on Token Bridge's
|
|
/// `State` object), the contract defers VAA verification to
|
|
/// `complete_registration` after this method has been executed.
|
|
public fun prepare_registration<CoinType: drop, Version>(
|
|
witness: CoinType,
|
|
decimals: u8,
|
|
ctx: &mut TxContext
|
|
): WrappedAssetSetup<CoinType, Version> {
|
|
let setup = prepare_registration_internal(witness, decimals, ctx);
|
|
|
|
// Also make sure that this witness module name is literally "coin".
|
|
let module_name = type_name::get_module(&type_name::get<CoinType>());
|
|
assert!(
|
|
ascii::into_bytes(module_name) == COIN_MODULE_NAME,
|
|
E_INVALID_COIN_MODULE_NAME
|
|
);
|
|
|
|
setup
|
|
}
|
|
|
|
/// This function performs the bulk of `prepare_registration`, except
|
|
/// checking the module name. This separation is useful for testing.
|
|
fun prepare_registration_internal<CoinType: drop, Version>(
|
|
witness: CoinType,
|
|
decimals: u8,
|
|
ctx: &mut TxContext
|
|
): WrappedAssetSetup<CoinType, Version> {
|
|
// Make sure there's only one instance of the type `CoinType`. This
|
|
// resembles the same check for `coin::create_currency`.
|
|
// Technically this check is redundant as it's performed by
|
|
// `coin::create_currency` below, but it doesn't hurt.
|
|
assert!(sui::types::is_one_time_witness(&witness), E_BAD_WITNESS);
|
|
|
|
// Ensure that the decimals passed into this method do not exceed max
|
|
// decimals (see `normalized_amount` module).
|
|
assert!(decimals <= max_decimals(), E_DECIMALS_EXCEED_WRAPPED_MAX);
|
|
|
|
// We initialise the currency with empty metadata. Later on, in the
|
|
// `complete_registration` call, when `CoinType` gets associated with a
|
|
// VAA, we update these fields.
|
|
let no_symbol = b"";
|
|
let no_name = b"";
|
|
let no_description = b"";
|
|
let no_icon_url = option::none();
|
|
|
|
let (treasury_cap, coin_meta) =
|
|
coin::create_currency(
|
|
witness,
|
|
decimals,
|
|
no_symbol,
|
|
no_name,
|
|
no_description,
|
|
no_icon_url,
|
|
ctx
|
|
);
|
|
|
|
// The CoinMetadata is turned into a shared object so that other
|
|
// functions (and wallets) can easily grab references to it. This is
|
|
// safe to do, as the metadata setters require a `TreasuryCap` for the
|
|
// coin too, which is held by the token bridge.
|
|
transfer::public_share_object(coin_meta);
|
|
|
|
// Create `WrappedAssetSetup` object and transfer to transaction sender.
|
|
// The owner of this object will call `complete_registration` to destroy
|
|
// it.
|
|
WrappedAssetSetup {
|
|
id: object::new(ctx),
|
|
treasury_cap
|
|
}
|
|
}
|
|
|
|
/// After executing `prepare_registration`, owner of `WrappedAssetSetup`
|
|
/// executes this method to complete this wrapped asset's registration.
|
|
///
|
|
/// This method destroys `WrappedAssetSetup`, unpacking the `TreasuryCap` and
|
|
/// encoded asset metadata VAA. The deserialized asset metadata VAA is used
|
|
/// to update the associated `CoinMetadata`.
|
|
public fun complete_registration<CoinType: drop, Version>(
|
|
token_bridge_state: &mut State,
|
|
coin_meta: &mut CoinMetadata<CoinType>,
|
|
setup: WrappedAssetSetup<CoinType, Version>,
|
|
coin_upgrade_cap: UpgradeCap,
|
|
msg: TokenBridgeMessage
|
|
) {
|
|
// This capability ensures that the current build version is used. This
|
|
// call performs an additional check of whether `WrappedAssetSetup` was
|
|
// created using the current package.
|
|
let latest_only =
|
|
state::assert_latest_only_specified<Version>(token_bridge_state);
|
|
|
|
let WrappedAssetSetup {
|
|
id,
|
|
treasury_cap
|
|
} = setup;
|
|
|
|
// Finally destroy the object.
|
|
object::delete(id);
|
|
|
|
// Deserialize to `AssetMeta`.
|
|
let token_meta = asset_meta::deserialize(vaa::take_payload(msg));
|
|
|
|
// `register_wrapped_asset` uses `token_registry::add_new_wrapped`,
|
|
// which will check whether the asset has already been registered and if
|
|
// the token chain ID is not Sui's.
|
|
//
|
|
// If both of these conditions are met, `register_wrapped_asset` will
|
|
// succeed and the new wrapped coin will be registered.
|
|
token_registry::add_new_wrapped(
|
|
state::borrow_mut_token_registry(&latest_only, token_bridge_state),
|
|
token_meta,
|
|
coin_meta,
|
|
treasury_cap,
|
|
coin_upgrade_cap
|
|
);
|
|
}
|
|
|
|
/// For registered wrapped assets, we can update `ForeignInfo` for a
|
|
/// given `CoinType` with a new asset meta VAA emitted from another network.
|
|
public fun update_attestation<CoinType>(
|
|
token_bridge_state: &mut State,
|
|
coin_meta: &mut CoinMetadata<CoinType>,
|
|
msg: TokenBridgeMessage
|
|
) {
|
|
// This capability ensures that the current build version is used.
|
|
let latest_only = state::assert_latest_only(token_bridge_state);
|
|
|
|
// Deserialize to `AssetMeta`.
|
|
let token_meta = asset_meta::deserialize(vaa::take_payload(msg));
|
|
|
|
// This asset must exist in the registry.
|
|
let registry =
|
|
state::borrow_mut_token_registry(&latest_only, token_bridge_state);
|
|
token_registry::assert_has<CoinType>(registry);
|
|
|
|
// Now update wrapped.
|
|
wrapped_asset::update_metadata(
|
|
token_registry::borrow_mut_wrapped<CoinType>(registry),
|
|
coin_meta,
|
|
token_meta
|
|
);
|
|
}
|
|
|
|
public fun incomplete_metadata<CoinType>(
|
|
coin_meta: &CoinMetadata<CoinType>
|
|
): bool {
|
|
use std::string::{bytes};
|
|
use std::vector::{is_empty};
|
|
|
|
(
|
|
is_empty(ascii::as_bytes(&coin::get_symbol(coin_meta))) &&
|
|
is_empty(bytes(&coin::get_name(coin_meta))) &&
|
|
is_empty(bytes(&coin::get_description(coin_meta))) &&
|
|
std::option::is_none(&coin::get_icon_url(coin_meta))
|
|
)
|
|
}
|
|
|
|
#[test_only]
|
|
public fun new_setup_test_only<CoinType: drop, Version: drop>(
|
|
_version: Version,
|
|
witness: CoinType,
|
|
decimals: u8,
|
|
ctx: &mut TxContext
|
|
): (WrappedAssetSetup<CoinType, Version>, UpgradeCap) {
|
|
let setup =
|
|
prepare_registration_internal(
|
|
witness,
|
|
decimals,
|
|
ctx
|
|
);
|
|
|
|
let upgrade_cap =
|
|
sui::package::test_publish(
|
|
object::id_from_address(@token_bridge),
|
|
ctx
|
|
);
|
|
|
|
(setup, upgrade_cap)
|
|
}
|
|
|
|
#[test_only]
|
|
public fun new_setup_current<CoinType: drop>(
|
|
witness: CoinType,
|
|
decimals: u8,
|
|
ctx: &mut TxContext
|
|
): (WrappedAssetSetup<CoinType, V__CURRENT>, UpgradeCap) {
|
|
new_setup_test_only(
|
|
version_control::current_version_test_only(),
|
|
witness,
|
|
decimals,
|
|
ctx
|
|
)
|
|
}
|
|
|
|
#[test_only]
|
|
public fun take_treasury_cap<CoinType>(
|
|
setup: WrappedAssetSetup<CoinType, V__CURRENT>
|
|
): TreasuryCap<CoinType> {
|
|
let WrappedAssetSetup {
|
|
id,
|
|
treasury_cap
|
|
} = setup;
|
|
object::delete(id);
|
|
|
|
treasury_cap
|
|
}
|
|
}
|
|
|
|
#[test_only]
|
|
module token_bridge::create_wrapped_tests {
|
|
use sui::coin::{Self};
|
|
use sui::test_scenario::{Self};
|
|
use sui::test_utils::{Self};
|
|
use sui::tx_context::{Self};
|
|
use wormhole::wormhole_scenario::{parse_and_verify_vaa};
|
|
|
|
use token_bridge::asset_meta::{Self};
|
|
use token_bridge::coin_wrapped_12::{Self};
|
|
use token_bridge::coin_wrapped_7::{Self};
|
|
use token_bridge::create_wrapped::{Self};
|
|
use token_bridge::state::{Self};
|
|
use token_bridge::string_utils::{Self};
|
|
use token_bridge::token_bridge_scenario::{
|
|
register_dummy_emitter,
|
|
return_state,
|
|
set_up_wormhole_and_token_bridge,
|
|
take_state,
|
|
two_people
|
|
};
|
|
use token_bridge::token_registry::{Self};
|
|
use token_bridge::vaa::{Self};
|
|
use token_bridge::version_control::{V__0_2_0 as V__CURRENT};
|
|
use token_bridge::wrapped_asset::{Self};
|
|
|
|
struct NOT_A_WITNESS has drop {}
|
|
|
|
struct CREATE_WRAPPED_TESTS has drop {}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = create_wrapped::E_BAD_WITNESS)]
|
|
fun test_cannot_prepare_registration_bad_witness() {
|
|
let ctx = &mut tx_context::dummy();
|
|
|
|
// You shall not pass!
|
|
let wrapped_asset_setup =
|
|
create_wrapped::prepare_registration<NOT_A_WITNESS, V__CURRENT>(
|
|
NOT_A_WITNESS {},
|
|
3,
|
|
ctx
|
|
);
|
|
|
|
// Clean up.
|
|
test_utils::destroy(wrapped_asset_setup);
|
|
|
|
abort 42
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = create_wrapped::E_INVALID_COIN_MODULE_NAME)]
|
|
fun test_cannot_prepare_registration_invalid_coin_module_name() {
|
|
let ctx = &mut tx_context::dummy();
|
|
|
|
// You shall not pass!
|
|
let wrapped_asset_setup =
|
|
create_wrapped::prepare_registration<
|
|
CREATE_WRAPPED_TESTS,
|
|
V__CURRENT
|
|
>(
|
|
CREATE_WRAPPED_TESTS {},
|
|
3,
|
|
ctx
|
|
);
|
|
|
|
// Clean up.
|
|
test_utils::destroy(wrapped_asset_setup);
|
|
|
|
abort 42
|
|
}
|
|
|
|
#[test]
|
|
fun test_complete_and_update_attestation() {
|
|
let (caller, coin_deployer) = two_people();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
// Set up contracts.
|
|
let wormhole_fee = 350;
|
|
set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
|
|
|
|
// Register foreign emitter on chain ID == 2.
|
|
let expected_source_chain = 2;
|
|
register_dummy_emitter(scenario, expected_source_chain);
|
|
|
|
// Ignore effects. Make sure `coin_deployer` receives
|
|
// `WrappedAssetSetup`.
|
|
test_scenario::next_tx(scenario, coin_deployer);
|
|
|
|
// Publish coin.
|
|
let (
|
|
wrapped_asset_setup,
|
|
upgrade_cap
|
|
) =
|
|
create_wrapped::new_setup_current(
|
|
CREATE_WRAPPED_TESTS {},
|
|
8,
|
|
test_scenario::ctx(scenario)
|
|
);
|
|
|
|
let token_bridge_state = take_state(scenario);
|
|
|
|
let verified_vaa =
|
|
parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa());
|
|
let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
|
|
|
|
let coin_meta = test_scenario::take_shared(scenario);
|
|
|
|
create_wrapped::complete_registration(
|
|
&mut token_bridge_state,
|
|
&mut coin_meta,
|
|
wrapped_asset_setup,
|
|
upgrade_cap,
|
|
msg
|
|
);
|
|
|
|
let (
|
|
token_address,
|
|
token_chain,
|
|
native_decimals,
|
|
symbol,
|
|
name
|
|
) = asset_meta::unpack_test_only(coin_wrapped_12::token_meta());
|
|
|
|
// Check registry.
|
|
{
|
|
let registry = state::borrow_token_registry(&token_bridge_state);
|
|
let verified =
|
|
token_registry::verified_asset<CREATE_WRAPPED_TESTS>(registry);
|
|
assert!(token_registry::is_wrapped(&verified), 0);
|
|
|
|
let asset =
|
|
token_registry::borrow_wrapped<CREATE_WRAPPED_TESTS>(registry);
|
|
assert!(wrapped_asset::total_supply(asset) == 0, 0);
|
|
|
|
// Decimals are capped for this wrapped asset.
|
|
assert!(coin::get_decimals(&coin_meta) == 8, 0);
|
|
|
|
// Check metadata against asset metadata.
|
|
let info = wrapped_asset::info(asset);
|
|
assert!(wrapped_asset::token_chain(info) == token_chain, 0);
|
|
assert!(wrapped_asset::token_address(info) == token_address, 0);
|
|
assert!(
|
|
wrapped_asset::native_decimals(info) == native_decimals,
|
|
0
|
|
);
|
|
assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&symbol), 0);
|
|
assert!(coin::get_name(&coin_meta) == name, 0);
|
|
};
|
|
|
|
|
|
// Now update metadata.
|
|
let verified_vaa =
|
|
parse_and_verify_vaa(
|
|
scenario,
|
|
coin_wrapped_12::encoded_updated_vaa()
|
|
);
|
|
let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
|
|
create_wrapped::update_attestation<CREATE_WRAPPED_TESTS>(
|
|
&mut token_bridge_state,
|
|
&mut coin_meta,
|
|
msg
|
|
);
|
|
|
|
// Check updated name and symbol.
|
|
let (
|
|
_,
|
|
_,
|
|
_,
|
|
new_symbol,
|
|
new_name
|
|
) = asset_meta::unpack_test_only(coin_wrapped_12::updated_token_meta());
|
|
|
|
assert!(symbol != new_symbol, 0);
|
|
|
|
assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&new_symbol), 0);
|
|
|
|
assert!(name != new_name, 0);
|
|
assert!(coin::get_name(&coin_meta) == new_name, 0);
|
|
|
|
test_scenario::return_shared(coin_meta);
|
|
|
|
// Clean up.
|
|
return_state(token_bridge_state);
|
|
|
|
// Done.
|
|
test_scenario::end(my_scenario);
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)]
|
|
fun test_cannot_update_attestation_wrong_canonical_info() {
|
|
let (caller, coin_deployer) = two_people();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
// Set up contracts.
|
|
let wormhole_fee = 350;
|
|
set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
|
|
|
|
// Register foreign emitter on chain ID == 2.
|
|
let expected_source_chain = 2;
|
|
register_dummy_emitter(scenario, expected_source_chain);
|
|
|
|
// Ignore effects. Make sure `coin_deployer` receives
|
|
// `WrappedAssetSetup`.
|
|
test_scenario::next_tx(scenario, coin_deployer);
|
|
|
|
// Publish coin.
|
|
let (
|
|
wrapped_asset_setup,
|
|
upgrade_cap
|
|
) =
|
|
create_wrapped::new_setup_current(
|
|
CREATE_WRAPPED_TESTS {},
|
|
8,
|
|
test_scenario::ctx(scenario)
|
|
);
|
|
|
|
let token_bridge_state = take_state(scenario);
|
|
|
|
let verified_vaa =
|
|
parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa());
|
|
let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
|
|
|
|
let coin_meta = test_scenario::take_shared(scenario);
|
|
|
|
create_wrapped::complete_registration(
|
|
&mut token_bridge_state,
|
|
&mut coin_meta,
|
|
wrapped_asset_setup,
|
|
upgrade_cap,
|
|
msg
|
|
);
|
|
// This VAA is for COIN_WRAPPED_7 metadata, which disagrees with
|
|
// COIN_WRAPPED_12.
|
|
let invalid_asset_meta_vaa = coin_wrapped_7::encoded_vaa();
|
|
|
|
let verified_vaa =
|
|
parse_and_verify_vaa(scenario, invalid_asset_meta_vaa);
|
|
let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
|
|
// You shall not pass!
|
|
create_wrapped::update_attestation<CREATE_WRAPPED_TESTS>(
|
|
&mut token_bridge_state,
|
|
&mut coin_meta,
|
|
msg
|
|
);
|
|
|
|
abort 42
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = state::E_VERSION_MISMATCH)]
|
|
fun test_cannot_complete_registration_version_mismatch() {
|
|
let (caller, coin_deployer) = two_people();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
// Set up contracts.
|
|
let wormhole_fee = 350;
|
|
set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
|
|
|
|
// Register foreign emitter on chain ID == 2.
|
|
let expected_source_chain = 2;
|
|
register_dummy_emitter(scenario, expected_source_chain);
|
|
|
|
// Ignore effects. Make sure `coin_deployer` receives
|
|
// `WrappedAssetSetup`.
|
|
test_scenario::next_tx(scenario, coin_deployer);
|
|
|
|
// Publish coin.
|
|
let (
|
|
wrapped_asset_setup,
|
|
upgrade_cap
|
|
) =
|
|
create_wrapped::new_setup_test_only(
|
|
token_bridge::version_control::dummy(),
|
|
CREATE_WRAPPED_TESTS {},
|
|
8,
|
|
test_scenario::ctx(scenario)
|
|
);
|
|
|
|
let token_bridge_state = take_state(scenario);
|
|
|
|
let verified_vaa =
|
|
parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa());
|
|
let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
|
|
|
|
let coin_meta = test_scenario::take_shared(scenario);
|
|
|
|
create_wrapped::complete_registration(
|
|
&mut token_bridge_state,
|
|
&mut coin_meta,
|
|
wrapped_asset_setup,
|
|
upgrade_cap,
|
|
msg
|
|
);
|
|
|
|
abort 42
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
|
|
fun test_cannot_complete_registration_outdated_version() {
|
|
let (caller, coin_deployer) = two_people();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
// Set up contracts.
|
|
let wormhole_fee = 350;
|
|
set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
|
|
|
|
// Register foreign emitter on chain ID == 2.
|
|
let expected_source_chain = 2;
|
|
register_dummy_emitter(scenario, expected_source_chain);
|
|
|
|
// Ignore effects. Make sure `coin_deployer` receives
|
|
// `WrappedAssetSetup`.
|
|
test_scenario::next_tx(scenario, coin_deployer);
|
|
|
|
// Publish coin.
|
|
let (
|
|
wrapped_asset_setup,
|
|
upgrade_cap
|
|
) =
|
|
create_wrapped::new_setup_current(
|
|
CREATE_WRAPPED_TESTS {},
|
|
8,
|
|
test_scenario::ctx(scenario)
|
|
);
|
|
|
|
let token_bridge_state = take_state(scenario);
|
|
|
|
let verified_vaa =
|
|
parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa());
|
|
let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
|
|
|
|
let coin_meta = test_scenario::take_shared(scenario);
|
|
|
|
// Conveniently roll version back.
|
|
state::reverse_migrate_version(&mut token_bridge_state);
|
|
|
|
// Simulate executing with an outdated build by upticking the minimum
|
|
// required version for `publish_message` to something greater than
|
|
// this build.
|
|
state::migrate_version_test_only(
|
|
&mut token_bridge_state,
|
|
token_bridge::version_control::previous_version_test_only(),
|
|
token_bridge::version_control::next_version()
|
|
);
|
|
|
|
// You shall not pass!
|
|
create_wrapped::complete_registration(
|
|
&mut token_bridge_state,
|
|
&mut coin_meta,
|
|
wrapped_asset_setup,
|
|
upgrade_cap,
|
|
msg
|
|
);
|
|
|
|
abort 42
|
|
}
|
|
}
|