complete_transfer.move 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228
  1. // SPDX-License-Identifier: Apache 2
  2. /// This module implements two methods: `authorize_transfer` and
  3. /// `redeem_relayer_payout`, which are to be executed in a transaction block in
  4. /// this order.
  5. ///
  6. /// `authorize_transfer` allows a contract to complete a Token Bridge transfer,
  7. /// sending assets to the encoded recipient. The coin payout incentive in
  8. /// redeeming the transfer is packaged in a `RelayerReceipt`.
  9. ///
  10. /// `redeem_relayer_payout` unpacks the `RelayerReceipt` to release the coin
  11. /// containing the relayer fee amount.
  12. ///
  13. /// The purpose of splitting this transfer redemption 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. /// `authorize_transfer` 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 `complete_transfer`.
  19. ///
  20. /// Instead, an integrator is encouraged to execute a transaction block, which
  21. /// executes `authorize_transfer` using the latest Token Bridge package ID and
  22. /// to implement `redeem_relayer_payout` in his contract to consume this receipt.
  23. /// This is similar to how an integrator with Wormhole is not meant to use
  24. /// `vaa::parse_and_verify` in his contract in case the `vaa` module needs to
  25. /// be upgraded due to a breaking change.
  26. ///
  27. /// See `transfer` module for serialization and deserialization of Wormhole
  28. /// message payload.
  29. module token_bridge::complete_transfer {
  30. use sui::balance::{Self, Balance};
  31. use sui::coin::{Self, Coin};
  32. use sui::tx_context::{Self, TxContext};
  33. use wormhole::external_address::{Self, ExternalAddress};
  34. use token_bridge::native_asset::{Self};
  35. use token_bridge::normalized_amount::{Self, NormalizedAmount};
  36. use token_bridge::state::{Self, State, LatestOnly};
  37. use token_bridge::token_registry::{Self, VerifiedAsset};
  38. use token_bridge::transfer::{Self};
  39. use token_bridge::vaa::{Self, TokenBridgeMessage};
  40. use token_bridge::wrapped_asset::{Self};
  41. // Requires `handle_complete_transfer`.
  42. friend token_bridge::complete_transfer_with_payload;
  43. /// Transfer not intended to be received on Sui.
  44. const E_TARGET_NOT_SUI: u64 = 0;
  45. /// Input token info does not match registered info.
  46. const E_CANONICAL_TOKEN_INFO_MISMATCH: u64 = 1;
  47. /// Event reflecting when a transfer via `complete_transfer` or
  48. /// `complete_transfer_with_payload` is successfully executed.
  49. struct TransferRedeemed has drop, copy {
  50. emitter_chain: u16,
  51. emitter_address: ExternalAddress,
  52. sequence: u64
  53. }
  54. #[allow(lint(coin_field))]
  55. /// This type is only generated from `authorize_transfer` and can only be
  56. /// redeemed using `redeem_relayer_payout`. Integrators running relayer
  57. /// contracts are expected to implement `redeem_relayer_payout` within their
  58. /// contracts and call `authorize_transfer` in a transaction block preceding
  59. /// the method that consumes this receipt.
  60. struct RelayerReceipt<phantom CoinType> {
  61. /// Coin of relayer fee payout.
  62. payout: Coin<CoinType>
  63. }
  64. /// `authorize_transfer` deserializes a token transfer VAA payload. Once the
  65. /// transfer is authorized, an event (`TransferRedeemed`) is emitted to
  66. /// reflect which Token Bridge this transfer originated from. The
  67. /// `RelayerReceipt` returned wraps a `Coin` object containing a payout that
  68. /// incentivizes someone to execute a transaction on behalf of the encoded
  69. /// recipient.
  70. ///
  71. /// NOTE: This method is guarded by a minimum build version check. This
  72. /// method could break backward compatibility on an upgrade.
  73. ///
  74. /// It is important for integrators to refrain from calling this method
  75. /// within their contracts. This method is meant to be called in a
  76. /// transaction block, passing the `RelayerReceipt` to a method which calls
  77. /// `redeem_relayer_payout` within a contract. If in a circumstance where
  78. /// this module has a breaking change in an upgrade, `redeem_relayer_payout`
  79. /// will not be affected by this change.
  80. ///
  81. /// See `redeem_relayer_payout` for more details.
  82. public fun authorize_transfer<CoinType>(
  83. token_bridge_state: &mut State,
  84. msg: TokenBridgeMessage,
  85. ctx: &mut TxContext
  86. ): RelayerReceipt<CoinType> {
  87. // This capability ensures that the current build version is used.
  88. let latest_only = state::assert_latest_only(token_bridge_state);
  89. // Emitting the transfer being redeemed (and disregard return value).
  90. emit_transfer_redeemed(&msg);
  91. // Deserialize transfer message and process.
  92. handle_complete_transfer<CoinType>(
  93. &latest_only,
  94. token_bridge_state,
  95. vaa::take_payload(msg),
  96. ctx
  97. )
  98. }
  99. /// After a transfer is authorized, a relayer contract may unpack the
  100. /// `RelayerReceipt` using this method. Coin representing the relaying
  101. /// incentive from this receipt is returned. This method is meant to be
  102. /// simple. It allows for a coordination with calling `authorize_upgrade`
  103. /// before a method that implements `redeem_relayer_payout` in a transaction
  104. /// block to consume this receipt.
  105. ///
  106. /// NOTE: Integrators of Token Bridge collecting relayer fee payouts from
  107. /// these token transfers should be calling only this method from their
  108. /// contracts. This method is not guarded by version control (thus not
  109. /// requiring a reference to the Token Bridge `State` object), so it is
  110. /// intended to work for any package version.
  111. public fun redeem_relayer_payout<CoinType>(
  112. receipt: RelayerReceipt<CoinType>
  113. ): Coin<CoinType> {
  114. let RelayerReceipt { payout } = receipt;
  115. payout
  116. }
  117. /// This is a privileged method only used by `complete_transfer` and
  118. /// `complete_transfer_with_payload` modules. This method validates the
  119. /// encoded token info with the passed in coin type via the `TokenRegistry`.
  120. /// The transfer amount is denormalized and either mints balance of
  121. /// wrapped asset or withdraws balance from native asset custody.
  122. ///
  123. /// Depending on whether this coin is a Token Bridge wrapped asset or a
  124. /// natively existing asset on Sui, the coin is either minted or withdrawn
  125. /// from Token Bridge's custody.
  126. public(friend) fun verify_and_bridge_out<CoinType>(
  127. latest_only: &LatestOnly,
  128. token_bridge_state: &mut State,
  129. token_chain: u16,
  130. token_address: ExternalAddress,
  131. target_chain: u16,
  132. amount: NormalizedAmount
  133. ): (
  134. VerifiedAsset<CoinType>,
  135. Balance<CoinType>
  136. ) {
  137. // Verify that the intended chain ID for this transfer is for Sui.
  138. assert!(
  139. target_chain == wormhole::state::chain_id(),
  140. E_TARGET_NOT_SUI
  141. );
  142. let asset_info = state::verified_asset<CoinType>(token_bridge_state);
  143. assert!(
  144. (
  145. token_chain == token_registry::token_chain(&asset_info) &&
  146. token_address == token_registry::token_address(&asset_info)
  147. ),
  148. E_CANONICAL_TOKEN_INFO_MISMATCH
  149. );
  150. // De-normalize amount in preparation to take `Balance`.
  151. let raw_amount =
  152. normalized_amount::to_raw(
  153. amount,
  154. token_registry::coin_decimals(&asset_info)
  155. );
  156. // If the token is wrapped by Token Bridge, we will mint these tokens.
  157. // Otherwise, we will withdraw from custody.
  158. let bridged_out = {
  159. let registry =
  160. state::borrow_mut_token_registry(
  161. latest_only,
  162. token_bridge_state
  163. );
  164. if (token_registry::is_wrapped(&asset_info)) {
  165. wrapped_asset::mint(
  166. token_registry::borrow_mut_wrapped(registry),
  167. raw_amount
  168. )
  169. } else {
  170. native_asset::withdraw(
  171. token_registry::borrow_mut_native(registry),
  172. raw_amount
  173. )
  174. }
  175. };
  176. (asset_info, bridged_out)
  177. }
  178. /// This method emits source information of the token transfer. Off-chain
  179. /// processes may want to observe when transfers have been redeemed.
  180. public(friend) fun emit_transfer_redeemed(msg: &TokenBridgeMessage): u16 {
  181. let emitter_chain = vaa::emitter_chain(msg);
  182. // Emit Sui event with `TransferRedeemed`.
  183. sui::event::emit(
  184. TransferRedeemed {
  185. emitter_chain,
  186. emitter_address: vaa::emitter_address(msg),
  187. sequence: vaa::sequence(msg)
  188. }
  189. );
  190. emitter_chain
  191. }
  192. fun handle_complete_transfer<CoinType>(
  193. latest_only: &LatestOnly,
  194. token_bridge_state: &mut State,
  195. transfer_vaa_payload: vector<u8>,
  196. ctx: &mut TxContext
  197. ): RelayerReceipt<CoinType> {
  198. let (
  199. amount,
  200. token_address,
  201. token_chain,
  202. recipient,
  203. recipient_chain,
  204. relayer_fee
  205. ) = transfer::unpack(transfer::deserialize(transfer_vaa_payload));
  206. let (
  207. asset_info,
  208. bridged_out
  209. ) =
  210. verify_and_bridge_out(
  211. latest_only,
  212. token_bridge_state,
  213. token_chain,
  214. token_address,
  215. recipient_chain,
  216. amount
  217. );
  218. let recipient = external_address::to_address(recipient);
  219. // If the recipient did not redeem his own transfer, Token Bridge will
  220. // split the withdrawn coins and send a portion to the transaction
  221. // relayer.
  222. let payout = if (
  223. normalized_amount::value(&relayer_fee) == 0 ||
  224. recipient == tx_context::sender(ctx)
  225. ) {
  226. balance::zero()
  227. } else {
  228. let payout_amount =
  229. normalized_amount::to_raw(
  230. relayer_fee,
  231. token_registry::coin_decimals(&asset_info)
  232. );
  233. balance::split(&mut bridged_out, payout_amount)
  234. };
  235. // Transfer tokens to the recipient.
  236. sui::transfer::public_transfer(
  237. coin::from_balance(bridged_out, ctx),
  238. recipient
  239. );
  240. // Finally produce the receipt that a relayer can consume via
  241. // `redeem_relayer_payout`.
  242. RelayerReceipt {
  243. payout: coin::from_balance(payout, ctx)
  244. }
  245. }
  246. #[test_only]
  247. public fun burn<CoinType>(receipt: RelayerReceipt<CoinType>) {
  248. coin::burn_for_testing(redeem_relayer_payout(receipt));
  249. }
  250. }
  251. #[test_only]
  252. module token_bridge::complete_transfer_tests {
  253. use sui::coin::{Self, Coin};
  254. use sui::test_scenario::{Self};
  255. use wormhole::state::{chain_id};
  256. use wormhole::wormhole_scenario::{parse_and_verify_vaa};
  257. use token_bridge::coin_wrapped_12::{Self, COIN_WRAPPED_12};
  258. use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7};
  259. use token_bridge::coin_native_10::{Self, COIN_NATIVE_10};
  260. use token_bridge::coin_native_4::{Self, COIN_NATIVE_4};
  261. use token_bridge::complete_transfer::{Self};
  262. use token_bridge::dummy_message::{Self};
  263. use token_bridge::native_asset::{Self};
  264. use token_bridge::state::{Self};
  265. use token_bridge::token_bridge_scenario::{
  266. set_up_wormhole_and_token_bridge,
  267. register_dummy_emitter,
  268. return_state,
  269. take_state,
  270. three_people,
  271. two_people
  272. };
  273. use token_bridge::token_registry::{Self};
  274. use token_bridge::transfer::{Self};
  275. use token_bridge::vaa::{Self};
  276. use token_bridge::wrapped_asset::{Self};
  277. struct OTHER_COIN_WITNESS has drop {}
  278. #[test]
  279. /// An end-to-end test for complete transfer native with VAA.
  280. fun test_complete_transfer_native_10_relayer_fee() {
  281. use token_bridge::complete_transfer::{
  282. authorize_transfer,
  283. redeem_relayer_payout
  284. };
  285. let transfer_vaa =
  286. dummy_message::encoded_transfer_vaa_native_with_fee();
  287. let (expected_recipient, tx_relayer, coin_deployer) = three_people();
  288. let my_scenario = test_scenario::begin(tx_relayer);
  289. let scenario = &mut my_scenario;
  290. // Set up contracts.
  291. let wormhole_fee = 350;
  292. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  293. // Register foreign emitter on chain ID == 2.
  294. let expected_source_chain = 2;
  295. register_dummy_emitter(scenario, expected_source_chain);
  296. let custody_amount = 500000;
  297. coin_native_10::init_register_and_deposit(
  298. scenario,
  299. coin_deployer,
  300. custody_amount
  301. );
  302. // Ignore effects.
  303. test_scenario::next_tx(scenario, tx_relayer);
  304. let token_bridge_state = take_state(scenario);
  305. // These will be checked later.
  306. let expected_relayer_fee = 100000;
  307. let expected_recipient_amount = 200000;
  308. let expected_amount = expected_relayer_fee + expected_recipient_amount;
  309. // Scope to allow immutable reference to `TokenRegistry`.
  310. {
  311. let registry = state::borrow_token_registry(&token_bridge_state);
  312. let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
  313. assert!(native_asset::custody(asset) == custody_amount, 0);
  314. // Verify transfer parameters.
  315. let parsed =
  316. transfer::deserialize_test_only(
  317. wormhole::vaa::take_payload(
  318. parse_and_verify_vaa(scenario, transfer_vaa)
  319. )
  320. );
  321. let asset_info =
  322. token_registry::verified_asset<COIN_NATIVE_10>(registry);
  323. let expected_token_chain = token_registry::token_chain(&asset_info);
  324. let expected_token_address =
  325. token_registry::token_address(&asset_info);
  326. assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
  327. assert!(
  328. transfer::token_address(&parsed) == expected_token_address,
  329. 0
  330. );
  331. let coin_meta = test_scenario::take_shared(scenario);
  332. let decimals = coin::get_decimals<COIN_NATIVE_10>(&coin_meta);
  333. test_scenario::return_shared(coin_meta);
  334. assert!(
  335. transfer::raw_amount(&parsed, decimals) == expected_amount,
  336. 0
  337. );
  338. assert!(
  339. transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee,
  340. 0
  341. );
  342. assert!(
  343. transfer::recipient_as_address(&parsed) == expected_recipient,
  344. 0
  345. );
  346. assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
  347. // Clean up.
  348. transfer::destroy(parsed);
  349. };
  350. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  351. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  352. // Ignore effects.
  353. test_scenario::next_tx(scenario, tx_relayer);
  354. let receipt =
  355. authorize_transfer<COIN_NATIVE_10>(
  356. &mut token_bridge_state,
  357. msg,
  358. test_scenario::ctx(scenario)
  359. );
  360. let payout = redeem_relayer_payout(receipt);
  361. assert!(coin::value(&payout) == expected_relayer_fee, 0);
  362. // TODO: Check for one event? `TransferRedeemed`.
  363. let _effects = test_scenario::next_tx(scenario, tx_relayer);
  364. // Check recipient's `Coin`.
  365. let received =
  366. test_scenario::take_from_address<Coin<COIN_NATIVE_10>>(
  367. scenario,
  368. expected_recipient
  369. );
  370. assert!(coin::value(&received) == expected_recipient_amount, 0);
  371. // And check remaining amount in custody.
  372. let registry = state::borrow_token_registry(&token_bridge_state);
  373. let remaining = custody_amount - expected_amount;
  374. {
  375. let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
  376. assert!(native_asset::custody(asset) == remaining, 0);
  377. };
  378. // Clean up.
  379. coin::burn_for_testing(payout);
  380. coin::burn_for_testing(received);
  381. return_state(token_bridge_state);
  382. // Done.
  383. test_scenario::end(my_scenario);
  384. }
  385. #[test]
  386. /// An end-to-end test for complete transfer native with VAA.
  387. fun test_complete_transfer_native_4_relayer_fee() {
  388. use token_bridge::complete_transfer::{
  389. authorize_transfer,
  390. redeem_relayer_payout
  391. };
  392. let transfer_vaa =
  393. dummy_message::encoded_transfer_vaa_native_with_fee();
  394. let (expected_recipient, tx_relayer, coin_deployer) = three_people();
  395. let my_scenario = test_scenario::begin(tx_relayer);
  396. let scenario = &mut my_scenario;
  397. // Set up contracts.
  398. let wormhole_fee = 350;
  399. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  400. // Register foreign emitter on chain ID == 2.
  401. let expected_source_chain = 2;
  402. register_dummy_emitter(scenario, expected_source_chain);
  403. let custody_amount = 5000;
  404. coin_native_4::init_register_and_deposit(
  405. scenario,
  406. coin_deployer,
  407. custody_amount
  408. );
  409. // Ignore effects.
  410. test_scenario::next_tx(scenario, tx_relayer);
  411. let token_bridge_state = take_state(scenario);
  412. // These will be checked later.
  413. let expected_relayer_fee = 1000;
  414. let expected_recipient_amount = 2000;
  415. let expected_amount = expected_relayer_fee + expected_recipient_amount;
  416. // Scope to allow immutable reference to `TokenRegistry`.
  417. {
  418. let registry = state::borrow_token_registry(&token_bridge_state);
  419. let asset = token_registry::borrow_native<COIN_NATIVE_4>(registry);
  420. assert!(native_asset::custody(asset) == custody_amount, 0);
  421. // Verify transfer parameters.
  422. let parsed =
  423. transfer::deserialize_test_only(
  424. wormhole::vaa::take_payload(
  425. parse_and_verify_vaa(scenario, transfer_vaa)
  426. )
  427. );
  428. let asset_info =
  429. token_registry::verified_asset<COIN_NATIVE_4>(registry);
  430. let expected_token_chain = token_registry::token_chain(&asset_info);
  431. let expected_token_address =
  432. token_registry::token_address(&asset_info);
  433. assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
  434. assert!(
  435. transfer::token_address(&parsed) == expected_token_address,
  436. 0
  437. );
  438. let coin_meta = test_scenario::take_shared(scenario);
  439. let decimals = coin::get_decimals<COIN_NATIVE_4>(&coin_meta);
  440. test_scenario::return_shared(coin_meta);
  441. assert!(
  442. transfer::raw_amount(&parsed, decimals) == expected_amount,
  443. 0
  444. );
  445. assert!(
  446. transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee,
  447. 0
  448. );
  449. assert!(
  450. transfer::recipient_as_address(&parsed) == expected_recipient,
  451. 0
  452. );
  453. assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
  454. // Clean up.
  455. transfer::destroy(parsed);
  456. };
  457. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  458. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  459. // Ignore effects.
  460. test_scenario::next_tx(scenario, tx_relayer);
  461. let receipt =
  462. authorize_transfer<COIN_NATIVE_4>(
  463. &mut token_bridge_state,
  464. msg,
  465. test_scenario::ctx(scenario)
  466. );
  467. let payout = redeem_relayer_payout(receipt);
  468. assert!(coin::value(&payout) == expected_relayer_fee, 0);
  469. // TODO: Check for one event? `TransferRedeemed`.
  470. let _effects = test_scenario::next_tx(scenario, tx_relayer);
  471. // Check recipient's `Coin`.
  472. let received =
  473. test_scenario::take_from_address<Coin<COIN_NATIVE_4>>(
  474. scenario,
  475. expected_recipient
  476. );
  477. assert!(coin::value(&received) == expected_recipient_amount, 0);
  478. // And check remaining amount in custody.
  479. let registry = state::borrow_token_registry(&token_bridge_state);
  480. let remaining = custody_amount - expected_amount;
  481. {
  482. let asset = token_registry::borrow_native<COIN_NATIVE_4>(registry);
  483. assert!(native_asset::custody(asset) == remaining, 0);
  484. };
  485. // Clean up.
  486. coin::burn_for_testing(payout);
  487. coin::burn_for_testing(received);
  488. return_state(token_bridge_state);
  489. // Done.
  490. test_scenario::end(my_scenario);
  491. }
  492. #[test]
  493. /// An end-to-end test for complete transfer wrapped with VAA.
  494. fun test_complete_transfer_wrapped_7_relayer_fee() {
  495. use token_bridge::complete_transfer::{
  496. authorize_transfer,
  497. redeem_relayer_payout
  498. };
  499. let transfer_vaa =
  500. dummy_message::encoded_transfer_vaa_wrapped_7_with_fee();
  501. let (expected_recipient, tx_relayer, coin_deployer) = three_people();
  502. let my_scenario = test_scenario::begin(tx_relayer);
  503. let scenario = &mut my_scenario;
  504. // Set up contracts.
  505. let wormhole_fee = 350;
  506. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  507. // Register foreign emitter on chain ID == 2.
  508. let expected_source_chain = 2;
  509. register_dummy_emitter(scenario, expected_source_chain);
  510. coin_wrapped_7::init_and_register(scenario, coin_deployer);
  511. // Ignore effects.
  512. test_scenario::next_tx(scenario, tx_relayer);
  513. let token_bridge_state = take_state(scenario);
  514. // These will be checked later.
  515. let expected_relayer_fee = 1000;
  516. let expected_recipient_amount = 2000;
  517. let expected_amount = expected_relayer_fee + expected_recipient_amount;
  518. // Scope to allow immutable reference to `TokenRegistry`.
  519. {
  520. let registry = state::borrow_token_registry(&token_bridge_state);
  521. let asset =
  522. token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
  523. assert!(wrapped_asset::total_supply(asset) == 0, 0);
  524. // Verify transfer parameters.
  525. let parsed =
  526. transfer::deserialize_test_only(
  527. wormhole::vaa::take_payload(
  528. parse_and_verify_vaa(scenario, transfer_vaa)
  529. )
  530. );
  531. let asset_info =
  532. token_registry::verified_asset<COIN_WRAPPED_7>(registry);
  533. let expected_token_chain = token_registry::token_chain(&asset_info);
  534. let expected_token_address =
  535. token_registry::token_address(&asset_info);
  536. assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
  537. assert!(
  538. transfer::token_address(&parsed) == expected_token_address,
  539. 0
  540. );
  541. let coin_meta = test_scenario::take_shared(scenario);
  542. let decimals = coin::get_decimals<COIN_WRAPPED_7>(&coin_meta);
  543. test_scenario::return_shared(coin_meta);
  544. assert!(
  545. transfer::raw_amount(&parsed, decimals) == expected_amount,
  546. 0
  547. );
  548. assert!(
  549. transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee,
  550. 0
  551. );
  552. assert!(
  553. transfer::recipient_as_address(&parsed) == expected_recipient,
  554. 0
  555. );
  556. assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
  557. // Clean up.
  558. transfer::destroy(parsed);
  559. };
  560. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  561. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  562. // Ignore effects.
  563. test_scenario::next_tx(scenario, tx_relayer);
  564. let receipt =
  565. authorize_transfer<COIN_WRAPPED_7>(
  566. &mut token_bridge_state,
  567. msg,
  568. test_scenario::ctx(scenario)
  569. );
  570. let payout = redeem_relayer_payout(receipt);
  571. assert!(coin::value(&payout) == expected_relayer_fee, 0);
  572. // TODO: Check for one event? `TransferRedeemed`.
  573. let _effects = test_scenario::next_tx(scenario, tx_relayer);
  574. // Check recipient's `Coin`.
  575. let received =
  576. test_scenario::take_from_address<Coin<COIN_WRAPPED_7>>(
  577. scenario,
  578. expected_recipient
  579. );
  580. assert!(coin::value(&received) == expected_recipient_amount, 0);
  581. // And check that the amount is the total wrapped supply.
  582. let registry = state::borrow_token_registry(&token_bridge_state);
  583. {
  584. let asset =
  585. token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
  586. assert!(wrapped_asset::total_supply(asset) == expected_amount, 0);
  587. };
  588. // Clean up.
  589. coin::burn_for_testing(payout);
  590. coin::burn_for_testing(received);
  591. return_state(token_bridge_state);
  592. // Done.
  593. test_scenario::end(my_scenario);
  594. }
  595. #[test]
  596. /// An end-to-end test for complete transfer wrapped with VAA.
  597. fun test_complete_transfer_wrapped_12_relayer_fee() {
  598. use token_bridge::complete_transfer::{
  599. authorize_transfer,
  600. redeem_relayer_payout
  601. };
  602. let transfer_vaa =
  603. dummy_message::encoded_transfer_vaa_wrapped_12_with_fee();
  604. let (expected_recipient, tx_relayer, coin_deployer) = three_people();
  605. let my_scenario = test_scenario::begin(tx_relayer);
  606. let scenario = &mut my_scenario;
  607. // Set up contracts.
  608. let wormhole_fee = 350;
  609. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  610. // Register foreign emitter on chain ID == 2.
  611. let expected_source_chain = 2;
  612. register_dummy_emitter(scenario, expected_source_chain);
  613. coin_wrapped_12::init_and_register(scenario, coin_deployer);
  614. // Ignore effects.
  615. //
  616. // NOTE: `tx_relayer` != `expected_recipient`.
  617. assert!(expected_recipient != tx_relayer, 0);
  618. test_scenario::next_tx(scenario, tx_relayer);
  619. let token_bridge_state = take_state(scenario);
  620. // These will be checked later.
  621. let expected_relayer_fee = 1000;
  622. let expected_recipient_amount = 2000;
  623. let expected_amount = expected_relayer_fee + expected_recipient_amount;
  624. // Scope to allow immutable reference to `TokenRegistry`.
  625. {
  626. let registry = state::borrow_token_registry(&token_bridge_state);
  627. let asset =
  628. token_registry::borrow_wrapped<COIN_WRAPPED_12>(registry);
  629. assert!(wrapped_asset::total_supply(asset) == 0, 0);
  630. // Verify transfer parameters.
  631. let parsed =
  632. transfer::deserialize_test_only(
  633. wormhole::vaa::take_payload(
  634. parse_and_verify_vaa(scenario, transfer_vaa)
  635. )
  636. );
  637. let asset_info =
  638. token_registry::verified_asset<COIN_WRAPPED_12>(registry);
  639. let expected_token_chain = token_registry::token_chain(&asset_info);
  640. let expected_token_address =
  641. token_registry::token_address(&asset_info);
  642. assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
  643. assert!(transfer::token_address(&parsed) == expected_token_address, 0);
  644. let coin_meta = test_scenario::take_shared(scenario);
  645. let decimals = coin::get_decimals<COIN_WRAPPED_12>(&coin_meta);
  646. test_scenario::return_shared(coin_meta);
  647. assert!(transfer::raw_amount(&parsed, decimals) == expected_amount, 0);
  648. assert!(
  649. transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee,
  650. 0
  651. );
  652. assert!(
  653. transfer::recipient_as_address(&parsed) == expected_recipient,
  654. 0
  655. );
  656. assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
  657. // Clean up.
  658. transfer::destroy(parsed);
  659. };
  660. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  661. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  662. // Ignore effects.
  663. test_scenario::next_tx(scenario, tx_relayer);
  664. let receipt =
  665. authorize_transfer<COIN_WRAPPED_12>(
  666. &mut token_bridge_state,
  667. msg,
  668. test_scenario::ctx(scenario)
  669. );
  670. let payout = redeem_relayer_payout(receipt);
  671. assert!(coin::value(&payout) == expected_relayer_fee, 0);
  672. // TODO: Check for one event? `TransferRedeemed`.
  673. let _effects = test_scenario::next_tx(scenario, tx_relayer);
  674. // Check recipient's `Coin`.
  675. let received =
  676. test_scenario::take_from_address<Coin<COIN_WRAPPED_12>>(
  677. scenario,
  678. expected_recipient
  679. );
  680. assert!(coin::value(&received) == expected_recipient_amount, 0);
  681. // And check that the amount is the total wrapped supply.
  682. let registry = state::borrow_token_registry(&token_bridge_state);
  683. {
  684. let asset = token_registry::borrow_wrapped<COIN_WRAPPED_12>(registry);
  685. assert!(wrapped_asset::total_supply(asset) == expected_amount, 0);
  686. };
  687. // Clean up.
  688. coin::burn_for_testing(payout);
  689. coin::burn_for_testing(received);
  690. return_state(token_bridge_state);
  691. // Done.
  692. test_scenario::end(my_scenario);
  693. }
  694. #[test]
  695. /// An end-to-end test for complete transfer native with VAA. The encoded VAA
  696. /// specifies a nonzero fee, however the `recipient` should receive the full
  697. /// amount for self redeeming the transfer.
  698. fun test_complete_transfer_native_10_relayer_fee_self_redemption() {
  699. use token_bridge::complete_transfer::{
  700. authorize_transfer,
  701. redeem_relayer_payout
  702. };
  703. let transfer_vaa =
  704. dummy_message::encoded_transfer_vaa_native_with_fee();
  705. let (expected_recipient, _, coin_deployer) = three_people();
  706. let my_scenario = test_scenario::begin(expected_recipient);
  707. let scenario = &mut my_scenario;
  708. // Set up contracts.
  709. let wormhole_fee = 350;
  710. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  711. // Register foreign emitter on chain ID == 2.
  712. let expected_source_chain = 2;
  713. register_dummy_emitter(scenario, expected_source_chain);
  714. let custody_amount = 500000;
  715. coin_native_10::init_register_and_deposit(
  716. scenario,
  717. coin_deployer,
  718. custody_amount
  719. );
  720. // Ignore effects.
  721. test_scenario::next_tx(scenario, expected_recipient);
  722. let token_bridge_state = take_state(scenario);
  723. // NOTE: Although there is a fee encoded in the VAA, the relayer
  724. // shouldn't receive this fee. The `expected_relayer_fee` should
  725. // go to the recipient.
  726. //
  727. // These values will be used later.
  728. let expected_relayer_fee = 0;
  729. let encoded_relayer_fee = 100000;
  730. let expected_recipient_amount = 300000;
  731. let expected_amount = expected_relayer_fee + expected_recipient_amount;
  732. // Scope to allow immutable reference to `TokenRegistry`.
  733. {
  734. let registry = state::borrow_token_registry(&token_bridge_state);
  735. let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
  736. assert!(native_asset::custody(asset) == custody_amount, 0);
  737. // Verify transfer parameters.
  738. let parsed =
  739. transfer::deserialize_test_only(
  740. wormhole::vaa::take_payload(
  741. parse_and_verify_vaa(scenario, transfer_vaa)
  742. )
  743. );
  744. let asset_info =
  745. token_registry::verified_asset<COIN_NATIVE_10>(registry);
  746. let expected_token_chain = token_registry::token_chain(&asset_info);
  747. let expected_token_address =
  748. token_registry::token_address(&asset_info);
  749. assert!(transfer::token_chain(&parsed) == expected_token_chain, 0);
  750. assert!(transfer::token_address(&parsed) == expected_token_address, 0);
  751. let coin_meta = test_scenario::take_shared(scenario);
  752. let decimals = coin::get_decimals<COIN_NATIVE_10>(&coin_meta);
  753. test_scenario::return_shared(coin_meta);
  754. assert!(transfer::raw_amount(&parsed, decimals) == expected_amount, 0);
  755. assert!(
  756. transfer::raw_relayer_fee(&parsed, decimals) == encoded_relayer_fee,
  757. 0
  758. );
  759. assert!(
  760. transfer::recipient_as_address(&parsed) == expected_recipient,
  761. 0
  762. );
  763. assert!(transfer::recipient_chain(&parsed) == chain_id(), 0);
  764. // Clean up.
  765. transfer::destroy(parsed);
  766. };
  767. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  768. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  769. // Ignore effects.
  770. test_scenario::next_tx(scenario, expected_recipient);
  771. let receipt =
  772. authorize_transfer<COIN_NATIVE_10>(
  773. &mut token_bridge_state,
  774. msg,
  775. test_scenario::ctx(scenario)
  776. );
  777. let payout = redeem_relayer_payout(receipt);
  778. assert!(coin::value(&payout) == expected_relayer_fee, 0);
  779. // TODO: Check for one event? `TransferRedeemed`.
  780. let _effects = test_scenario::next_tx(scenario, expected_recipient);
  781. // Check recipient's `Coin`.
  782. let received =
  783. test_scenario::take_from_address<Coin<COIN_NATIVE_10>>(
  784. scenario,
  785. expected_recipient
  786. );
  787. assert!(coin::value(&received) == expected_recipient_amount, 0);
  788. // And check remaining amount in custody.
  789. let registry = state::borrow_token_registry(&token_bridge_state);
  790. let remaining = custody_amount - expected_amount;
  791. {
  792. let asset = token_registry::borrow_native<COIN_NATIVE_10>(registry);
  793. assert!(native_asset::custody(asset) == remaining, 0);
  794. };
  795. // Clean up.
  796. coin::burn_for_testing(payout);
  797. coin::burn_for_testing(received);
  798. return_state(token_bridge_state);
  799. // Done.
  800. test_scenario::end(my_scenario);
  801. }
  802. #[test]
  803. #[expected_failure(
  804. abort_code = complete_transfer::E_CANONICAL_TOKEN_INFO_MISMATCH
  805. )]
  806. /// This test verifies that `authorize_transfer` reverts when called with
  807. /// a native COIN_TYPE that's not encoded in the VAA.
  808. fun test_cannot_authorize_transfer_native_invalid_coin_type() {
  809. use token_bridge::complete_transfer::{authorize_transfer};
  810. let transfer_vaa =
  811. dummy_message::encoded_transfer_vaa_native_with_fee();
  812. let (_, tx_relayer, coin_deployer) = three_people();
  813. let my_scenario = test_scenario::begin(tx_relayer);
  814. let scenario = &mut my_scenario;
  815. // Set up contracts.
  816. let wormhole_fee = 350;
  817. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  818. // Register foreign emitter on chain ID == 2.
  819. let expected_source_chain = 2;
  820. register_dummy_emitter(scenario, expected_source_chain);
  821. let custody_amount_coin_10 = 500000;
  822. coin_native_10::init_register_and_deposit(
  823. scenario,
  824. coin_deployer,
  825. custody_amount_coin_10
  826. );
  827. // Register a second native asset.
  828. let custody_amount_coin_4 = 69420;
  829. coin_native_4::init_register_and_deposit(
  830. scenario,
  831. coin_deployer,
  832. custody_amount_coin_4
  833. );
  834. // Ignore effects.
  835. test_scenario::next_tx(scenario, tx_relayer);
  836. let token_bridge_state = take_state(scenario);
  837. // Scope to allow immutable reference to `TokenRegistry`. This verifies
  838. // that both coin types have been registered.
  839. {
  840. let registry = state::borrow_token_registry(&token_bridge_state);
  841. // COIN_10.
  842. let coin_10 =
  843. token_registry::borrow_native<COIN_NATIVE_10>(registry);
  844. assert!(
  845. native_asset::custody(coin_10) == custody_amount_coin_10,
  846. 0
  847. );
  848. // COIN_4.
  849. let coin_4 = token_registry::borrow_native<COIN_NATIVE_4>(registry);
  850. assert!(native_asset::custody(coin_4) == custody_amount_coin_4, 0);
  851. };
  852. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  853. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  854. // Ignore effects.
  855. test_scenario::next_tx(scenario, tx_relayer);
  856. // NOTE: this call should revert since the transfer VAA is for
  857. // a coin of type COIN_NATIVE_10. However, the `complete_transfer`
  858. // method is called using the COIN_NATIVE_4 type.
  859. let receipt =
  860. authorize_transfer<COIN_NATIVE_4>(
  861. &mut token_bridge_state,
  862. msg,
  863. test_scenario::ctx(scenario)
  864. );
  865. // Clean up.
  866. complete_transfer::burn(receipt);
  867. abort 42
  868. }
  869. #[test]
  870. #[expected_failure(
  871. abort_code = complete_transfer::E_CANONICAL_TOKEN_INFO_MISMATCH
  872. )]
  873. /// This test verifies that `authorize_transfer` reverts when called with
  874. /// a wrapped COIN_TYPE that's not encoded in the VAA.
  875. fun test_cannot_authorize_transfer_wrapped_invalid_coin_type() {
  876. use token_bridge::complete_transfer::{authorize_transfer};
  877. let transfer_vaa = dummy_message::encoded_transfer_vaa_wrapped_12_with_fee();
  878. let (expected_recipient, tx_relayer, coin_deployer) = three_people();
  879. let my_scenario = test_scenario::begin(tx_relayer);
  880. let scenario = &mut my_scenario;
  881. // Set up contracts.
  882. let wormhole_fee = 350;
  883. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  884. // Register foreign emitter on chain ID == 2.
  885. let expected_source_chain = 2;
  886. register_dummy_emitter(scenario, expected_source_chain);
  887. // Register both wrapped coin types (12 and 7).
  888. coin_wrapped_12::init_and_register(scenario, coin_deployer);
  889. coin_wrapped_7::init_and_register(scenario, coin_deployer);
  890. // Ignore effects.
  891. test_scenario::next_tx(scenario, tx_relayer);
  892. // NOTE: `tx_relayer` != `expected_recipient`.
  893. assert!(expected_recipient != tx_relayer, 0);
  894. let token_bridge_state = take_state(scenario);
  895. // Scope to allow immutable reference to `TokenRegistry`. This verifies
  896. // that both coin types have been registered.
  897. {
  898. let registry = state::borrow_token_registry(&token_bridge_state);
  899. let coin_12 =
  900. token_registry::borrow_wrapped<COIN_WRAPPED_12>(registry);
  901. assert!(wrapped_asset::total_supply(coin_12) == 0, 0);
  902. let coin_7 =
  903. token_registry::borrow_wrapped<COIN_WRAPPED_7>(registry);
  904. assert!(wrapped_asset::total_supply(coin_7) == 0, 0);
  905. };
  906. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  907. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  908. // Ignore effects.
  909. test_scenario::next_tx(scenario, tx_relayer);
  910. // NOTE: this call should revert since the transfer VAA is for
  911. // a coin of type COIN_WRAPPED_12. However, the `authorize_transfer`
  912. // method is called using the COIN_WRAPPED_7 type.
  913. let receipt =
  914. authorize_transfer<COIN_WRAPPED_7>(
  915. &mut token_bridge_state,
  916. msg,
  917. test_scenario::ctx(scenario)
  918. );
  919. // Clean up.
  920. complete_transfer::burn(receipt);
  921. abort 42
  922. }
  923. #[test]
  924. #[expected_failure(abort_code = complete_transfer::E_TARGET_NOT_SUI)]
  925. /// This test verifies that `authorize_transfer` reverts when a transfer is
  926. /// sent to the wrong target blockchain (chain ID != 21).
  927. fun test_cannot_authorize_transfer_wrapped_12_invalid_target_chain() {
  928. use token_bridge::complete_transfer::{authorize_transfer};
  929. let transfer_vaa =
  930. dummy_message::encoded_transfer_vaa_wrapped_12_invalid_target_chain();
  931. let (expected_recipient, tx_relayer, coin_deployer) = three_people();
  932. let my_scenario = test_scenario::begin(tx_relayer);
  933. let scenario = &mut my_scenario;
  934. // Set up contracts.
  935. let wormhole_fee = 350;
  936. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  937. // Register foreign emitter on chain ID == 2.
  938. let expected_source_chain = 2;
  939. register_dummy_emitter(scenario, expected_source_chain);
  940. coin_wrapped_12::init_and_register(scenario, coin_deployer);
  941. // Ignore effects.
  942. //
  943. // NOTE: `tx_relayer` != `expected_recipient`.
  944. assert!(expected_recipient != tx_relayer, 0);
  945. // Ignore effects.
  946. test_scenario::next_tx(scenario, tx_relayer);
  947. let token_bridge_state = take_state(scenario);
  948. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  949. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  950. // Ignore effects.
  951. test_scenario::next_tx(scenario, tx_relayer);
  952. // NOTE: this call should revert since the target chain encoded is
  953. // chain 69 instead of chain 21 (Sui).
  954. let receipt =
  955. authorize_transfer<COIN_WRAPPED_12>(
  956. &mut token_bridge_state,
  957. msg,
  958. test_scenario::ctx(scenario)
  959. );
  960. // Clean up.
  961. complete_transfer::burn(receipt);
  962. abort 42
  963. }
  964. #[test]
  965. #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)]
  966. fun test_cannot_complete_transfer_outdated_version() {
  967. use token_bridge::complete_transfer::{authorize_transfer};
  968. let transfer_vaa =
  969. dummy_message::encoded_transfer_vaa_native_with_fee();
  970. let (tx_relayer, coin_deployer) = two_people();
  971. let my_scenario = test_scenario::begin(tx_relayer);
  972. let scenario = &mut my_scenario;
  973. // Set up contracts.
  974. let wormhole_fee = 350;
  975. set_up_wormhole_and_token_bridge(scenario, wormhole_fee);
  976. // Register foreign emitter on chain ID == 2.
  977. let expected_source_chain = 2;
  978. register_dummy_emitter(scenario, expected_source_chain);
  979. let custody_amount = 500000;
  980. coin_native_10::init_register_and_deposit(
  981. scenario,
  982. coin_deployer,
  983. custody_amount
  984. );
  985. // Ignore effects.
  986. test_scenario::next_tx(scenario, tx_relayer);
  987. let token_bridge_state = take_state(scenario);
  988. let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa);
  989. let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa);
  990. // Ignore effects.
  991. test_scenario::next_tx(scenario, tx_relayer);
  992. // Conveniently roll version back.
  993. state::reverse_migrate_version(&mut token_bridge_state);
  994. // Simulate executing with an outdated build by upticking the minimum
  995. // required version for `publish_message` to something greater than
  996. // this build.
  997. state::migrate_version_test_only(
  998. &mut token_bridge_state,
  999. token_bridge::version_control::previous_version_test_only(),
  1000. token_bridge::version_control::next_version()
  1001. );
  1002. // You shall not pass!
  1003. let receipt =
  1004. authorize_transfer<COIN_NATIVE_10>(
  1005. &mut token_bridge_state,
  1006. msg,
  1007. test_scenario::ctx(scenario)
  1008. );
  1009. // Clean up.
  1010. complete_transfer::burn(receipt);
  1011. abort 42
  1012. }
  1013. }