transfer_tokens.move 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. // SPDX-License-Identifier: Apache 2
  2. /// This module implements three methods: `prepare_transfer` and
  3. /// `transfer_tokens`, which are meant to work together.
  4. ///
  5. /// `prepare_transfer` allows a contract to pack token transfer parameters in
  6. /// preparation to bridge these assets to another network. Anyone can call this
  7. /// method to create `TransferTicket`.
  8. ///
  9. /// `transfer_tokens` unpacks the `TransferTicket` and constructs a
  10. /// `MessageTicket`, which will be used by Wormhole's `publish_message`
  11. /// module.
  12. ///
  13. /// The purpose of splitting this token transferring into two steps is in case
  14. /// Token Bridge needs to be upgraded and there is a breaking change for this
  15. /// module, an integrator would not be left broken. It is discouraged to put
  16. /// `transfer_tokens` in an integrator's package logic. Otherwise, this
  17. /// integrator needs to be prepared to upgrade his contract to handle the latest
  18. /// version of `transfer_tokens`.
  19. ///
  20. /// Instead, an integrator is encouraged to execute a transaction block, which
  21. /// executes `transfer_tokens` using the latest Token Bridge package ID and to
  22. /// implement `prepare_transfer` in his contract to produce `PrepareTransfer`.
  23. ///
  24. /// NOTE: Only assets that exist in the `TokenRegistry` can be bridged out,
  25. /// which are native Sui assets that have been attested for via `attest_token`
  26. /// and wrapped foreign assets that have been created using foreign asset
  27. /// metadata via the `create_wrapped` module.
  28. ///
  29. /// See `transfer` module for serialization and deserialization of Wormhole
  30. /// message payload.
  31. module token_bridge::transfer_tokens {
  32. use sui::balance::{Self, Balance};
  33. use sui::coin::{Self, Coin};
  34. use wormhole::bytes32::{Self};
  35. use wormhole::external_address::{Self, ExternalAddress};
  36. use wormhole::publish_message::{MessageTicket};
  37. use token_bridge::native_asset::{Self};
  38. use token_bridge::normalized_amount::{Self, NormalizedAmount};
  39. use token_bridge::state::{Self, State, LatestOnly};
  40. use token_bridge::token_registry::{Self, VerifiedAsset};
  41. use token_bridge::transfer::{Self};
  42. use token_bridge::wrapped_asset::{Self};
  43. friend token_bridge::transfer_tokens_with_payload;
  44. /// Relayer fee exceeds `Coin` object's value.
  45. const E_RELAYER_FEE_EXCEEDS_AMOUNT: u64 = 0;
  46. /// This type represents transfer data for a recipient on a foreign chain.
  47. /// The only way to destroy this type is calling `transfer_tokens`.
  48. ///
  49. /// NOTE: An integrator that expects to bridge assets between his contracts
  50. /// should probably use the `transfer_tokens_with_payload` module, which
  51. /// expects a specific redeemer to complete the transfer (transfers sent
  52. /// using `transfer_tokens` can be redeemed by anyone on behalf of the
  53. /// encoded recipient).
  54. struct TransferTicket<phantom CoinType> {
  55. asset_info: VerifiedAsset<CoinType>,
  56. bridged_in: Balance<CoinType>,
  57. norm_amount: NormalizedAmount,
  58. recipient_chain: u16,
  59. recipient: vector<u8>,
  60. relayer_fee: u64,
  61. nonce: u32
  62. }
  63. /// `prepare_transfer` constructs token transfer parameters. Any remaining
  64. /// amount (A.K.A. dust) from the funds provided will be returned along with
  65. /// the `TransferTicket` type. The returned coin object is the same object
  66. /// moved into this method.
  67. ///
  68. /// NOTE: Integrators of Token Bridge should be calling only this method
  69. /// from their contracts. This method is not guarded by version control
  70. /// (thus not requiring a reference to the Token Bridge `State` object), so
  71. /// it is intended to work for any package version.
  72. public fun prepare_transfer<CoinType>(
  73. asset_info: VerifiedAsset<CoinType>,
  74. funded: Coin<CoinType>,
  75. recipient_chain: u16,
  76. recipient: vector<u8>,
  77. relayer_fee: u64,
  78. nonce: u32
  79. ): (
  80. TransferTicket<CoinType>,
  81. Coin<CoinType>
  82. ) {
  83. let (
  84. bridged_in,
  85. norm_amount
  86. ) = take_truncated_amount(&asset_info, &mut funded);
  87. let ticket =
  88. TransferTicket {
  89. asset_info,
  90. bridged_in,
  91. norm_amount,
  92. relayer_fee,
  93. recipient_chain,
  94. recipient,
  95. nonce
  96. };
  97. // The remaining amount of funded may have dust depending on the
  98. // decimals of this asset.
  99. (ticket, funded)
  100. }
  101. /// `transfer_tokens` is the only method that can unpack the members of
  102. /// `TransferTicket`. This method takes the balance from this type and
  103. /// bridges this asset out of Sui by either joining its balance in the Token
  104. /// Bridge's custody for native assets or burning its balance for wrapped
  105. /// assets.
  106. ///
  107. /// A `relayer_fee` of some value less than or equal to the bridged balance
  108. /// can be specified to incentivize someone to redeem this transfer on
  109. /// behalf of the `recipient`.
  110. ///
  111. /// This method returns the prepared Wormhole message (which should be
  112. /// consumed by calling `publish_message` in a transaction block).
  113. ///
  114. /// NOTE: This method is guarded by a minimum build version check. This
  115. /// method could break backward compatibility on an upgrade.
  116. ///
  117. /// It is important for integrators to refrain from calling this method
  118. /// within their contracts. This method is meant to be called in a
  119. /// transaction block after receiving a `TransferTicket` from calling
  120. /// `prepare_transfer` within a contract. If in a circumstance where this
  121. /// module has a breaking change in an upgrade, `prepare_transfer` will not
  122. /// be affected by this change.
  123. public fun transfer_tokens<CoinType>(
  124. token_bridge_state: &mut State,
  125. ticket: TransferTicket<CoinType>
  126. ): MessageTicket {
  127. // This capability ensures that the current build version is used.
  128. let latest_only = state::assert_latest_only(token_bridge_state);
  129. let (
  130. nonce,
  131. encoded_transfer
  132. ) =
  133. bridge_in_and_serialize_transfer(
  134. &latest_only,
  135. token_bridge_state,
  136. ticket
  137. );
  138. // Prepare Wormhole message with encoded `Transfer`.
  139. state::prepare_wormhole_message(
  140. &latest_only,
  141. token_bridge_state,
  142. nonce,
  143. encoded_transfer
  144. )
  145. }
  146. /// Modify coin based on the decimals of a given coin type, which may
  147. /// leave some amount if the decimals lead to truncating the coin's balance.
  148. /// This method returns the extracted balance (which will be bridged out of
  149. /// Sui) and the normalized amount, which will be encoded in the token
  150. /// transfer payload.
  151. ///
  152. /// NOTE: This is a privileged method, which only this and the
  153. /// `transfer_tokens_with_payload` modules can use.
  154. public(friend) fun take_truncated_amount<CoinType>(
  155. asset_info: &VerifiedAsset<CoinType>,
  156. funded: &mut Coin<CoinType>
  157. ): (
  158. Balance<CoinType>,
  159. NormalizedAmount
  160. ) {
  161. // Calculate dust. If there is any, `bridged_in` will have remaining
  162. // value after split. `norm_amount` is copied since it is denormalized
  163. // at this step.
  164. let decimals = token_registry::coin_decimals(asset_info);
  165. let norm_amount =
  166. normalized_amount::from_raw(coin::value(funded), decimals);
  167. // Split the `bridged_in` coin object to return any dust remaining on
  168. // that object. Only bridge in the adjusted amount after de-normalizing
  169. // the normalized amount.
  170. let truncated =
  171. balance::split(
  172. coin::balance_mut(funded),
  173. normalized_amount::to_raw(norm_amount, decimals)
  174. );
  175. (truncated, norm_amount)
  176. }
  177. /// For a given coin type, either burn Token Bridge wrapped assets or
  178. /// deposit coin into Token Bridge's custody. This method returns the
  179. /// canonical token info (chain ID and address), which will be encoded in
  180. /// the token transfer.
  181. ///
  182. /// NOTE: This is a privileged method, which only this and the
  183. /// `transfer_tokens_with_payload` modules can use.
  184. public(friend) fun burn_or_deposit_funds<CoinType>(
  185. latest_only: &LatestOnly,
  186. token_bridge_state: &mut State,
  187. asset_info: &VerifiedAsset<CoinType>,
  188. bridged_in: Balance<CoinType>
  189. ): (
  190. u16,
  191. ExternalAddress
  192. ) {
  193. // Either burn or deposit depending on `CoinType`.
  194. let registry =
  195. state::borrow_mut_token_registry(latest_only, token_bridge_state);
  196. if (token_registry::is_wrapped(asset_info)) {
  197. wrapped_asset::burn(
  198. token_registry::borrow_mut_wrapped(registry),
  199. bridged_in
  200. );
  201. } else {
  202. native_asset::deposit(
  203. token_registry::borrow_mut_native(registry),
  204. bridged_in
  205. );
  206. };
  207. // Return canonical token info.
  208. (
  209. token_registry::token_chain(asset_info),
  210. token_registry::token_address(asset_info)
  211. )
  212. }
  213. fun bridge_in_and_serialize_transfer<CoinType>(
  214. latest_only: &LatestOnly,
  215. token_bridge_state: &mut State,
  216. ticket: TransferTicket<CoinType>
  217. ): (
  218. u32,
  219. vector<u8>
  220. ) {
  221. let TransferTicket {
  222. asset_info,
  223. bridged_in,
  224. norm_amount,
  225. recipient_chain,
  226. recipient,
  227. relayer_fee,
  228. nonce
  229. } = ticket;
  230. // Disallow `relayer_fee` to be greater than the `Coin` object's value.
  231. // Keep in mind that the relayer fee is evaluated against the truncated
  232. // amount.
  233. let amount = sui::balance::value(&bridged_in);
  234. assert!(relayer_fee <= amount, E_RELAYER_FEE_EXCEEDS_AMOUNT);
  235. // Handle funds and get canonical token info for encoded transfer.
  236. let (
  237. token_chain,
  238. token_address
  239. ) = burn_or_deposit_funds(
  240. latest_only,
  241. token_bridge_state,
  242. &asset_info, bridged_in
  243. );
  244. // Ensure that the recipient is a 32-byte address.
  245. let recipient = external_address::new(bytes32::from_bytes(recipient));
  246. // Finally encode `Transfer`.
  247. let encoded =
  248. transfer::serialize(
  249. transfer::new(
  250. norm_amount,
  251. token_address,
  252. token_chain,
  253. recipient,
  254. recipient_chain,
  255. normalized_amount::from_raw(
  256. relayer_fee,
  257. token_registry::coin_decimals(&asset_info)
  258. )
  259. )
  260. );
  261. (nonce, encoded)
  262. }
  263. #[test_only]
  264. public fun bridge_in_and_serialize_transfer_test_only<CoinType>(
  265. token_bridge_state: &mut State,
  266. ticket: TransferTicket<CoinType>
  267. ): (
  268. u32,
  269. vector<u8>
  270. ) {
  271. // This capability ensures that the current build version is used.
  272. let latest_only = state::assert_latest_only(token_bridge_state);
  273. bridge_in_and_serialize_transfer(
  274. &latest_only,
  275. token_bridge_state,
  276. ticket
  277. )
  278. }
  279. }
  280. #[test_only]
  281. module token_bridge::transfer_token_tests {
  282. use sui::coin::{Self};
  283. use sui::test_scenario::{Self};
  284. use wormhole::bytes32::{Self};
  285. use wormhole::external_address::{Self};
  286. use wormhole::publish_message::{Self};
  287. use wormhole::state::{chain_id};
  288. use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
  289. use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7};
  290. use token_bridge::native_asset::{Self};
  291. use token_bridge::normalized_amount::{Self};
  292. use token_bridge::state::{Self};
  293. use token_bridge::token_bridge_scenario::{
  294. set_up_wormhole_and_token_bridge,
  295. register_dummy_emitter,
  296. return_state,
  297. take_state,
  298. person
  299. };
  300. use token_bridge::token_registry::{Self};
  301. use token_bridge::transfer::{Self};
  302. use token_bridge::transfer_tokens::{Self};
  303. use token_bridge::wrapped_asset::{Self};
  304. /// Test consts.
  305. const TEST_TARGET_RECIPIENT: vector<u8> = x"beef4269";
  306. const TEST_TARGET_CHAIN: u16 = 2;
  307. const TEST_NONCE: u32 = 0;
  308. const TEST_COIN_NATIVE_10_DECIMALS: u8 = 10;
  309. const TEST_COIN_WRAPPED_7_DECIMALS: u8 = 7;
  310. #[test]
  311. fun test_transfer_tokens_native_10() {
  312. use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
  313. let sender = person();
  314. let my_scenario = test_scenario::begin(sender);
  315. let scenario = &mut my_scenario;
  316. // Set up contracts.
  317. let wormhole_fee = 350;
  318. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  319. // Register foreign emitter on chain ID == 2.
  320. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  321. // Register and mint coins.
  322. let transfer_amount = 6942000;
  323. let coin_10_balance =
  324. coin_native_10::init_register_and_mint(
  325. scenario,
  326. sender,
  327. transfer_amount
  328. );
  329. // Ignore effects.
  330. test_scenario::next_tx(scenario, sender);
  331. // Fetch objects necessary for sending the transfer.
  332. let token_bridge_state = take_state(scenario);
  333. // Define the relayer fee.
  334. let relayer_fee = 100000;
  335. // Balance check the Token Bridge before executing the transfer. The
  336. // initial balance should be zero for COIN_NATIVE_10.
  337. {
  338. let registry = state::borrow_token_registry(&token_bridge_state);
  339. let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
  340. assert!(native_asset::custody(asset) == 0, 0);
  341. };
  342. let asset_info = state::verified_asset(&token_bridge_state);
  343. let (
  344. ticket,
  345. dust
  346. ) =
  347. prepare_transfer(
  348. asset_info,
  349. coin::from_balance(
  350. coin_10_balance,
  351. test_scenario::ctx(scenario)
  352. ),
  353. TEST_TARGET_CHAIN,
  354. TEST_TARGET_RECIPIENT,
  355. relayer_fee,
  356. TEST_NONCE,
  357. );
  358. coin::destroy_zero(dust);
  359. // Call `transfer_tokens`.
  360. let prepared_msg =
  361. transfer_tokens(&mut token_bridge_state, ticket);
  362. // Balance check the Token Bridge after executing the transfer. The
  363. // balance should now reflect the `transfer_amount` defined in this
  364. // test.
  365. {
  366. let registry = state::borrow_token_registry(&token_bridge_state);
  367. let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
  368. assert!(native_asset::custody(asset) == transfer_amount, 0);
  369. };
  370. // Clean up.
  371. publish_message::destroy(prepared_msg);
  372. return_state(token_bridge_state);
  373. // Done.
  374. test_scenario::end(my_scenario);
  375. }
  376. #[test]
  377. fun test_transfer_tokens_native_10_with_dust_refund() {
  378. use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
  379. let sender = person();
  380. let my_scenario = test_scenario::begin(sender);
  381. let scenario = &mut my_scenario;
  382. // Set up contracts.
  383. let wormhole_fee = 350;
  384. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  385. // Register foreign emitter on chain ID == 2.
  386. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  387. // Register and mint coins.
  388. let transfer_amount = 1000069;
  389. let coin_10_balance =
  390. coin_native_10::init_register_and_mint(
  391. scenario,
  392. sender,
  393. transfer_amount
  394. );
  395. // This value will be used later. The contract should return dust
  396. // to the caller since COIN_NATIVE_10 has 10 decimals.
  397. let expected_dust = 69;
  398. // Ignore effects.
  399. test_scenario::next_tx(scenario, sender);
  400. // Fetch objects necessary for sending the transfer.
  401. let token_bridge_state = take_state(scenario);
  402. // Define the relayer fee.
  403. let relayer_fee = 100000;
  404. // Balance check the Token Bridge before executing the transfer. The
  405. // initial balance should be zero for COIN_NATIVE_10.
  406. {
  407. let registry = state::borrow_token_registry(&token_bridge_state);
  408. let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
  409. assert!(native_asset::custody(asset) == 0, 0);
  410. };
  411. let asset_info = state::verified_asset(&token_bridge_state);
  412. let (
  413. ticket,
  414. dust
  415. ) =
  416. prepare_transfer(
  417. asset_info,
  418. coin::from_balance(
  419. coin_10_balance,
  420. test_scenario::ctx(scenario)
  421. ),
  422. TEST_TARGET_CHAIN,
  423. TEST_TARGET_RECIPIENT,
  424. relayer_fee,
  425. TEST_NONCE,
  426. );
  427. assert!(coin::value(&dust) == expected_dust, 0);
  428. // Call `transfer_tokens`.
  429. let prepared_msg =
  430. transfer_tokens(&mut token_bridge_state, ticket);
  431. // Balance check the Token Bridge after executing the transfer. The
  432. // balance should now reflect the `transfer_amount` less `expected_dust`
  433. // defined in this test.
  434. {
  435. let registry = state::borrow_token_registry(&token_bridge_state);
  436. let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
  437. assert!(
  438. native_asset::custody(asset) == transfer_amount - expected_dust,
  439. 0
  440. );
  441. };
  442. // Clean up.
  443. publish_message::destroy(prepared_msg);
  444. coin::burn_for_testing(dust);
  445. return_state(token_bridge_state);
  446. // Done.
  447. test_scenario::end(my_scenario);
  448. }
  449. #[test]
  450. fun test_serialize_transfer_tokens_native_10() {
  451. use token_bridge::transfer_tokens::{
  452. bridge_in_and_serialize_transfer_test_only,
  453. prepare_transfer
  454. };
  455. let sender = person();
  456. let my_scenario = test_scenario::begin(sender);
  457. let scenario = &mut my_scenario;
  458. // Set up contracts.
  459. let wormhole_fee = 350;
  460. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  461. // Register foreign emitter on chain ID == 2.
  462. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  463. // Register and mint coins.
  464. let transfer_amount = 6942000;
  465. let bridged_coin_10 =
  466. coin::from_balance(
  467. coin_native_10::init_register_and_mint(
  468. scenario,
  469. sender,
  470. transfer_amount
  471. ),
  472. test_scenario::ctx(scenario)
  473. );
  474. // Ignore effects.
  475. test_scenario::next_tx(scenario, sender);
  476. // Fetch objects necessary for sending the transfer.
  477. let token_bridge_state = take_state(scenario);
  478. // Define the relayer fee.
  479. let relayer_fee = 100000;
  480. let asset_info = state::verified_asset(&token_bridge_state);
  481. let expected_token_address = token_registry::token_address(&asset_info);
  482. let (
  483. ticket,
  484. dust
  485. ) =
  486. prepare_transfer(
  487. asset_info,
  488. bridged_coin_10,
  489. TEST_TARGET_CHAIN,
  490. TEST_TARGET_RECIPIENT,
  491. relayer_fee,
  492. TEST_NONCE,
  493. );
  494. coin::destroy_zero(dust);
  495. // Call `transfer_tokens`.
  496. let (
  497. nonce,
  498. payload
  499. ) =
  500. bridge_in_and_serialize_transfer_test_only(
  501. &mut token_bridge_state,
  502. ticket
  503. );
  504. assert!(nonce == TEST_NONCE, 0);
  505. // Construct expected payload from scratch and confirm that the
  506. // `transfer_tokens` call produces the same payload.
  507. let expected_amount =
  508. normalized_amount::from_raw(
  509. transfer_amount,
  510. TEST_COIN_NATIVE_10_DECIMALS
  511. );
  512. let expected_relayer_fee =
  513. normalized_amount::from_raw(
  514. relayer_fee,
  515. TEST_COIN_NATIVE_10_DECIMALS
  516. );
  517. let expected_payload =
  518. transfer::new_test_only(
  519. expected_amount,
  520. expected_token_address,
  521. chain_id(),
  522. external_address::new(
  523. bytes32::from_bytes(TEST_TARGET_RECIPIENT)
  524. ),
  525. TEST_TARGET_CHAIN,
  526. expected_relayer_fee
  527. );
  528. assert!(transfer::serialize_test_only(expected_payload) == payload, 0);
  529. // Clean up.
  530. return_state(token_bridge_state);
  531. // Done.
  532. test_scenario::end(my_scenario);
  533. }
  534. #[test]
  535. fun test_transfer_tokens_wrapped_7() {
  536. use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
  537. let sender = person();
  538. let my_scenario = test_scenario::begin(sender);
  539. let scenario = &mut my_scenario;
  540. // Set up contracts.
  541. let wormhole_fee = 350;
  542. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  543. // Register foreign emitter on chain ID == 2.
  544. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  545. // Register and mint coins.
  546. let transfer_amount = 42069000;
  547. let coin_7_balance =
  548. coin_wrapped_7::init_register_and_mint(
  549. scenario,
  550. sender,
  551. transfer_amount
  552. );
  553. // Ignore effects.
  554. test_scenario::next_tx(scenario, sender);
  555. // Fetch objects necessary for sending the transfer.
  556. let token_bridge_state = take_state(scenario);
  557. // Define the relayer fee.
  558. let relayer_fee = 100000;
  559. // Balance check the Token Bridge before executing the transfer. The
  560. // initial balance should be the `transfer_amount` for COIN_WRAPPED_7.
  561. {
  562. let registry = state::borrow_token_registry(&token_bridge_state);
  563. let asset =
  564. token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
  565. assert!(wrapped_asset::total_supply(asset) == transfer_amount, 0);
  566. };
  567. let asset_info = state::verified_asset(&token_bridge_state);
  568. let (
  569. ticket,
  570. dust
  571. ) =
  572. prepare_transfer(
  573. asset_info,
  574. coin::from_balance(
  575. coin_7_balance,
  576. test_scenario::ctx(scenario)
  577. ),
  578. TEST_TARGET_CHAIN,
  579. TEST_TARGET_RECIPIENT,
  580. relayer_fee,
  581. TEST_NONCE,
  582. );
  583. coin::destroy_zero(dust);
  584. // Call `transfer_tokens`.
  585. let prepared_msg =
  586. transfer_tokens(&mut token_bridge_state, ticket);
  587. // Balance check the Token Bridge after executing the transfer. The
  588. // balance should be zero, since tokens are burned when an outbound
  589. // wrapped token transfer occurs.
  590. {
  591. let registry = state::borrow_token_registry(&token_bridge_state);
  592. let asset = token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
  593. assert!(wrapped_asset::total_supply(asset) == 0, 0);
  594. };
  595. // Clean up.
  596. publish_message::destroy(prepared_msg);
  597. return_state(token_bridge_state);
  598. // Done.
  599. test_scenario::end(my_scenario);
  600. }
  601. #[test]
  602. fun test_serialize_transfer_tokens_wrapped_7() {
  603. use token_bridge::transfer_tokens::{
  604. bridge_in_and_serialize_transfer_test_only,
  605. prepare_transfer
  606. };
  607. let sender = person();
  608. let my_scenario = test_scenario::begin(sender);
  609. let scenario = &mut my_scenario;
  610. // Set up contracts.
  611. let wormhole_fee = 350;
  612. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  613. // Register foreign emitter on chain ID == 2.
  614. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  615. // Register and mint coins.
  616. let transfer_amount = 6942000;
  617. let bridged_coin_7 =
  618. coin::from_balance(
  619. coin_wrapped_7::init_register_and_mint(
  620. scenario,
  621. sender,
  622. transfer_amount
  623. ),
  624. test_scenario::ctx(scenario)
  625. );
  626. // Ignore effects.
  627. test_scenario::next_tx(scenario, sender);
  628. // Fetch objects necessary for sending the transfer.
  629. let token_bridge_state = take_state(scenario);
  630. // Define the relayer fee.
  631. let relayer_fee = 100000;
  632. let asset_info = state::verified_asset(&token_bridge_state);
  633. let expected_token_address = token_registry::token_address(&asset_info);
  634. let expected_token_chain = token_registry::token_chain(&asset_info);
  635. let (
  636. ticket,
  637. dust
  638. ) =
  639. prepare_transfer(
  640. asset_info,
  641. bridged_coin_7,
  642. TEST_TARGET_CHAIN,
  643. TEST_TARGET_RECIPIENT,
  644. relayer_fee,
  645. TEST_NONCE,
  646. );
  647. coin::destroy_zero(dust);
  648. // Call `transfer_tokens`.
  649. let (
  650. nonce,
  651. payload
  652. ) =
  653. bridge_in_and_serialize_transfer_test_only(
  654. &mut token_bridge_state,
  655. ticket
  656. );
  657. assert!(nonce == TEST_NONCE, 0);
  658. // Construct expected payload from scratch and confirm that the
  659. // `transfer_tokens` call produces the same payload.
  660. let expected_amount =
  661. normalized_amount::from_raw(
  662. transfer_amount,
  663. TEST_COIN_WRAPPED_7_DECIMALS
  664. );
  665. let expected_relayer_fee =
  666. normalized_amount::from_raw(
  667. relayer_fee,
  668. TEST_COIN_WRAPPED_7_DECIMALS
  669. );
  670. let expected_payload =
  671. transfer::new_test_only(
  672. expected_amount,
  673. expected_token_address,
  674. expected_token_chain,
  675. external_address::new(
  676. bytes32::from_bytes(TEST_TARGET_RECIPIENT)
  677. ),
  678. TEST_TARGET_CHAIN,
  679. expected_relayer_fee
  680. );
  681. assert!(transfer::serialize_test_only(expected_payload) == payload, 0);
  682. // Clean up.
  683. return_state(token_bridge_state);
  684. // Done.
  685. test_scenario::end(my_scenario);
  686. }
  687. #[test]
  688. #[expected_failure(abort_code = token_registry::E_UNREGISTERED)]
  689. fun test_cannot_transfer_tokens_native_not_registered() {
  690. use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
  691. let sender = person();
  692. let my_scenario = test_scenario::begin(sender);
  693. let scenario = &mut my_scenario;
  694. // Set up contracts.
  695. let wormhole_fee = 350;
  696. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  697. // Register foreign emitter on chain ID == 2.
  698. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  699. // Initialize COIN_NATIVE_10 (but don't register it).
  700. coin_native_10::init_test_only(test_scenario::ctx(scenario));
  701. // NOTE: This test purposely doesn't `attest` COIN_NATIVE_10.
  702. let transfer_amount = 6942000;
  703. let test_coins =
  704. coin::mint_for_testing<COIN_NATIVE_10>(
  705. transfer_amount,
  706. test_scenario::ctx(scenario)
  707. );
  708. // Ignore effects.
  709. test_scenario::next_tx(scenario, sender);
  710. // Fetch objects necessary for sending the transfer.
  711. let token_bridge_state = take_state(scenario);
  712. // Define the relayer fee.
  713. let relayer_fee = 100000;
  714. let asset_info = state::verified_asset(&token_bridge_state);
  715. let (
  716. ticket,
  717. dust
  718. ) =
  719. prepare_transfer(
  720. asset_info,
  721. test_coins,
  722. TEST_TARGET_CHAIN,
  723. TEST_TARGET_RECIPIENT,
  724. relayer_fee,
  725. TEST_NONCE,
  726. );
  727. coin::destroy_zero(dust);
  728. // You shall not pass!
  729. let prepared_msg =
  730. transfer_tokens(&mut token_bridge_state, ticket);
  731. // Clean up.
  732. publish_message::destroy(prepared_msg);
  733. abort 42
  734. }
  735. #[test]
  736. #[expected_failure(abort_code = token_registry::E_UNREGISTERED)]
  737. fun test_cannot_transfer_tokens_wrapped_not_registered() {
  738. use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
  739. let sender = person();
  740. let my_scenario = test_scenario::begin(sender);
  741. let scenario = &mut my_scenario;
  742. // Set up contracts.
  743. let wormhole_fee = 350;
  744. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  745. // Register foreign emitter on chain ID == 2.
  746. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  747. // Initialize COIN_WRAPPED_7 (but don't register it).
  748. coin_native_10::init_test_only(test_scenario::ctx(scenario));
  749. let treasury_cap =
  750. coin_wrapped_7::init_and_take_treasury_cap(
  751. scenario,
  752. sender
  753. );
  754. sui::test_utils::destroy(treasury_cap);
  755. // NOTE: This test purposely doesn't `attest` COIN_WRAPPED_7.
  756. let transfer_amount = 42069;
  757. let test_coins =
  758. coin::mint_for_testing<COIN_WRAPPED_7>(
  759. transfer_amount,
  760. test_scenario::ctx(scenario)
  761. );
  762. // Ignore effects.
  763. test_scenario::next_tx(scenario, sender);
  764. // Fetch objects necessary for sending the transfer.
  765. let token_bridge_state = take_state(scenario);
  766. // Define the relayer fee.
  767. let relayer_fee = 1000;
  768. let asset_info = state::verified_asset(&token_bridge_state);
  769. let (
  770. ticket,
  771. dust
  772. ) =
  773. prepare_transfer(
  774. asset_info,
  775. test_coins,
  776. TEST_TARGET_CHAIN,
  777. TEST_TARGET_RECIPIENT,
  778. relayer_fee,
  779. TEST_NONCE,
  780. );
  781. coin::destroy_zero(dust);
  782. // You shall not pass!
  783. let prepared_msg =
  784. transfer_tokens(&mut token_bridge_state, ticket);
  785. // Clean up.
  786. publish_message::destroy(prepared_msg);
  787. abort 42
  788. }
  789. #[test]
  790. #[expected_failure(
  791. abort_code = transfer_tokens::E_RELAYER_FEE_EXCEEDS_AMOUNT
  792. )]
  793. fun test_cannot_transfer_tokens_fee_exceeds_amount() {
  794. use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
  795. let sender = person();
  796. let my_scenario = test_scenario::begin(sender);
  797. let scenario = &mut my_scenario;
  798. // Set up contracts.
  799. let wormhole_fee = 350;
  800. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  801. // Register foreign emitter on chain ID == 2.
  802. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  803. // NOTE: The `relayer_fee` is intentionally set to a higher number
  804. // than the `transfer_amount`.
  805. let relayer_fee = 100001;
  806. let transfer_amount = 100000;
  807. let coin_10_balance =
  808. coin_native_10::init_register_and_mint(
  809. scenario,
  810. sender,
  811. transfer_amount
  812. );
  813. // Ignore effects.
  814. test_scenario::next_tx(scenario, sender);
  815. // Fetch objects necessary for sending the transfer.
  816. let token_bridge_state = take_state(scenario);
  817. let asset_info = state::verified_asset(&token_bridge_state);
  818. let (
  819. ticket,
  820. dust
  821. ) =
  822. prepare_transfer(
  823. asset_info,
  824. coin::from_balance(
  825. coin_10_balance,
  826. test_scenario::ctx(scenario)
  827. ),
  828. TEST_TARGET_CHAIN,
  829. TEST_TARGET_RECIPIENT,
  830. relayer_fee,
  831. TEST_NONCE,
  832. );
  833. coin::destroy_zero(dust);
  834. // You shall not pass!
  835. let prepared_msg =
  836. transfer_tokens(&mut token_bridge_state, ticket);
  837. // Done.
  838. publish_message::destroy(prepared_msg);
  839. abort 42
  840. }
  841. #[test]
  842. #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
  843. fun test_cannot_transfer_tokens_outdated_version() {
  844. use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens};
  845. let sender = person();
  846. let my_scenario = test_scenario::begin(sender);
  847. let scenario = &mut my_scenario;
  848. // Set up contracts.
  849. let wormhole_fee = 350;
  850. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  851. // Register foreign emitter on chain ID == 2.
  852. register_dummy_emitter(scenario, TEST_TARGET_CHAIN);
  853. // Register and mint coins.
  854. let transfer_amount = 6942000;
  855. let coin_10_balance =
  856. coin_native_10::init_register_and_mint(
  857. scenario,
  858. sender,
  859. transfer_amount
  860. );
  861. // Ignore effects.
  862. test_scenario::next_tx(scenario, sender);
  863. // Fetch objects necessary for sending the transfer.
  864. let token_bridge_state = take_state(scenario);
  865. let asset_info = state::verified_asset(&token_bridge_state);
  866. let relayer_fee = 0;
  867. let (
  868. ticket,
  869. dust
  870. ) =
  871. prepare_transfer(
  872. asset_info,
  873. coin::from_balance(
  874. coin_10_balance,
  875. test_scenario::ctx(scenario)
  876. ),
  877. TEST_TARGET_CHAIN,
  878. TEST_TARGET_RECIPIENT,
  879. relayer_fee,
  880. TEST_NONCE,
  881. );
  882. coin::destroy_zero(dust);
  883. // Conveniently roll version back.
  884. state::reverse_migrate_version(&mut token_bridge_state);
  885. // Simulate executing with an outdated build by upticking the minimum
  886. // required version for `publish_message` to something greater than
  887. // this build.
  888. state::migrate_version_test_only(
  889. &mut token_bridge_state,
  890. token_bridge::version_control::previous_version_test_only(),
  891. token_bridge::version_control::next_version()
  892. );
  893. // You shall not pass!
  894. let prepared_msg =
  895. transfer_tokens(&mut token_bridge_state, ticket);
  896. // Clean up.
  897. publish_message::destroy(prepared_msg);
  898. abort 42
  899. }
  900. }