785 lines
26 KiB
Plaintext
785 lines
26 KiB
Plaintext
// SPDX-License-Identifier: Apache 2
|
|
|
|
/// This module implements a custom type that keeps track of both native and
|
|
/// wrapped assets via dynamic fields. These dynamic fields are keyed off using
|
|
/// coin types. This registry lives in `State`.
|
|
///
|
|
/// See `state` module for more details.
|
|
module token_bridge::token_registry {
|
|
use std::ascii::{String};
|
|
use std::type_name::{Self};
|
|
use sui::coin::{TreasuryCap, CoinMetadata};
|
|
use sui::dynamic_field::{Self};
|
|
use sui::object::{Self, UID};
|
|
use sui::package::{UpgradeCap};
|
|
use sui::table::{Self, Table};
|
|
use sui::tx_context::{TxContext};
|
|
use wormhole::external_address::{Self, ExternalAddress};
|
|
|
|
use token_bridge::asset_meta::{Self, AssetMeta};
|
|
use token_bridge::native_asset::{Self, NativeAsset};
|
|
use token_bridge::wrapped_asset::{Self, WrappedAsset};
|
|
|
|
friend token_bridge::attest_token;
|
|
friend token_bridge::complete_transfer;
|
|
friend token_bridge::create_wrapped;
|
|
friend token_bridge::state;
|
|
friend token_bridge::transfer_tokens;
|
|
|
|
/// Asset is not registered yet.
|
|
const E_UNREGISTERED: u64 = 0;
|
|
/// Cannot register wrapped asset with same canonical token info.
|
|
const E_ALREADY_WRAPPED: u64 = 1;
|
|
|
|
/// This container is used to store native and wrapped assets of coin type
|
|
/// as dynamic fields under its `UID`. It also uses a mechanism to generate
|
|
/// arbitrary token addresses for native assets.
|
|
struct TokenRegistry has key, store {
|
|
id: UID,
|
|
num_wrapped: u64,
|
|
num_native: u64,
|
|
coin_types: Table<CoinTypeKey, String>
|
|
}
|
|
|
|
/// Container to provide convenient checking of whether an asset is wrapped
|
|
/// or native. `VerifiedAsset` can only be created either by passing in a
|
|
/// resource with `CoinType` or by verifying input token info against the
|
|
/// canonical info that exists in `TokenRegistry`.
|
|
///
|
|
/// NOTE: This container can be dropped after it was created.
|
|
struct VerifiedAsset<phantom CoinType> has drop {
|
|
is_wrapped: bool,
|
|
chain: u16,
|
|
addr: ExternalAddress,
|
|
coin_decimals: u8
|
|
}
|
|
|
|
/// Wrapper of coin type to act as dynamic field key.
|
|
struct Key<phantom CoinType> has copy, drop, store {}
|
|
|
|
/// This struct is not used for anything within the contract. It exists
|
|
/// purely for someone with an RPC query to be able to fetch the type name
|
|
/// of coin type as a string via `TokenRegistry`.
|
|
struct CoinTypeKey has drop, copy, store {
|
|
chain: u16,
|
|
addr: vector<u8>
|
|
}
|
|
|
|
/// Create new `TokenRegistry`.
|
|
///
|
|
/// See `setup` module for more info.
|
|
public(friend) fun new(ctx: &mut TxContext): TokenRegistry {
|
|
TokenRegistry {
|
|
id: object::new(ctx),
|
|
num_wrapped: 0,
|
|
num_native: 0,
|
|
coin_types: table::new(ctx)
|
|
}
|
|
}
|
|
|
|
#[test_only]
|
|
public fun new_test_only(ctx: &mut TxContext): TokenRegistry {
|
|
new(ctx)
|
|
}
|
|
|
|
/// Determine whether a particular coin type is registered.
|
|
public fun has<CoinType>(self: &TokenRegistry): bool {
|
|
dynamic_field::exists_(&self.id, Key<CoinType> {})
|
|
}
|
|
|
|
public fun assert_has<CoinType>(self: &TokenRegistry) {
|
|
assert!(has<CoinType>(self), E_UNREGISTERED);
|
|
}
|
|
|
|
public fun verified_asset<CoinType>(
|
|
self: &TokenRegistry
|
|
): VerifiedAsset<CoinType> {
|
|
// We check specifically whether `CoinType` is associated with a dynamic
|
|
// field for `WrappedAsset`. This boolean will be used as the underlying
|
|
// value for `VerifiedAsset`.
|
|
let is_wrapped =
|
|
dynamic_field::exists_with_type<Key<CoinType>, WrappedAsset<CoinType>>(
|
|
&self.id,
|
|
Key {}
|
|
);
|
|
if (is_wrapped) {
|
|
let asset = borrow_wrapped<CoinType>(self);
|
|
let (chain, addr) = wrapped_asset::canonical_info(asset);
|
|
let coin_decimals = wrapped_asset::decimals(asset);
|
|
|
|
VerifiedAsset { is_wrapped, chain, addr, coin_decimals }
|
|
} else {
|
|
let asset = borrow_native<CoinType>(self);
|
|
let (chain, addr) = native_asset::canonical_info(asset);
|
|
let coin_decimals = native_asset::decimals(asset);
|
|
|
|
VerifiedAsset { is_wrapped, chain, addr, coin_decimals }
|
|
}
|
|
}
|
|
|
|
/// Determine whether a given `CoinType` is a wrapped asset.
|
|
public fun is_wrapped<CoinType>(verified: &VerifiedAsset<CoinType>): bool {
|
|
verified.is_wrapped
|
|
}
|
|
|
|
/// Retrieve canonical token chain ID from `VerifiedAsset`.
|
|
public fun token_chain<CoinType>(
|
|
verified: &VerifiedAsset<CoinType>
|
|
): u16 {
|
|
verified.chain
|
|
}
|
|
|
|
/// Retrieve canonical token address from `VerifiedAsset`.
|
|
public fun token_address<CoinType>(
|
|
verified: &VerifiedAsset<CoinType>
|
|
): ExternalAddress {
|
|
verified.addr
|
|
}
|
|
|
|
/// Retrieve decimals for a `VerifiedAsset`.
|
|
public fun coin_decimals<CoinType>(
|
|
verified: &VerifiedAsset<CoinType>
|
|
): u8 {
|
|
verified.coin_decimals
|
|
}
|
|
|
|
/// Add a new wrapped asset to the registry and return the canonical token
|
|
/// address.
|
|
///
|
|
/// See `state` module for more info.
|
|
public(friend) fun add_new_wrapped<CoinType>(
|
|
self: &mut TokenRegistry,
|
|
token_meta: AssetMeta,
|
|
coin_meta: &mut CoinMetadata<CoinType>,
|
|
treasury_cap: TreasuryCap<CoinType>,
|
|
upgrade_cap: UpgradeCap
|
|
): ExternalAddress {
|
|
// Grab canonical token info.
|
|
let token_chain = asset_meta::token_chain(&token_meta);
|
|
let token_addr = asset_meta::token_address(&token_meta);
|
|
|
|
let coin_types = &mut self.coin_types;
|
|
let key =
|
|
CoinTypeKey {
|
|
chain: token_chain,
|
|
addr: external_address::to_bytes(token_addr)
|
|
};
|
|
// We need to make sure that the canonical token info has not been
|
|
// created for another coin type. This can happen if asset metadata
|
|
// is attested again from a foreign chain and another coin type is
|
|
// published using its VAA.
|
|
assert!(!table::contains(coin_types, key), E_ALREADY_WRAPPED);
|
|
|
|
// Now add the coin type.
|
|
table::add(
|
|
coin_types,
|
|
key,
|
|
type_name::into_string(type_name::get<CoinType>())
|
|
);
|
|
|
|
// NOTE: We do not assert that the coin type has not already been
|
|
// registered using !has<CoinType>(self) because `wrapped_asset::new`
|
|
// consumes `TreasuryCap`. This `TreasuryCap` is only created once for a particuar
|
|
// coin type via `create_wrapped::prepare_registration`. Because the
|
|
// `TreasuryCap` is globally unique and can only be created once, there is no
|
|
// risk that `add_new_wrapped` can be called again on the same coin
|
|
// type.
|
|
let asset =
|
|
wrapped_asset::new(
|
|
token_meta,
|
|
coin_meta,
|
|
treasury_cap,
|
|
upgrade_cap
|
|
);
|
|
dynamic_field::add(&mut self.id, Key<CoinType> {}, asset);
|
|
self.num_wrapped = self.num_wrapped + 1;
|
|
|
|
token_addr
|
|
}
|
|
|
|
#[test_only]
|
|
public fun add_new_wrapped_test_only<CoinType>(
|
|
self: &mut TokenRegistry,
|
|
token_meta: AssetMeta,
|
|
coin_meta: &mut CoinMetadata<CoinType>,
|
|
treasury_cap: TreasuryCap<CoinType>,
|
|
ctx: &mut TxContext
|
|
): ExternalAddress {
|
|
add_new_wrapped(
|
|
self,
|
|
token_meta,
|
|
coin_meta,
|
|
treasury_cap,
|
|
sui::package::test_publish(
|
|
object::id_from_address(@token_bridge),
|
|
ctx
|
|
)
|
|
)
|
|
}
|
|
|
|
/// Add a new native asset to the registry and return the canonical token
|
|
/// address.
|
|
///
|
|
/// NOTE: This method does not verify if `CoinType` is already in the
|
|
/// registry because `attest_token` already takes care of this check. If
|
|
/// This method were to be called on an already-registered asset, this
|
|
/// will throw with an error from `sui::dynamic_field` reflectina duplicate
|
|
/// field.
|
|
///
|
|
/// See `attest_token` module for more info.
|
|
public(friend) fun add_new_native<CoinType>(
|
|
self: &mut TokenRegistry,
|
|
metadata: &CoinMetadata<CoinType>,
|
|
): ExternalAddress {
|
|
// Create new native asset.
|
|
let asset = native_asset::new(metadata);
|
|
let token_addr = native_asset::token_address(&asset);
|
|
|
|
// Add to registry.
|
|
dynamic_field::add(&mut self.id, Key<CoinType> {}, asset);
|
|
self.num_native = self.num_native + 1;
|
|
|
|
// Now add the coin type.
|
|
table::add(
|
|
&mut self.coin_types,
|
|
CoinTypeKey {
|
|
chain: wormhole::state::chain_id(),
|
|
addr: external_address::to_bytes(token_addr)
|
|
},
|
|
type_name::into_string(type_name::get<CoinType>())
|
|
);
|
|
|
|
// Return the token address.
|
|
token_addr
|
|
}
|
|
|
|
#[test_only]
|
|
public fun add_new_native_test_only<CoinType>(
|
|
self: &mut TokenRegistry,
|
|
metadata: &CoinMetadata<CoinType>
|
|
): ExternalAddress {
|
|
add_new_native(self, metadata)
|
|
}
|
|
|
|
public fun borrow_wrapped<CoinType>(
|
|
self: &TokenRegistry
|
|
): &WrappedAsset<CoinType> {
|
|
dynamic_field::borrow(&self.id, Key<CoinType> {})
|
|
}
|
|
|
|
public(friend) fun borrow_mut_wrapped<CoinType>(
|
|
self: &mut TokenRegistry
|
|
): &mut WrappedAsset<CoinType> {
|
|
dynamic_field::borrow_mut(&mut self.id, Key<CoinType> {})
|
|
}
|
|
|
|
#[test_only]
|
|
public fun borrow_mut_wrapped_test_only<CoinType>(
|
|
self: &mut TokenRegistry
|
|
): &mut WrappedAsset<CoinType> {
|
|
borrow_mut_wrapped(self)
|
|
}
|
|
|
|
public fun borrow_native<CoinType>(
|
|
self: &TokenRegistry
|
|
): &NativeAsset<CoinType> {
|
|
dynamic_field::borrow(&self.id, Key<CoinType> {})
|
|
}
|
|
|
|
public(friend) fun borrow_mut_native<CoinType>(
|
|
self: &mut TokenRegistry
|
|
): &mut NativeAsset<CoinType> {
|
|
dynamic_field::borrow_mut(&mut self.id, Key<CoinType> {})
|
|
}
|
|
|
|
#[test_only]
|
|
public fun borrow_mut_native_test_only<CoinType>(
|
|
self: &mut TokenRegistry
|
|
): &mut NativeAsset<CoinType> {
|
|
borrow_mut_native(self)
|
|
}
|
|
|
|
#[test_only]
|
|
public fun num_native(self: &TokenRegistry): u64 {
|
|
self.num_native
|
|
}
|
|
|
|
#[test_only]
|
|
public fun num_wrapped(self: &TokenRegistry): u64 {
|
|
self.num_wrapped
|
|
}
|
|
|
|
#[test_only]
|
|
public fun destroy(registry: TokenRegistry) {
|
|
let TokenRegistry {
|
|
id,
|
|
num_wrapped: _,
|
|
num_native: _,
|
|
coin_types
|
|
} = registry;
|
|
object::delete(id);
|
|
table::drop(coin_types);
|
|
}
|
|
|
|
#[test_only]
|
|
public fun coin_type_for(
|
|
self: &TokenRegistry,
|
|
chain: u16,
|
|
addr: vector<u8>
|
|
): String {
|
|
*table::borrow(&self.coin_types, CoinTypeKey { chain, addr })
|
|
}
|
|
}
|
|
|
|
// In this test, we exercise the various functionalities of TokenRegistry,
|
|
// including registering native and wrapped coins via add_new_native, and
|
|
// add_new_wrapped, minting/burning/depositing/withdrawing said tokens, and also
|
|
// storing metadata about the tokens.
|
|
#[test_only]
|
|
module token_bridge::token_registry_tests {
|
|
use std::type_name::{Self};
|
|
use sui::balance::{Self};
|
|
use sui::coin::{CoinMetadata};
|
|
use sui::test_scenario::{Self};
|
|
use wormhole::external_address::{Self};
|
|
use wormhole::state::{chain_id};
|
|
|
|
use token_bridge::asset_meta::{Self};
|
|
use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
|
|
use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7};
|
|
use token_bridge::native_asset::{Self};
|
|
use token_bridge::token_registry::{Self};
|
|
use token_bridge::token_bridge_scenario::{person};
|
|
use token_bridge::wrapped_asset::{Self};
|
|
|
|
struct SCAM_COIN has drop {}
|
|
|
|
#[test]
|
|
fun test_registered_tokens_native() {
|
|
let caller = person();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
// Initialize new coin.
|
|
coin_native_10::init_test_only(test_scenario::ctx(scenario));
|
|
|
|
// Ignore effects.
|
|
test_scenario::next_tx(scenario, caller);
|
|
|
|
// Initialize new token registry.
|
|
let registry =
|
|
token_registry::new_test_only(test_scenario::ctx(scenario));
|
|
|
|
// Check initial state.
|
|
assert!(token_registry::num_native(®istry) == 0, 0);
|
|
assert!(token_registry::num_wrapped(®istry) == 0, 0);
|
|
|
|
// Register native asset.
|
|
let coin_meta = coin_native_10::take_metadata(scenario);
|
|
let token_address =
|
|
token_registry::add_new_native_test_only(
|
|
&mut registry,
|
|
&coin_meta,
|
|
);
|
|
let expected_token_address =
|
|
native_asset::canonical_address(&coin_meta);
|
|
assert!(token_address == expected_token_address, 0);
|
|
|
|
// mint some native coins, then deposit them into the token registry
|
|
let deposit_amount = 69;
|
|
let (i, n) = (0, 8);
|
|
while (i < n) {
|
|
native_asset::deposit_test_only(
|
|
token_registry::borrow_mut_native_test_only(
|
|
&mut registry,
|
|
),
|
|
balance::create_for_testing<COIN_NATIVE_10>(
|
|
deposit_amount
|
|
)
|
|
);
|
|
i = i + 1;
|
|
};
|
|
let total_deposited = n * deposit_amount;
|
|
{
|
|
let asset =
|
|
token_registry::borrow_native<COIN_NATIVE_10>(®istry);
|
|
assert!(native_asset::custody(asset) == total_deposited, 0);
|
|
};
|
|
|
|
// Withdraw and check balances.
|
|
let withdraw_amount = 420;
|
|
let withdrawn =
|
|
native_asset::withdraw_test_only(
|
|
token_registry::borrow_mut_native_test_only<COIN_NATIVE_10>(
|
|
&mut registry
|
|
),
|
|
withdraw_amount
|
|
);
|
|
assert!(balance::value(&withdrawn) == withdraw_amount, 0);
|
|
balance::destroy_for_testing(withdrawn);
|
|
|
|
let expected_remaining = total_deposited - withdraw_amount;
|
|
{
|
|
let asset =
|
|
token_registry::borrow_native<COIN_NATIVE_10>(®istry);
|
|
assert!(native_asset::custody(asset) == expected_remaining, 0);
|
|
};
|
|
|
|
// Verify registry values.
|
|
assert!(token_registry::num_native(®istry) == 1, 0);
|
|
assert!(token_registry::num_wrapped(®istry) == 0, 0);
|
|
|
|
let verified = token_registry::verified_asset<COIN_NATIVE_10>(®istry);
|
|
assert!(!token_registry::is_wrapped(&verified), 0);
|
|
assert!(token_registry::coin_decimals(&verified) == 10, 0);
|
|
assert!(token_registry::token_chain(&verified) == chain_id(), 0);
|
|
assert!(
|
|
token_registry::token_address(&verified) == expected_token_address,
|
|
0
|
|
);
|
|
|
|
// Check coin type.
|
|
let coin_type =
|
|
token_registry::coin_type_for(
|
|
®istry,
|
|
token_registry::token_chain(&verified),
|
|
external_address::to_bytes(
|
|
token_registry::token_address(&verified)
|
|
)
|
|
);
|
|
assert!(
|
|
coin_type == type_name::into_string(type_name::get<COIN_NATIVE_10>()),
|
|
0
|
|
);
|
|
|
|
// Clean up.
|
|
token_registry::destroy(registry);
|
|
coin_native_10::return_metadata(coin_meta);
|
|
|
|
// Done.
|
|
test_scenario::end(my_scenario);
|
|
}
|
|
|
|
#[test]
|
|
fun test_registered_tokens_wrapped() {
|
|
let caller = person();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
// Initialize new coin.
|
|
let treasury_cap =
|
|
coin_wrapped_7::init_and_take_treasury_cap(
|
|
scenario,
|
|
caller
|
|
);
|
|
|
|
// Ignore effects.
|
|
test_scenario::next_tx(scenario, caller);
|
|
|
|
// Initialize new token registry.
|
|
let registry =
|
|
token_registry::new_test_only(test_scenario::ctx(scenario));
|
|
|
|
// Check initial state.
|
|
assert!(token_registry::num_wrapped(®istry) == 0, 0);
|
|
assert!(token_registry::num_native(®istry) == 0, 0);
|
|
|
|
let coin_meta = test_scenario::take_shared<CoinMetadata<COIN_WRAPPED_7>>(scenario);
|
|
|
|
// Register wrapped asset.
|
|
let wrapped_token_meta = coin_wrapped_7::token_meta();
|
|
token_registry::add_new_wrapped_test_only(
|
|
&mut registry,
|
|
wrapped_token_meta,
|
|
&mut coin_meta,
|
|
treasury_cap,
|
|
test_scenario::ctx(scenario)
|
|
);
|
|
|
|
test_scenario::return_shared(coin_meta);
|
|
|
|
// Mint wrapped coin via `WrappedAsset` several times.
|
|
let mint_amount = 420;
|
|
let total_minted = balance::zero();
|
|
let (i, n) = (0, 8);
|
|
while (i < n) {
|
|
let minted =
|
|
wrapped_asset::mint_test_only(
|
|
token_registry::borrow_mut_wrapped_test_only<COIN_WRAPPED_7>(
|
|
&mut registry,
|
|
),
|
|
mint_amount
|
|
);
|
|
assert!(balance::value(&minted) == mint_amount, 0);
|
|
balance::join(&mut total_minted, minted);
|
|
i = i + 1;
|
|
};
|
|
|
|
let total_supply =
|
|
wrapped_asset::total_supply(
|
|
token_registry::borrow_wrapped<COIN_WRAPPED_7>(
|
|
®istry
|
|
)
|
|
);
|
|
assert!(total_supply == balance::value(&total_minted), 0);
|
|
|
|
// withdraw, check value, and re-deposit native coins into registry
|
|
let burn_amount = 69;
|
|
let burned =
|
|
wrapped_asset::burn_test_only(
|
|
token_registry::borrow_mut_wrapped_test_only(&mut registry),
|
|
balance::split(&mut total_minted, burn_amount)
|
|
);
|
|
assert!(burned == burn_amount, 0);
|
|
|
|
let expected_remaining = total_supply - burn_amount;
|
|
let remaining =
|
|
wrapped_asset::total_supply(
|
|
token_registry::borrow_wrapped<COIN_WRAPPED_7>(
|
|
®istry
|
|
)
|
|
);
|
|
assert!(remaining == expected_remaining, 0);
|
|
balance::destroy_for_testing(total_minted);
|
|
|
|
// Verify registry values.
|
|
assert!(token_registry::num_wrapped(®istry) == 1, 0);
|
|
assert!(token_registry::num_native(®istry) == 0, 0);
|
|
|
|
|
|
let verified = token_registry::verified_asset<COIN_WRAPPED_7>(®istry);
|
|
assert!(token_registry::is_wrapped(&verified), 0);
|
|
assert!(token_registry::coin_decimals(&verified) == 7, 0);
|
|
|
|
let wrapped_token_meta = coin_wrapped_7::token_meta();
|
|
assert!(
|
|
token_registry::token_chain(&verified) == asset_meta::token_chain(&wrapped_token_meta),
|
|
0
|
|
);
|
|
assert!(
|
|
token_registry::token_address(&verified) == asset_meta::token_address(&wrapped_token_meta),
|
|
0
|
|
);
|
|
|
|
// Check coin type.
|
|
let coin_type =
|
|
token_registry::coin_type_for(
|
|
®istry,
|
|
token_registry::token_chain(&verified),
|
|
external_address::to_bytes(
|
|
token_registry::token_address(&verified)
|
|
)
|
|
);
|
|
assert!(
|
|
coin_type == type_name::into_string(type_name::get<COIN_WRAPPED_7>()),
|
|
0
|
|
);
|
|
|
|
|
|
// Clean up.
|
|
token_registry::destroy(registry);
|
|
asset_meta::destroy(wrapped_token_meta);
|
|
|
|
// Done.
|
|
test_scenario::end(my_scenario);
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = sui::dynamic_field::EFieldAlreadyExists)]
|
|
/// In this negative test case, we try to register a native token twice.
|
|
fun test_cannot_add_new_native_again() {
|
|
let caller = person();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
// Initialize new coin.
|
|
coin_native_10::init_test_only(test_scenario::ctx(scenario));
|
|
|
|
// Ignore effects.
|
|
test_scenario::next_tx(scenario, caller);
|
|
|
|
// Initialize new token registry.
|
|
let registry =
|
|
token_registry::new_test_only(test_scenario::ctx(scenario));
|
|
|
|
let coin_meta = coin_native_10::take_metadata(scenario);
|
|
|
|
// Add new native asset.
|
|
token_registry::add_new_native_test_only(
|
|
&mut registry,
|
|
&coin_meta
|
|
);
|
|
|
|
// You shall not pass!
|
|
//
|
|
// NOTE: We don't have a custom error for this. This will trigger a
|
|
// `sui::dynamic_field` error.
|
|
token_registry::add_new_native_test_only(
|
|
&mut registry,
|
|
&coin_meta
|
|
);
|
|
|
|
abort 42
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = sui::dynamic_field::EFieldTypeMismatch)]
|
|
// In this negative test case, we attempt to deposit a wrapped token into
|
|
// a TokenRegistry object, resulting in failure. A wrapped coin can
|
|
// only be minted and burned, not deposited.
|
|
fun test_cannot_deposit_wrapped_asset() {
|
|
let caller = person();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
let treasury_cap =
|
|
coin_wrapped_7::init_and_take_treasury_cap(
|
|
scenario,
|
|
caller
|
|
);
|
|
|
|
// Ignore effects.
|
|
test_scenario::next_tx(scenario, caller);
|
|
|
|
// Initialize new token registry.
|
|
let registry =
|
|
token_registry::new_test_only(test_scenario::ctx(scenario));
|
|
|
|
let coin_meta = test_scenario::take_shared<CoinMetadata<COIN_WRAPPED_7>>(scenario);
|
|
|
|
token_registry::add_new_wrapped_test_only(
|
|
&mut registry,
|
|
coin_wrapped_7::token_meta(),
|
|
&mut coin_meta,
|
|
treasury_cap,
|
|
test_scenario::ctx(scenario)
|
|
);
|
|
|
|
test_scenario::return_shared(coin_meta);
|
|
|
|
// Mint some wrapped coins and attempt to deposit balance.
|
|
let minted =
|
|
wrapped_asset::mint_test_only(
|
|
token_registry::borrow_mut_wrapped_test_only<COIN_WRAPPED_7>(
|
|
&mut registry
|
|
),
|
|
420420420
|
|
);
|
|
|
|
let verified = token_registry::verified_asset<COIN_WRAPPED_7>(®istry);
|
|
assert!(token_registry::is_wrapped(&verified), 0);
|
|
|
|
// You shall not pass!
|
|
//
|
|
// NOTE: We don't have a custom error for this. This will trigger a
|
|
// `sui::dynamic_field` error.
|
|
native_asset::deposit_test_only(
|
|
token_registry::borrow_mut_native_test_only<COIN_WRAPPED_7>(
|
|
&mut registry
|
|
),
|
|
minted
|
|
);
|
|
|
|
abort 42
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = sui::dynamic_field::EFieldTypeMismatch)]
|
|
// In this negative test case, we attempt to deposit a wrapped token into
|
|
// a TokenRegistry object, resulting in failure. A wrapped coin can
|
|
// only be minted and burned, not deposited.
|
|
fun test_cannot_mint_native_asset() {
|
|
let caller = person();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
coin_native_10::init_test_only(test_scenario::ctx(scenario));
|
|
|
|
// Ignore effects.
|
|
test_scenario::next_tx(scenario, caller);
|
|
|
|
// Initialize new token registry.
|
|
let registry =
|
|
token_registry::new_test_only(test_scenario::ctx(scenario));
|
|
|
|
let coin_meta = coin_native_10::take_metadata(scenario);
|
|
token_registry::add_new_native_test_only(
|
|
&mut registry,
|
|
&coin_meta
|
|
);
|
|
|
|
// Show that this asset is not wrapped.
|
|
let verified = token_registry::verified_asset<COIN_NATIVE_10>(®istry);
|
|
assert!(!token_registry::is_wrapped(&verified), 0);
|
|
|
|
// You shall not pass!
|
|
//
|
|
// NOTE: We don't have a custom error for this. This will trigger a
|
|
// `sui::dynamic_field` error.
|
|
let minted =
|
|
wrapped_asset::mint_test_only(
|
|
token_registry::borrow_mut_wrapped_test_only<COIN_NATIVE_10>(
|
|
&mut registry
|
|
),
|
|
420
|
|
);
|
|
|
|
// Clean up.
|
|
balance::destroy_for_testing(minted);
|
|
|
|
abort 42
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = token_registry::E_ALREADY_WRAPPED)]
|
|
fun test_cannot_add_new_wrapped_with_same_canonical_info() {
|
|
let caller = person();
|
|
let my_scenario = test_scenario::begin(caller);
|
|
let scenario = &mut my_scenario;
|
|
|
|
// Initialize new coin.
|
|
let treasury_cap =
|
|
coin_wrapped_7::init_and_take_treasury_cap(
|
|
scenario,
|
|
caller
|
|
);
|
|
|
|
// Initialize other coin
|
|
coin_native_10::init_test_only(test_scenario::ctx(scenario));
|
|
|
|
// Ignore effects.
|
|
test_scenario::next_tx(scenario, caller);
|
|
|
|
// Initialize new token registry.
|
|
let registry =
|
|
token_registry::new_test_only(test_scenario::ctx(scenario));
|
|
|
|
let coin_meta = test_scenario::take_shared<CoinMetadata<COIN_WRAPPED_7>>(scenario);
|
|
|
|
// Register wrapped asset.
|
|
token_registry::add_new_wrapped_test_only(
|
|
&mut registry,
|
|
coin_wrapped_7::token_meta(),
|
|
&mut coin_meta,
|
|
treasury_cap,
|
|
test_scenario::ctx(scenario)
|
|
);
|
|
|
|
test_scenario::return_shared(coin_meta);
|
|
|
|
let coin_meta = coin_native_10::take_metadata(scenario);
|
|
let treasury_cap = coin_native_10::take_treasury_cap(scenario);
|
|
|
|
// You shall not pass!
|
|
token_registry::add_new_wrapped_test_only(
|
|
&mut registry,
|
|
coin_wrapped_7::token_meta(),
|
|
&mut coin_meta,
|
|
treasury_cap,
|
|
test_scenario::ctx(scenario)
|
|
);
|
|
|
|
abort 42
|
|
}
|
|
}
|