wrapped.move 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. module nft_bridge::wrapped {
  2. use std::signer;
  3. use std::vector;
  4. use std::bcs;
  5. use std::string::{Self, String};
  6. use aptos_framework::account;
  7. use aptos_token::token;
  8. use wormhole::serialize;
  9. use wormhole::external_address::{Self, ExternalAddress};
  10. use token_bridge::string32::{Self, String32};
  11. use nft_bridge::state::{Self, OriginInfo};
  12. use nft_bridge::transfer::{Self, Transfer};
  13. use nft_bridge::uri::{Self, URI};
  14. use nft_bridge::wrapped_token_name;
  15. friend nft_bridge::complete_transfer;
  16. friend nft_bridge::transfer_nft;
  17. #[test_only]
  18. friend nft_bridge::transfer_nft_test;
  19. #[test_only]
  20. friend nft_bridge::wrapped_test;
  21. #[test_only]
  22. friend nft_bridge::complete_transfer_test;
  23. const E_IS_NOT_WRAPPED_ASSET: u64 = 0;
  24. /// Create a new collection from the transfer data if it doesn't already exist.
  25. /// The collection will be created into a resource account, whose signer is
  26. /// returned.
  27. public(friend) fun create_or_find_wrapped_nft_collection(transfer: &Transfer): (signer, String32) {
  28. let token_address = transfer::get_token_address(transfer);
  29. let token_chain = transfer::get_token_chain(transfer);
  30. let origin_info = state::create_origin_info(token_chain, token_address);
  31. let original_name = transfer::get_name(transfer);
  32. let original_symbol = transfer::get_symbol(transfer);
  33. let name: String32;
  34. let symbol: String32;
  35. if (state::is_unified_solana_collection(origin_info)) {
  36. name = string32::from_bytes(b"Wormhole Bridged Solana-NFT");
  37. symbol = string32::from_bytes(b"WORMSPLNFT");
  38. state::set_spl_cache(transfer::get_token_id(transfer), original_name, original_symbol);
  39. } else {
  40. name = original_name;
  41. symbol = original_symbol;
  42. };
  43. // if the resource account already exists, we don't need do anything
  44. if (!state::wrapped_asset_signer_exists(origin_info)) {
  45. let seed = create_seed(&origin_info);
  46. //create resource account
  47. let nft_bridge_signer = state::nft_bridge_signer();
  48. let (new_signer, new_cap) = account::create_resource_account(&nft_bridge_signer, seed);
  49. state::set_wrapped_asset_info(origin_info, new_cap, symbol);
  50. init_wrapped_nft(&new_signer, name, origin_info);
  51. (new_signer, name)
  52. } else {
  53. (state::get_wrapped_asset_signer(origin_info), name)
  54. }
  55. }
  56. fun init_wrapped_nft(
  57. creator_signer: &signer,
  58. name: String32,
  59. origin_info: OriginInfo,
  60. ) {
  61. let description = string::utf8(b"NFT transferred through Wormhole");
  62. let uri = string::utf8(b"http://portalbridge.com");
  63. // unbounded
  64. let maximum = 0;
  65. // allow all fields to be mutated, in case needed in the future
  66. let mutability_config = vector[true, true, true];
  67. token::create_collection(
  68. creator_signer,
  69. string32::to_string(&name),
  70. description,
  71. uri,
  72. maximum,
  73. mutability_config,
  74. );
  75. state::setup_wrapped(origin_info);
  76. }
  77. public(friend) fun mint_to(
  78. creator: &signer,
  79. recipient: address,
  80. collection: String,
  81. token_external_id: &ExternalAddress,
  82. uri: URI
  83. ) {
  84. // for the token name, we put the hex of the token id.
  85. // TODO: is there anything better we could do? maybe render as
  86. // decimal, as most chains use decimal numbers for token ids.
  87. let name = wrapped_token_name::render_hex(external_address::get_bytes(token_external_id));
  88. // set token data, including property keys (set token burnability to true)
  89. let token_mut_config = token::create_token_mutability_config(
  90. &vector[
  91. true, // TOKEN_MAX_MUTABLE
  92. true, // TOKEN_URI_MUTABLE
  93. true, // TOKEN_ROYALTY_MUTABLE_IND
  94. true, // TOKEN_DESCRIPTION_MUTABLE_IND
  95. true // TOKEN_PROPERTY_MUTABLE_IND
  96. ]
  97. );
  98. // NOTE: Whether a token can be burned at all, burned by owner, or
  99. // burned by creator is set in the property keys field when calling
  100. // token::create_tokendata. We only allow `burn_by_creator` to avoid an
  101. // edge case whereby a user burns a wrapped token and can no longer
  102. // bridge it back to the origin chain.
  103. let token_data_id = token::create_tokendata(
  104. creator,
  105. collection, // token collection name
  106. name, // token name
  107. string::utf8(b""), //empty description
  108. 1, //supply cap 1
  109. uri::to_string(&uri),
  110. signer::address_of(creator),
  111. 0, // royalty_points_denominator
  112. 0, // royalty_points_numerator
  113. token_mut_config, // see above
  114. // the following three arguments declare that
  115. // TOKEN_BURNABLE_BY_CREATOR (of type bool) should be set to true
  116. // see NOTE above
  117. vector<String>[string::utf8(b"TOKEN_BURNABLE_BY_CREATOR")],
  118. vector<vector<u8>>[bcs::to_bytes<bool>(&true)],
  119. vector<String>[string::utf8(b"bool")],
  120. );
  121. token::mint_token_to(
  122. creator,
  123. recipient,
  124. token_data_id,
  125. 1
  126. );
  127. }
  128. /// Derive the generation seed for the resource account from
  129. /// (token chain (2 bytes) || token address (32 bytes)).
  130. fun create_seed(origin_info: &OriginInfo): vector<u8> {
  131. let token_chain = state::get_origin_info_token_chain(origin_info);
  132. let token_address = state::get_origin_info_token_address(origin_info);
  133. let seed = vector::empty<u8>();
  134. serialize::serialize_u16(&mut seed, token_chain);
  135. external_address::serialize(&mut seed, token_address);
  136. seed
  137. }
  138. }
  139. #[test_only]
  140. module nft_bridge::wrapped_test {
  141. use std::signer;
  142. use std::string::String;
  143. use aptos_token::token;
  144. use wormhole::external_address;
  145. use wormhole::u16;
  146. use token_bridge::string32;
  147. use nft_bridge::transfer;
  148. use nft_bridge::uri;
  149. use nft_bridge::wrapped;
  150. /// Creates a test NFT collection
  151. public fun create_wrapped_nft_collection(recipient: address, collection_name: String): signer {
  152. let token_address = external_address::from_bytes(x"00");
  153. let token_chain = u16::from_u64(14);
  154. let token_id = external_address::from_bytes(x"01");
  155. let token_symbol = string32::from_bytes(b"collection symbol");
  156. let token_name = string32::from_string(&collection_name);
  157. let uri = uri::from_bytes(b"http://netscape-navigator.it");
  158. let t = transfer::create(
  159. token_address,
  160. token_chain,
  161. token_symbol,
  162. token_name,
  163. token_id,
  164. uri,
  165. external_address::from_bytes(x"0000"),
  166. u16::from_u64(1) // target chain
  167. );
  168. let (creator, _) = wrapped::create_or_find_wrapped_nft_collection(&t);
  169. // assert that collection was indeed created
  170. assert!(token::check_collection_exists(signer::address_of(&creator), string32::to_string(&token_name)), 0);
  171. wrapped::mint_to(&creator, recipient, string32::to_string(&token_name), &token_id, uri);
  172. creator
  173. }
  174. }