wormhole/aptos/nft_bridge/sources/complete_transfer.move

461 lines
16 KiB
Plaintext

module nft_bridge::complete_transfer {
use aptos_std::from_bcs;
use aptos_token::token;
use nft_bridge::vaa;
use nft_bridge::transfer::{Self, Transfer};
use nft_bridge::state;
use nft_bridge::wrapped;
use nft_bridge::token_hash;
use token_bridge::string32;
use wormhole::state as wormhole_state;
use wormhole::external_address;
const E_INVALID_TARGET: u64 = 0;
const E_INVALID_TOKEN_ADDRESS: u64 = 1;
public fun submit_vaa(vaa: vector<u8>): Transfer {
let vaa = vaa::parse_verify_and_replay_protect(vaa);
let transfer = transfer::parse(wormhole::vaa::destroy(vaa));
complete_transfer(&transfer);
transfer
}
public entry fun submit_vaa_entry(vaa: vector<u8>) {
submit_vaa(vaa);
}
/// Submits the complete transfer VAA and registers the NFT for the
/// recipient if not already registered.
public entry fun submit_vaa_and_register_entry(recipient: &signer, vaa: vector<u8>) {
token::opt_in_direct_transfer(recipient, true);
submit_vaa(vaa);
}
#[test_only]
public fun test(transfer: &Transfer) {
complete_transfer(transfer)
}
fun complete_transfer(transfer: &Transfer) {
let to_chain = transfer::get_to_chain(transfer);
assert!(to_chain == wormhole::state::get_chain_id(), E_INVALID_TARGET);
let token_chain = transfer::get_token_chain(transfer);
let token_id = transfer::get_token_id(transfer);
let uri = transfer::get_uri(transfer);
let recipient = from_bcs::to_address(external_address::get_bytes(&transfer::get_to(transfer)));
let is_wrapped_asset: bool = token_chain != wormhole_state::get_chain_id();
if (is_wrapped_asset) {
let (creator, collection) = wrapped::create_or_find_wrapped_nft_collection(transfer);
wrapped::mint_to(&creator, recipient, string32::to_string(&collection), &token_id, uri);
} else {
// native, we must have seen this token before (on the way out)
let nft_bridge = state::nft_bridge_signer();
// Since the way we derive external ids for tokens (see
// token_hash.move) guarantees that the ids are globally unique, we
// only need the token_id field of the transfer VAA to identify the
// token in question, the token_address is not necessary...
let token_hash = token_hash::from_external_address(token_id);
let token_id = state::get_native_asset_info(token_hash);
// ...nevertheless, as a sanity check, we derive the collection hash
// and ensure that it comes from collection that token_address claims.
let (collection_hash, _) = token_hash::derive(&token_id);
let collection_hash = token_hash::get_collection_external_address(&collection_hash);
assert!(collection_hash == transfer::get_token_address(transfer), E_INVALID_TOKEN_ADDRESS);
token::transfer(&nft_bridge, token_id, recipient, 1);
};
}
}
#[test_only]
module nft_bridge::complete_transfer_test {
use std::signer;
use std::string::{Self, String};
use std::bcs;
use aptos_framework::account;
use aptos_token::token;
use wormhole::external_address::{Self, ExternalAddress};
use wormhole::u16;
use token_bridge::string32;
use nft_bridge::transfer;
use nft_bridge::uri;
use nft_bridge::complete_transfer;
use nft_bridge::state;
use nft_bridge::transfer_nft;
use nft_bridge::token_hash;
use nft_bridge::transfer_nft_test;
use nft_bridge::wrapped_token_name;
// ------ Wrapped asset tests
// Test that complete_transfer for wrapped token works
#[test(deployer = @deployer, recipient = @0x1234)]
public fun test_complete_transfer_wrapped_asset(deployer: &signer, recipient: address) {
wormhole::wormhole_test::setup(0);
nft_bridge::nft_bridge::init_test(deployer);
let recipient_signer = aptos_framework::account::create_account_for_test(recipient);
complete_transfer_wrapped_helper(&recipient_signer, external_address::from_bytes(x"01"));
}
// Test that the same token can't be transferred in twice without being
// transferred out first
#[test(deployer = @deployer, recipient1 = @0x1234, recipient2 = @0x5678)]
#[expected_failure(abort_code = 524297, location = aptos_token::token)]
public fun test_complete_transfer_wrapped_asset_twice(
deployer: &signer,
recipient1: address,
recipient2: address
) {
wormhole::wormhole_test::setup(0);
nft_bridge::nft_bridge::init_test(deployer);
let recipient_signer1 = aptos_framework::account::create_account_for_test(recipient1);
let recipient_signer2 = aptos_framework::account::create_account_for_test(recipient2);
complete_transfer_wrapped_helper(&recipient_signer1, external_address::from_bytes(x"01"));
complete_transfer_wrapped_helper(&recipient_signer2, external_address::from_bytes(x"01"));
}
// Test that transferring a token in, then out, then back in again works
#[test(deployer = @deployer, recipient = @0x1234)]
public fun test_complete_transfer_wrapped_there_and_back(
deployer: &signer,
recipient: address,
) {
wormhole::wormhole_test::setup(0);
nft_bridge::nft_bridge::init_test(deployer);
let recipient_signer = aptos_framework::account::create_account_for_test(recipient);
let token_id = external_address::from_bytes(x"01");
// step 1) transfer in
complete_transfer_wrapped_helper(&recipient_signer, token_id);
let token_address = external_address::from_bytes(x"09");
let token_chain = u16::from_u64(14);
let origin_info = state::create_origin_info(token_chain, token_address);
let creator = state::get_wrapped_asset_signer(origin_info);
let expected_token_name = string::utf8(b"0000000000000000000000000000000000000000000000000000000000000001");
// step 2) transfer out
transfer_nft::transfer_nft_entry(
&recipient_signer,
signer::address_of(&creator),
string::utf8(b"my name"),
expected_token_name,
0, // property_version
1, // recipient chain (doesn't matter)
x"0000000000000000000000000000000000000000000000000000000000012345", // recipient (doesn't matter)
0 // nonce (doesn't matter)
);
// step 3) transfer in again
complete_transfer_wrapped_helper(&recipient_signer, token_id);
}
// Test that transferring a token in, then out, preserves the metadata
#[test(deployer = @deployer, recipient = @0x1234)]
public fun test_complete_transfer_wrapped_preserves_metadata(
deployer: &signer,
recipient: address,
) {
wormhole::wormhole_test::setup(0);
nft_bridge::nft_bridge::init_test(deployer);
let recipient_signer = aptos_framework::account::create_account_for_test(recipient);
let token_id = external_address::from_bytes(x"01");
// step 1) transfer in
complete_transfer_wrapped_helper(&recipient_signer, token_id);
let token_address = external_address::from_bytes(x"09");
let token_chain = u16::from_u64(14);
let origin_info = state::create_origin_info(token_chain, token_address);
let creator = state::get_wrapped_asset_signer(origin_info);
let expected_token_name = string::utf8(b"0000000000000000000000000000000000000000000000000000000000000001");
let token_id = token::create_token_id_raw(
signer::address_of(&creator),
string::utf8(b"my name"),
expected_token_name,
0, // property_version
);
let token = token::withdraw_token(&recipient_signer, token_id, 1);
let (token_address,
token_chain,
symbol,
name,
token_id,
uri,
) = transfer_nft::transfer_nft_test(token);
assert!(token_address == external_address::from_bytes(x"09"), 0);
assert!(token_chain == u16::from_u64(14), 0);
assert!(symbol == string32::from_bytes(b"my symbol"), 0);
assert!(name == string32::from_bytes(b"my name"), 0);
assert!(token_id == external_address::from_bytes(x"01"), 0);
assert!(uri == uri::from_bytes(b"http://google.com"), 0);
}
// Test that multiple tokens can be minted from the same collection
#[test(deployer=@deployer, recipient=@0x1234)]
public fun test_complete_transfer_wrapped_asset_multiple_tokens(deployer: &signer, recipient: address) {
wormhole::wormhole_test::setup(0);
nft_bridge::nft_bridge::init_test(deployer);
let recipient = aptos_framework::account::create_account_for_test(recipient);
token::opt_in_direct_transfer(&recipient, true);
let token_id1 = external_address::from_bytes(x"01");
let token_id2 = external_address::from_bytes(x"02");
complete_transfer_wrapped_helper(&recipient, token_id1);
complete_transfer_wrapped_helper(&recipient, token_id2);
}
/// Helper function that transfer a token into the recipient address
fun complete_transfer_wrapped_helper(
recipient_signer: &signer,
token_id: ExternalAddress
) {
let recipient = signer::address_of(recipient_signer);
let recipient_external = external_address::left_pad(&bcs::to_bytes(&recipient));
token::opt_in_direct_transfer(recipient_signer, true);
let token_address = external_address::from_bytes(x"09");
let token_chain = u16::from_u64(14);
let token_symbol = string32::from_bytes(b"my symbol");
let token_name = string32::from_bytes(b"my name");
let token_uri = uri::from_bytes(b"http://google.com");
let t = transfer::create(
token_address,
token_chain,
token_symbol,
token_name,
token_id,
token_uri,
recipient_external,
u16::from_u64(22) // to chain
);
// complete transfer using transfer object above
complete_transfer::test(&t);
let origin_info = state::create_origin_info(token_chain, token_address);
let creator = state::get_wrapped_asset_signer(origin_info);
let expected_collection_name = string::utf8(b"my name");
let expected_token_name = wrapped_token_name::render_hex(external_address::get_bytes(&token_id));
let token_id = token::create_token_id_raw(
signer::address_of(&creator),
expected_collection_name,
expected_token_name,
0
);
assert!(token::balance_of(recipient, token_id) == 1, 0);
}
// ------ Native asset tests
#[test(
deployer = @deployer,
creator = @0x654321,
first_user = @0x123456,
second_user = @0x121212
)]
fun complete_transfer_native_test(
deployer: &signer,
creator: address,
first_user: &signer,
second_user: &signer
) {
let collection_name = string::utf8(b"my test collection");
let token_name = string::utf8(b"my test token");
complete_transfer_native_helper(
deployer,
creator,
first_user,
second_user,
collection_name,
token_name,
true,
22
);
}
#[test(
deployer = @deployer,
creator = @0x654321,
first_user = @0x123456,
second_user = @0x121212
)]
#[expected_failure(abort_code = 1, location = nft_bridge::complete_transfer)]
fun complete_transfer_native_test_incorrect_token_address(
deployer: &signer,
creator: address,
first_user: &signer,
second_user: &signer
) {
let collection_name = string::utf8(b"my test collection");
let token_name = string::utf8(b"my test token");
complete_transfer_native_helper(
deployer,
creator,
first_user,
second_user,
collection_name,
token_name,
false,
22
);
}
#[test(
deployer = @deployer,
creator = @0x654321,
first_user = @0x123456,
second_user = @0x121212
)]
#[expected_failure(abort_code = 0, location = nft_bridge::complete_transfer)]
fun complete_transfer_native_test_incorrect_target_chain(
deployer: &signer,
creator: address,
first_user: &signer,
second_user: &signer
) {
let collection_name = string::utf8(b"my test collection");
let token_name = string::utf8(b"my test token");
complete_transfer_native_helper(
deployer,
creator,
first_user,
second_user,
collection_name,
token_name,
true,
21
);
}
/// Helper function for performing a variety of tests that all follow the
/// same flow.
///
/// This function performs the setup, then creates a collection under
/// `creator`, then mints a token to `first_user`, then transfers out that
/// token.
/// Finally, it transfers the token back in to `second_user`.
fun complete_transfer_native_helper(
deployer: &signer,
creator: address,
first_user: &signer,
second_user: &signer,
collection_name: String,
token_name: String,
// if this flag is `true`, the 'token_address' field in the incoming
// transfer will be one matching the collection hash of the token,
// otherwise an arbitary address
use_correct_token_address: bool,
// this flag determines the target chain of the incoming transfer
// (only 22 should be accepted)
target_chain: u64
) {
// ------ Setup
wormhole::wormhole_test::setup(0);
nft_bridge::nft_bridge::init_test(deployer);
aptos_framework::aptos_account::create_account(signer::address_of(first_user));
token::opt_in_direct_transfer(first_user, true);
aptos_framework::aptos_account::create_account(signer::address_of(second_user));
token::opt_in_direct_transfer(second_user, true);
let creator = account::create_account_for_test(creator);
// ------ Create a collection under `creator`
transfer_nft_test::create_collection(&creator, collection_name);
// ------ Mint a token to `first_user` from the collection we just created
transfer_nft_test::mint_token_to(
&creator,
signer::address_of(first_user),
collection_name,
token_name,
1
);
// ------ Construct token_id for follow-up queries
let token_id = token::create_token_id_raw(
signer::address_of(&creator),
collection_name,
token_name,
0 // property_version
);
let (collection_hash, token_hash) = token_hash::derive(&token_id);
// ------ Transfer the tokens
transfer_nft::transfer_nft_entry(
first_user,
signer::address_of(&creator),
collection_name,
token_name,
0, // property_version
3, // recipient chain
x"0000000000000000000000000000000000000000000000000000000000FAFAFA",
0
);
// ------ Check that `nft_bridge` now holds the token
assert!(token::balance_of(@nft_bridge, token_id) == 1, 0);
let expected_token_address: ExternalAddress;
if (use_correct_token_address) {
expected_token_address = token_hash::get_collection_external_address(&collection_hash);
} else {
expected_token_address = external_address::from_bytes(x"0123");
};
let t = transfer::create(
expected_token_address,
u16::from_u64(22),
string32::from_bytes(b"symbol (ignored)"),
string32::from_bytes(b"name (ignored)"),
token_hash::get_token_external_address(&token_hash),
uri::from_bytes(b"uri (ignored)"),
external_address::from_bytes(std::bcs::to_bytes(&signer::address_of(second_user))),
u16::from_u64(target_chain)
);
// complete transfer using transfer object above
complete_transfer::test(&t);
// ------ Check that `second_user` now holds the token
assert!(token::balance_of(signer::address_of(second_user), token_id) == 1, 0);
}
}