807 lines
26 KiB
Plaintext
807 lines
26 KiB
Plaintext
|
// SPDX-License-Identifier: Apache 2
|
||
|
|
||
|
/// This module implements two custom types relating to Token Bridge wrapped
|
||
|
/// assets. These assets have been attested from foreign networks, whose
|
||
|
/// metadata is stored in `ForeignInfo`. The Token Bridge contract is the
|
||
|
/// only authority that can mint and burn these assets via `Supply`.
|
||
|
///
|
||
|
/// See `create_wrapped` and 'token_registry' modules for more details.
|
||
|
module token_bridge::wrapped_asset {
|
||
|
use std::string::{String};
|
||
|
use sui::balance::{Self, Balance};
|
||
|
use sui::coin::{Self, TreasuryCap, CoinMetadata};
|
||
|
use sui::package::{Self, UpgradeCap};
|
||
|
use wormhole::external_address::{ExternalAddress};
|
||
|
use wormhole::state::{chain_id};
|
||
|
|
||
|
use token_bridge::string_utils;
|
||
|
use token_bridge::asset_meta::{Self, AssetMeta};
|
||
|
use token_bridge::normalized_amount::{cap_decimals};
|
||
|
|
||
|
friend token_bridge::complete_transfer;
|
||
|
friend token_bridge::create_wrapped;
|
||
|
friend token_bridge::token_registry;
|
||
|
friend token_bridge::transfer_tokens;
|
||
|
|
||
|
/// Token chain ID matching Sui's are not allowed.
|
||
|
const E_SUI_CHAIN: u64 = 0;
|
||
|
/// Canonical token info does match `AssetMeta` payload.
|
||
|
const E_ASSET_META_MISMATCH: u64 = 1;
|
||
|
/// Coin decimals don't match the VAA.
|
||
|
const E_DECIMALS_MISMATCH: u64 = 2;
|
||
|
|
||
|
/// Container storing foreign asset info.
|
||
|
struct ForeignInfo<phantom C> has store {
|
||
|
token_chain: u16,
|
||
|
token_address: ExternalAddress,
|
||
|
native_decimals: u8,
|
||
|
symbol: String
|
||
|
}
|
||
|
|
||
|
/// Container managing `ForeignInfo` and `TreasuryCap` for a wrapped asset
|
||
|
/// coin type.
|
||
|
struct WrappedAsset<phantom C> has store {
|
||
|
info: ForeignInfo<C>,
|
||
|
treasury_cap: TreasuryCap<C>,
|
||
|
decimals: u8,
|
||
|
upgrade_cap: UpgradeCap
|
||
|
}
|
||
|
|
||
|
/// Create new `WrappedAsset`.
|
||
|
///
|
||
|
/// See `token_registry` module for more info.
|
||
|
public(friend) fun new<C>(
|
||
|
token_meta: AssetMeta,
|
||
|
coin_meta: &mut CoinMetadata<C>,
|
||
|
treasury_cap: TreasuryCap<C>,
|
||
|
upgrade_cap: UpgradeCap
|
||
|
): WrappedAsset<C> {
|
||
|
// Verify that the upgrade cap is from the same package as coin type.
|
||
|
// This cap should not have been modified prior to creating this asset
|
||
|
// (i.e. should have the default upgrade policy and build version == 1).
|
||
|
wormhole::package_utils::assert_package_upgrade_cap<C>(
|
||
|
&upgrade_cap,
|
||
|
package::compatible_policy(),
|
||
|
1
|
||
|
);
|
||
|
|
||
|
let (
|
||
|
token_address,
|
||
|
token_chain,
|
||
|
native_decimals,
|
||
|
symbol,
|
||
|
name
|
||
|
) = asset_meta::unpack(token_meta);
|
||
|
|
||
|
// Protect against adding `AssetMeta` which has Sui's chain ID.
|
||
|
assert!(token_chain != chain_id(), E_SUI_CHAIN);
|
||
|
|
||
|
// Set metadata.
|
||
|
coin::update_name(&mut treasury_cap, coin_meta, name);
|
||
|
coin::update_symbol(&mut treasury_cap, coin_meta, string_utils::to_ascii(&symbol));
|
||
|
|
||
|
let decimals = cap_decimals(native_decimals);
|
||
|
|
||
|
// Ensure that the `C` type has the right number of decimals. This is
|
||
|
// the only field in the coinmeta that cannot be changed after the fact,
|
||
|
// so we expect to receive one that already has the correct decimals
|
||
|
// set.
|
||
|
assert!(decimals == coin::get_decimals(coin_meta), E_DECIMALS_MISMATCH);
|
||
|
|
||
|
let info =
|
||
|
ForeignInfo {
|
||
|
token_address,
|
||
|
token_chain,
|
||
|
native_decimals,
|
||
|
symbol
|
||
|
};
|
||
|
|
||
|
WrappedAsset {
|
||
|
info,
|
||
|
treasury_cap,
|
||
|
decimals,
|
||
|
upgrade_cap
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[test_only]
|
||
|
public fun new_test_only<C>(
|
||
|
token_meta: AssetMeta,
|
||
|
coin_meta: &mut CoinMetadata<C>,
|
||
|
treasury_cap: TreasuryCap<C>,
|
||
|
upgrade_cap: UpgradeCap
|
||
|
): WrappedAsset<C> {
|
||
|
new(token_meta, coin_meta, treasury_cap, upgrade_cap)
|
||
|
}
|
||
|
|
||
|
/// Update existing `ForeignInfo` using new `AssetMeta`.
|
||
|
///
|
||
|
/// See `token_registry` module for more info.
|
||
|
public(friend) fun update_metadata<C>(
|
||
|
self: &mut WrappedAsset<C>,
|
||
|
coin_meta: &mut CoinMetadata<C>,
|
||
|
token_meta: AssetMeta
|
||
|
) {
|
||
|
// NOTE: We ignore `native_decimals` because we do not enforce that
|
||
|
// an asset's decimals on a foreign network needs to stay the same.
|
||
|
let (
|
||
|
token_address,
|
||
|
token_chain,
|
||
|
_native_decimals,
|
||
|
symbol,
|
||
|
name
|
||
|
) = asset_meta::unpack(token_meta);
|
||
|
|
||
|
// Verify canonical token info. Also check that the native decimals
|
||
|
// have not changed (because changing this info is not desirable, as
|
||
|
// this change means the supply changed on its native network).
|
||
|
//
|
||
|
// NOTE: This implicitly verifies that `token_chain` is not Sui's
|
||
|
// because this was checked already when the asset was first added.
|
||
|
let (expected_chain, expected_address) = canonical_info<C>(self);
|
||
|
assert!(
|
||
|
(
|
||
|
token_chain == expected_chain &&
|
||
|
token_address == expected_address
|
||
|
),
|
||
|
E_ASSET_META_MISMATCH
|
||
|
);
|
||
|
|
||
|
// Finally only update the name and symbol.
|
||
|
self.info.symbol = symbol;
|
||
|
coin::update_name(&mut self.treasury_cap, coin_meta, name);
|
||
|
coin::update_symbol(&mut self.treasury_cap, coin_meta, string_utils::to_ascii(&symbol));
|
||
|
}
|
||
|
|
||
|
#[test_only]
|
||
|
public fun update_metadata_test_only<C>(
|
||
|
self: &mut WrappedAsset<C>,
|
||
|
coin_meta: &mut CoinMetadata<C>,
|
||
|
token_meta: AssetMeta
|
||
|
) {
|
||
|
update_metadata(self, coin_meta, token_meta)
|
||
|
}
|
||
|
|
||
|
/// Retrieve immutable reference to `ForeignInfo`.
|
||
|
public fun info<C>(self: &WrappedAsset<C>): &ForeignInfo<C> {
|
||
|
&self.info
|
||
|
}
|
||
|
|
||
|
/// Retrieve canonical token chain ID from `ForeignInfo`.
|
||
|
public fun token_chain<C>(info: &ForeignInfo<C>): u16 {
|
||
|
info.token_chain
|
||
|
}
|
||
|
|
||
|
/// Retrieve canonical token address from `ForeignInfo`.
|
||
|
public fun token_address<C>(info: &ForeignInfo<C>): ExternalAddress {
|
||
|
info.token_address
|
||
|
}
|
||
|
|
||
|
/// Retrieve decimal amount from `ForeignInfo`.
|
||
|
///
|
||
|
/// NOTE: This is for informational purposes. This decimal amount is not
|
||
|
/// used for any calculations.
|
||
|
public fun native_decimals<C>(info: &ForeignInfo<C>): u8 {
|
||
|
info.native_decimals
|
||
|
}
|
||
|
|
||
|
/// Retrieve asset's symbol (UTF-8) from `ForeignMetadata`.
|
||
|
///
|
||
|
/// NOTE: This value can be updated.
|
||
|
public fun symbol<C>(info: &ForeignInfo<C>): String {
|
||
|
info.symbol
|
||
|
}
|
||
|
|
||
|
/// Retrieve total minted supply.
|
||
|
public fun total_supply<C>(self: &WrappedAsset<C>): u64 {
|
||
|
coin::total_supply(&self.treasury_cap)
|
||
|
}
|
||
|
|
||
|
/// Retrieve decimals for this wrapped asset. For any asset whose native
|
||
|
/// decimals is greater than the cap (8), this will be 8.
|
||
|
///
|
||
|
/// See `normalized_amount` module for more info.
|
||
|
public fun decimals<C>(self: &WrappedAsset<C>): u8 {
|
||
|
self.decimals
|
||
|
}
|
||
|
|
||
|
/// Retrieve canonical token chain ID and token address.
|
||
|
public fun canonical_info<C>(
|
||
|
self: &WrappedAsset<C>
|
||
|
): (u16, ExternalAddress) {
|
||
|
(self.info.token_chain, self.info.token_address)
|
||
|
}
|
||
|
|
||
|
/// Burn a given `Balance`. `Balance` originates from an outbound token
|
||
|
/// transfer for a wrapped asset.
|
||
|
///
|
||
|
/// See `transfer_tokens` module for more info.
|
||
|
public(friend) fun burn<C>(
|
||
|
self: &mut WrappedAsset<C>,
|
||
|
burned: Balance<C>
|
||
|
): u64 {
|
||
|
balance::decrease_supply(coin::supply_mut(&mut self.treasury_cap), burned)
|
||
|
}
|
||
|
|
||
|
#[test_only]
|
||
|
public fun burn_test_only<C>(
|
||
|
self: &mut WrappedAsset<C>,
|
||
|
burned: Balance<C>
|
||
|
): u64 {
|
||
|
burn(self, burned)
|
||
|
}
|
||
|
|
||
|
/// Mint a given amount. This amount is determined by an inbound token
|
||
|
/// transfer payload for a wrapped asset.
|
||
|
///
|
||
|
/// See `complete_transfer` module for more info.
|
||
|
public(friend) fun mint<C>(
|
||
|
self: &mut WrappedAsset<C>,
|
||
|
amount: u64
|
||
|
): Balance<C> {
|
||
|
coin::mint_balance(&mut self.treasury_cap, amount)
|
||
|
}
|
||
|
|
||
|
#[test_only]
|
||
|
public fun mint_test_only<C>(
|
||
|
self: &mut WrappedAsset<C>,
|
||
|
amount: u64
|
||
|
): Balance<C> {
|
||
|
mint(self, amount)
|
||
|
}
|
||
|
|
||
|
#[test_only]
|
||
|
public fun destroy<C>(asset: WrappedAsset<C>) {
|
||
|
let WrappedAsset {
|
||
|
info,
|
||
|
treasury_cap,
|
||
|
decimals: _,
|
||
|
upgrade_cap
|
||
|
} = asset;
|
||
|
sui::test_utils::destroy(treasury_cap);
|
||
|
|
||
|
let ForeignInfo {
|
||
|
token_chain: _,
|
||
|
token_address: _,
|
||
|
native_decimals: _,
|
||
|
symbol: _
|
||
|
} = info;
|
||
|
|
||
|
sui::package::make_immutable(upgrade_cap);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[test_only]
|
||
|
module token_bridge::wrapped_asset_tests {
|
||
|
use std::string::{Self};
|
||
|
use sui::balance::{Self};
|
||
|
use sui::coin::{Self, CoinMetadata};
|
||
|
use sui::object::{Self};
|
||
|
use sui::package::{Self};
|
||
|
use sui::test_scenario::{Self};
|
||
|
use wormhole::external_address::{Self};
|
||
|
use wormhole::state::{chain_id};
|
||
|
|
||
|
use token_bridge::asset_meta::{Self};
|
||
|
use token_bridge::string_utils;
|
||
|
use token_bridge::coin_native_10::{COIN_NATIVE_10, Self};
|
||
|
use token_bridge::coin_wrapped_12::{COIN_WRAPPED_12, Self};
|
||
|
use token_bridge::coin_wrapped_7::{COIN_WRAPPED_7, Self};
|
||
|
use token_bridge::token_bridge_scenario::{person};
|
||
|
use token_bridge::wrapped_asset::{Self};
|
||
|
|
||
|
#[test]
|
||
|
fun test_wrapped_asset_7() {
|
||
|
let caller = person();
|
||
|
let my_scenario = test_scenario::begin(caller);
|
||
|
let scenario = &mut my_scenario;
|
||
|
|
||
|
let parsed_meta = coin_wrapped_7::token_meta();
|
||
|
let expected_token_chain = asset_meta::token_chain(&parsed_meta);
|
||
|
let expected_token_address = asset_meta::token_address(&parsed_meta);
|
||
|
let expected_native_decimals =
|
||
|
asset_meta::native_decimals(&parsed_meta);
|
||
|
let expected_symbol = asset_meta::symbol(&parsed_meta);
|
||
|
let expected_name = asset_meta::name(&parsed_meta);
|
||
|
|
||
|
// Publish coin.
|
||
|
let treasury_cap =
|
||
|
coin_wrapped_7::init_and_take_treasury_cap(
|
||
|
scenario,
|
||
|
caller
|
||
|
);
|
||
|
|
||
|
// Ignore effects.
|
||
|
test_scenario::next_tx(scenario, caller);
|
||
|
|
||
|
// Upgrade cap belonging to coin type.
|
||
|
let upgrade_cap =
|
||
|
package::test_publish(
|
||
|
object::id_from_address(@token_bridge),
|
||
|
test_scenario::ctx(scenario)
|
||
|
);
|
||
|
|
||
|
let coin_meta: CoinMetadata<COIN_WRAPPED_7> = test_scenario::take_shared(scenario);
|
||
|
|
||
|
// Make new.
|
||
|
let asset =
|
||
|
wrapped_asset::new_test_only(
|
||
|
parsed_meta,
|
||
|
&mut coin_meta,
|
||
|
treasury_cap,
|
||
|
upgrade_cap
|
||
|
);
|
||
|
|
||
|
// Verify members.
|
||
|
let info = wrapped_asset::info(&asset);
|
||
|
assert!(
|
||
|
wrapped_asset::token_chain(info) == expected_token_chain,
|
||
|
0
|
||
|
);
|
||
|
assert!(
|
||
|
wrapped_asset::token_address(info) == expected_token_address,
|
||
|
0
|
||
|
);
|
||
|
assert!(
|
||
|
wrapped_asset::native_decimals(info) == expected_native_decimals,
|
||
|
0
|
||
|
);
|
||
|
assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&expected_symbol), 0);
|
||
|
assert!(coin::get_name(&coin_meta) == expected_name, 0);
|
||
|
assert!(wrapped_asset::total_supply(&asset) == 0, 0);
|
||
|
|
||
|
let (token_chain, token_address) =
|
||
|
wrapped_asset::canonical_info(&asset);
|
||
|
assert!(token_chain == expected_token_chain, 0);
|
||
|
assert!(token_address == expected_token_address, 0);
|
||
|
|
||
|
// Decimals are read from `CoinMetadata`, but in this case will agree
|
||
|
// with the value encoded in the VAA.
|
||
|
assert!(wrapped_asset::decimals(&asset) == expected_native_decimals, 0);
|
||
|
assert!(coin::get_decimals(&coin_meta) == expected_native_decimals, 0);
|
||
|
|
||
|
// Change name and symbol for update.
|
||
|
let new_symbol = std::ascii::into_bytes(coin::get_symbol(&coin_meta));
|
||
|
|
||
|
std::vector::append(&mut new_symbol, b"??? and profit");
|
||
|
assert!(new_symbol != *string::bytes(&expected_symbol), 0);
|
||
|
|
||
|
let new_name = coin::get_name(&coin_meta);
|
||
|
string::append(&mut new_name, string::utf8(b"??? and profit"));
|
||
|
assert!(new_name != expected_name, 0);
|
||
|
|
||
|
let updated_meta =
|
||
|
asset_meta::new(
|
||
|
expected_token_address,
|
||
|
expected_token_chain,
|
||
|
expected_native_decimals,
|
||
|
string::utf8(new_symbol),
|
||
|
new_name
|
||
|
);
|
||
|
|
||
|
// Update metadata now.
|
||
|
wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, updated_meta);
|
||
|
|
||
|
assert!(coin::get_symbol(&coin_meta) == std::ascii::string(new_symbol), 0);
|
||
|
assert!(coin::get_name(&coin_meta) == new_name, 0);
|
||
|
|
||
|
// Try to mint.
|
||
|
let mint_amount = 420;
|
||
|
let collected = balance::zero();
|
||
|
let (i, n) = (0, 8);
|
||
|
while (i < n) {
|
||
|
let minted =
|
||
|
wrapped_asset::mint_test_only(&mut asset, mint_amount);
|
||
|
assert!(balance::value(&minted) == mint_amount, 0);
|
||
|
balance::join(&mut collected, minted);
|
||
|
i = i + 1;
|
||
|
};
|
||
|
assert!(balance::value(&collected) == n * mint_amount, 0);
|
||
|
assert!(
|
||
|
wrapped_asset::total_supply(&asset) == balance::value(&collected),
|
||
|
0
|
||
|
);
|
||
|
|
||
|
// Now try to burn.
|
||
|
let burn_amount = 69;
|
||
|
let i = 0;
|
||
|
while (i < n) {
|
||
|
let burned = balance::split(&mut collected, burn_amount);
|
||
|
let check_amount =
|
||
|
wrapped_asset::burn_test_only(&mut asset, burned);
|
||
|
assert!(check_amount == burn_amount, 0);
|
||
|
i = i + 1;
|
||
|
};
|
||
|
let remaining = n * mint_amount - n * burn_amount;
|
||
|
assert!(wrapped_asset::total_supply(&asset) == remaining, 0);
|
||
|
assert!(balance::value(&collected) == remaining, 0);
|
||
|
|
||
|
test_scenario::return_shared(coin_meta);
|
||
|
|
||
|
// Clean up.
|
||
|
balance::destroy_for_testing(collected);
|
||
|
wrapped_asset::destroy(asset);
|
||
|
|
||
|
// Done.
|
||
|
test_scenario::end(my_scenario);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fun test_wrapped_asset_12() {
|
||
|
let caller = person();
|
||
|
let my_scenario = test_scenario::begin(caller);
|
||
|
let scenario = &mut my_scenario;
|
||
|
|
||
|
let parsed_meta = coin_wrapped_12::token_meta();
|
||
|
let expected_token_chain = asset_meta::token_chain(&parsed_meta);
|
||
|
let expected_token_address = asset_meta::token_address(&parsed_meta);
|
||
|
let expected_native_decimals =
|
||
|
asset_meta::native_decimals(&parsed_meta);
|
||
|
let expected_symbol = asset_meta::symbol(&parsed_meta);
|
||
|
let expected_name = asset_meta::name(&parsed_meta);
|
||
|
|
||
|
// Publish coin.
|
||
|
let treasury_cap =
|
||
|
coin_wrapped_12::init_and_take_treasury_cap(
|
||
|
scenario,
|
||
|
caller
|
||
|
);
|
||
|
|
||
|
// Ignore effects.
|
||
|
test_scenario::next_tx(scenario, caller);
|
||
|
|
||
|
// Upgrade cap belonging to coin type.
|
||
|
let upgrade_cap =
|
||
|
package::test_publish(
|
||
|
object::id_from_address(@token_bridge),
|
||
|
test_scenario::ctx(scenario)
|
||
|
);
|
||
|
|
||
|
let coin_meta: CoinMetadata<COIN_WRAPPED_12> = test_scenario::take_shared(scenario);
|
||
|
|
||
|
// Make new.
|
||
|
let asset =
|
||
|
wrapped_asset::new_test_only(
|
||
|
parsed_meta,
|
||
|
&mut coin_meta,
|
||
|
treasury_cap,
|
||
|
upgrade_cap
|
||
|
);
|
||
|
|
||
|
// Verify members.
|
||
|
let info = wrapped_asset::info(&asset);
|
||
|
assert!(
|
||
|
wrapped_asset::token_chain(info) == expected_token_chain,
|
||
|
0
|
||
|
);
|
||
|
assert!(
|
||
|
wrapped_asset::token_address(info) == expected_token_address,
|
||
|
0
|
||
|
);
|
||
|
assert!(
|
||
|
wrapped_asset::native_decimals(info) == expected_native_decimals,
|
||
|
0
|
||
|
);
|
||
|
assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&expected_symbol), 0);
|
||
|
assert!(coin::get_name(&coin_meta) == expected_name, 0);
|
||
|
assert!(wrapped_asset::total_supply(&asset) == 0, 0);
|
||
|
|
||
|
let (token_chain, token_address) =
|
||
|
wrapped_asset::canonical_info(&asset);
|
||
|
assert!(token_chain == expected_token_chain, 0);
|
||
|
assert!(token_address == expected_token_address, 0);
|
||
|
|
||
|
// Decimals are read from `CoinMetadata`, but in this case will not
|
||
|
// agree with the value encoded in the VAA.
|
||
|
assert!(wrapped_asset::decimals(&asset) == 8, 0);
|
||
|
assert!(
|
||
|
coin::get_decimals(&coin_meta) == wrapped_asset::decimals(&asset),
|
||
|
0
|
||
|
);
|
||
|
assert!(wrapped_asset::decimals(&asset) != expected_native_decimals, 0);
|
||
|
|
||
|
// Change name and symbol for update.
|
||
|
let new_symbol = std::ascii::into_bytes(coin::get_symbol(&coin_meta));
|
||
|
|
||
|
std::vector::append(&mut new_symbol, b"??? and profit");
|
||
|
assert!(new_symbol != *string::bytes(&expected_symbol), 0);
|
||
|
|
||
|
let new_name = coin::get_name(&coin_meta);
|
||
|
string::append(&mut new_name, string::utf8(b"??? and profit"));
|
||
|
assert!(new_name != expected_name, 0);
|
||
|
|
||
|
let updated_meta =
|
||
|
asset_meta::new(
|
||
|
expected_token_address,
|
||
|
expected_token_chain,
|
||
|
expected_native_decimals,
|
||
|
string::utf8(new_symbol),
|
||
|
new_name
|
||
|
);
|
||
|
|
||
|
// Update metadata now.
|
||
|
wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, updated_meta);
|
||
|
|
||
|
assert!(coin::get_symbol(&coin_meta) == std::ascii::string(new_symbol), 0);
|
||
|
assert!(coin::get_name(&coin_meta) == new_name, 0);
|
||
|
|
||
|
// Try to mint.
|
||
|
let mint_amount = 420;
|
||
|
let collected = balance::zero();
|
||
|
let (i, n) = (0, 8);
|
||
|
while (i < n) {
|
||
|
let minted =
|
||
|
wrapped_asset::mint_test_only(&mut asset, mint_amount);
|
||
|
assert!(balance::value(&minted) == mint_amount, 0);
|
||
|
balance::join(&mut collected, minted);
|
||
|
i = i + 1;
|
||
|
};
|
||
|
assert!(balance::value(&collected) == n * mint_amount, 0);
|
||
|
assert!(
|
||
|
wrapped_asset::total_supply(&asset) == balance::value(&collected),
|
||
|
0
|
||
|
);
|
||
|
|
||
|
// Now try to burn.
|
||
|
let burn_amount = 69;
|
||
|
let i = 0;
|
||
|
while (i < n) {
|
||
|
let burned = balance::split(&mut collected, burn_amount);
|
||
|
let check_amount =
|
||
|
wrapped_asset::burn_test_only(&mut asset, burned);
|
||
|
assert!(check_amount == burn_amount, 0);
|
||
|
i = i + 1;
|
||
|
};
|
||
|
let remaining = n * mint_amount - n * burn_amount;
|
||
|
assert!(wrapped_asset::total_supply(&asset) == remaining, 0);
|
||
|
assert!(balance::value(&collected) == remaining, 0);
|
||
|
|
||
|
// Clean up.
|
||
|
balance::destroy_for_testing(collected);
|
||
|
wrapped_asset::destroy(asset);
|
||
|
test_scenario::return_shared(coin_meta);
|
||
|
|
||
|
// Done.
|
||
|
test_scenario::end(my_scenario);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
#[expected_failure(abort_code = wrapped_asset::E_SUI_CHAIN)]
|
||
|
// In this negative test case, we attempt to register a native coin as a
|
||
|
// wrapped coin.
|
||
|
fun test_cannot_new_sui_chain() {
|
||
|
let caller = person();
|
||
|
let my_scenario = test_scenario::begin(caller);
|
||
|
let scenario = &mut my_scenario;
|
||
|
|
||
|
// Initialize new coin type.
|
||
|
coin_native_10::init_test_only(test_scenario::ctx(scenario));
|
||
|
|
||
|
// Ignore effects.
|
||
|
test_scenario::next_tx(scenario, caller);
|
||
|
|
||
|
// Sui's chain ID is not allowed.
|
||
|
let invalid_meta =
|
||
|
asset_meta::new(
|
||
|
external_address::default(),
|
||
|
chain_id(),
|
||
|
10,
|
||
|
string::utf8(b""),
|
||
|
string::utf8(b"")
|
||
|
);
|
||
|
|
||
|
// Upgrade cap belonging to coin type.
|
||
|
let upgrade_cap =
|
||
|
package::test_publish(
|
||
|
object::id_from_address(@token_bridge),
|
||
|
test_scenario::ctx(scenario)
|
||
|
);
|
||
|
|
||
|
let treasury_cap = test_scenario::take_shared<coin::TreasuryCap<COIN_NATIVE_10>>(scenario);
|
||
|
let coin_meta = test_scenario::take_shared<CoinMetadata<COIN_NATIVE_10>>(scenario);
|
||
|
|
||
|
// You shall not pass!
|
||
|
let asset =
|
||
|
wrapped_asset::new_test_only(
|
||
|
invalid_meta,
|
||
|
&mut coin_meta,
|
||
|
treasury_cap,
|
||
|
upgrade_cap
|
||
|
);
|
||
|
|
||
|
// Clean up.
|
||
|
wrapped_asset::destroy(asset);
|
||
|
|
||
|
abort 42
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
#[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)]
|
||
|
/// In this negative test case, we attempt to update with a mismatching
|
||
|
/// chain.
|
||
|
fun test_cannot_update_metadata_asset_meta_mismatch_token_address() {
|
||
|
let caller = person();
|
||
|
let my_scenario = test_scenario::begin(caller);
|
||
|
let scenario = &mut my_scenario;
|
||
|
|
||
|
let parsed_meta = coin_wrapped_12::token_meta();
|
||
|
let expected_token_chain = asset_meta::token_chain(&parsed_meta);
|
||
|
let expected_token_address = asset_meta::token_address(&parsed_meta);
|
||
|
let expected_native_decimals =
|
||
|
asset_meta::native_decimals(&parsed_meta);
|
||
|
|
||
|
// Publish coin.
|
||
|
let treasury_cap =
|
||
|
coin_wrapped_12::init_and_take_treasury_cap(
|
||
|
scenario,
|
||
|
caller
|
||
|
);
|
||
|
|
||
|
// Ignore effects.
|
||
|
test_scenario::next_tx(scenario, caller);
|
||
|
|
||
|
// Upgrade cap belonging to coin type.
|
||
|
let upgrade_cap =
|
||
|
package::test_publish(
|
||
|
object::id_from_address(@token_bridge),
|
||
|
test_scenario::ctx(scenario)
|
||
|
);
|
||
|
|
||
|
let coin_meta = test_scenario::take_shared(scenario);
|
||
|
|
||
|
// Make new.
|
||
|
let asset =
|
||
|
wrapped_asset::new_test_only(
|
||
|
parsed_meta,
|
||
|
&mut coin_meta,
|
||
|
treasury_cap,
|
||
|
upgrade_cap
|
||
|
);
|
||
|
|
||
|
let invalid_meta =
|
||
|
asset_meta::new(
|
||
|
external_address::default(),
|
||
|
expected_token_chain,
|
||
|
expected_native_decimals,
|
||
|
string::utf8(b""),
|
||
|
string::utf8(b""),
|
||
|
);
|
||
|
assert!(
|
||
|
asset_meta::token_address(&invalid_meta) != expected_token_address,
|
||
|
0
|
||
|
);
|
||
|
assert!(
|
||
|
asset_meta::token_chain(&invalid_meta) == expected_token_chain,
|
||
|
0
|
||
|
);
|
||
|
assert!(
|
||
|
asset_meta::native_decimals(&invalid_meta) == expected_native_decimals,
|
||
|
0
|
||
|
);
|
||
|
|
||
|
// You shall not pass!
|
||
|
wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, invalid_meta);
|
||
|
|
||
|
// Clean up.
|
||
|
wrapped_asset::destroy(asset);
|
||
|
|
||
|
abort 42
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
#[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)]
|
||
|
/// In this negative test case, we attempt to update with a mismatching
|
||
|
/// chain.
|
||
|
fun test_cannot_update_metadata_asset_meta_mismatch_token_chain() {
|
||
|
let caller = person();
|
||
|
let my_scenario = test_scenario::begin(caller);
|
||
|
let scenario = &mut my_scenario;
|
||
|
|
||
|
let parsed_meta = coin_wrapped_12::token_meta();
|
||
|
let expected_token_chain = asset_meta::token_chain(&parsed_meta);
|
||
|
let expected_token_address = asset_meta::token_address(&parsed_meta);
|
||
|
let expected_native_decimals =
|
||
|
asset_meta::native_decimals(&parsed_meta);
|
||
|
|
||
|
// Publish coin.
|
||
|
let treasury_cap =
|
||
|
coin_wrapped_12::init_and_take_treasury_cap(
|
||
|
scenario,
|
||
|
caller
|
||
|
);
|
||
|
|
||
|
// Ignore effects.
|
||
|
test_scenario::next_tx(scenario, caller);
|
||
|
|
||
|
// Upgrade cap belonging to coin type.
|
||
|
let upgrade_cap =
|
||
|
package::test_publish(
|
||
|
object::id_from_address(@token_bridge),
|
||
|
test_scenario::ctx(scenario)
|
||
|
);
|
||
|
|
||
|
let coin_meta = test_scenario::take_shared(scenario);
|
||
|
|
||
|
// Make new.
|
||
|
let asset =
|
||
|
wrapped_asset::new_test_only(
|
||
|
parsed_meta,
|
||
|
&mut coin_meta,
|
||
|
treasury_cap,
|
||
|
upgrade_cap
|
||
|
);
|
||
|
|
||
|
let invalid_meta =
|
||
|
asset_meta::new(
|
||
|
expected_token_address,
|
||
|
chain_id(),
|
||
|
expected_native_decimals,
|
||
|
string::utf8(b""),
|
||
|
string::utf8(b""),
|
||
|
);
|
||
|
assert!(
|
||
|
asset_meta::token_address(&invalid_meta) == expected_token_address,
|
||
|
0
|
||
|
);
|
||
|
assert!(
|
||
|
asset_meta::token_chain(&invalid_meta) != expected_token_chain,
|
||
|
0
|
||
|
);
|
||
|
assert!(
|
||
|
asset_meta::native_decimals(&invalid_meta) == expected_native_decimals,
|
||
|
0
|
||
|
);
|
||
|
|
||
|
// You shall not pass!
|
||
|
wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, invalid_meta);
|
||
|
|
||
|
// Clean up.
|
||
|
wrapped_asset::destroy(asset);
|
||
|
|
||
|
abort 42
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
#[expected_failure(
|
||
|
abort_code = wormhole::package_utils::E_INVALID_UPGRADE_CAP
|
||
|
)]
|
||
|
fun test_cannot_new_upgrade_cap_mismatch() {
|
||
|
let caller = person();
|
||
|
let my_scenario = test_scenario::begin(caller);
|
||
|
let scenario = &mut my_scenario;
|
||
|
|
||
|
// Publish coin.
|
||
|
let treasury_cap =
|
||
|
coin_wrapped_12::init_and_take_treasury_cap(
|
||
|
scenario,
|
||
|
caller
|
||
|
);
|
||
|
|
||
|
// Ignore effects.
|
||
|
test_scenario::next_tx(scenario, caller);
|
||
|
|
||
|
// Upgrade cap belonging to coin type.
|
||
|
let upgrade_cap =
|
||
|
package::test_publish(
|
||
|
object::id_from_address(@0xbadc0de),
|
||
|
test_scenario::ctx(scenario)
|
||
|
);
|
||
|
|
||
|
let coin_meta = test_scenario::take_shared(scenario);
|
||
|
|
||
|
// You shall not pass!
|
||
|
let asset =
|
||
|
wrapped_asset::new_test_only(
|
||
|
coin_wrapped_12::token_meta(),
|
||
|
&mut coin_meta,
|
||
|
treasury_cap,
|
||
|
upgrade_cap
|
||
|
);
|
||
|
|
||
|
// Clean up.
|
||
|
wrapped_asset::destroy(asset);
|
||
|
|
||
|
abort 42
|
||
|
}
|
||
|
}
|