Skip to content

Instantly share code, notes, and snippets.

@lrsaturnino
Created April 24, 2025 20:15
Show Gist options
  • Save lrsaturnino/9a5df9c3fc2a21a2ffacd17dd5c291b6 to your computer and use it in GitHub Desktop.
Save lrsaturnino/9a5df9c3fc2a21a2ffacd17dd5c291b6 to your computer and use it in GitHub Desktop.
This file is a merged representation of the entire codebase, combined into a single document by Repomix.
The content has been processed where line numbers have been added, content has been formatted for parsing in plain style, security check has been disabled.
================================================================
File Summary
================================================================
Purpose:
--------
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.
File Format:
------------
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Multiple file entries, each consisting of:
a. A separator line (================)
b. The file path (File: path/to/file)
c. Another separator line
d. The full contents of the file
e. A blank line
Usage Guidelines:
-----------------
- This file should be treated as read-only. Any changes should be made to the
original repository files, not this packed version.
- When processing this file, use the file path to distinguish
between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
the same level of security as you would the original repository.
Notes:
------
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded
- Line numbers have been added to the beginning of each line
- Content has been formatted for parsing in plain style
- Security check has been disabled - content may contain sensitive information
- Files are sorted by Git change count (files with more changes are at the bottom)
Additional Info:
----------------
================================================================
Directory Structure
================================================================
docs/
bitcoin_depositor.md
gateway.md
tbtc.md
sources/
bitcoin_depositor/
bitcoin_depositor.move
gateway/
helpers.move
wormhole_gateway.move
token/
tbtc.move
tests/
bitcoin_depositor_tests.move
gateway_tests.move
tbtc_tests.move
utils.move
.gitattributes
Move.devnet.toml
Move.lock
Move.testnet.toml
Move.toml
README.md
================================================================
Files
================================================================
================
File: docs/bitcoin_depositor.md
================
1: # L2 TBTC Bitcoin Depositor Contract Documentation
2:
3: ## Overview
4:
5: The BitcoinDepositor contract is a cross-chain messaging and token reception module designed to handle Bitcoin deposit interactions on the Sui blockchain. It works in conjunction with the Gateway contract to facilitate secure token transfers and message processing from Ethereum (Layer 1) to Sui.
6:
7: ## Key Constants
8:
9: - `EMITTER_CHAIN_L1`: Identifies the Ethereum blockchain (Chain ID 2)
10: - `INVALID_CHAIN_ID`: Error code for invalid chain identification
11: - `INVALID_SENDER`: Error code for unauthorized message sender
12: - `MESSAGE_ALREADY_PROCESSED`: Error code for replay attack prevention
13:
14: ## Key Structs
15:
16: ### ReceiverState
17: Manages the contract's core state:
18: - `processed_vaas`: A table tracking processed Verified Action Approvals (VAAs)
19: - `trusted_emitter`: The authorized Wormhole external address for message emission
20:
21: ### AdminCap
22: An administrative capability object that grants privileged access to contract management functions.
23:
24: ## Events
25:
26: ### MessageProcessed
27: Emitted when a Wormhole message is successfully processed:
28: - `vaa_hash`: Hash of the processed VAA
29:
30: ### DepositInitialized
31: Emitted when a new Bitcoin deposit is initiated:
32: - `funding_tx`: Bitcoin funding transaction data
33: - `deposit_reveal`: Deposit reveal information
34: - `deposit_owner`: Address of the L2 deposit owner
35: - `sender`: Sender's external address
36:
37: ## Administrative Methods
38:
39: ### `init`
40: Initializes the contract with a default state:
41: - Creates a `ReceiverState` with an empty trusted emitter address
42: - Transfers an `AdminCap` to the contract deployer
43:
44: ### `set_trusted_emitter`
45: Sets the trusted emitter address for cross-chain communication.
46:
47: **Parameters:**
48: - `_: &AdminCap`: Administrative capability
49: - `state: &mut ReceiverState`: Contract state
50: - `emitter: vector<u8>`: Wormhole external address of the L1 BitcoinDepositor
51:
52: **Behavior:**
53: - Updates the trusted emitter address in the contract state
54:
55: ## Deposit Methods
56:
57: ### `initialize_deposit`
58: Initiates a new Bitcoin deposit process.
59:
60: **Parameters:**
61: - `funding_tx`: Bitcoin funding transaction data
62: - `deposit_reveal`: Deposit reveal information
63: - `deposit_owner`: Address of the L2 deposit owner
64:
65: **Behavior:**
66: - Emits a `DepositInitialized` event with deposit details
67:
68: ## Cross-Chain Message Processing
69:
70: ### `receiveWormholeMessages`
71: Processes incoming cross-chain messages from the Ethereum BitcoinDepositor.
72:
73: **Parameters:**
74: - Multiple state objects including `ReceiverState`, `Gateway::GatewayState`, etc.
75: - `vaa_bytes`: Raw Verified Action Approval (VAA) bytes
76: - `clock: &Clock`: Current time reference
77: - Other blockchain state and capability objects
78:
79: **Key Processing Steps:**
80: 1. Verify the VAA's authenticity
81: 2. Check that the message hasn't been processed before
82: 3. Validate the emitter's chain and address
83: 4. Mark the VAA as processed
84: 5. Emit a `MessageProcessed` event
85: 6. Call the Gateway contract to redeem tokens
86:
87: ## Security Considerations
88:
89: - Prevents replay attacks by tracking processed VAAs
90: - Restricts message processing to a specific chain and trusted emitter
91: - Requires administrative capability for critical configurations
92: - Leverages Wormhole's cross-chain messaging infrastructure
93:
94: ## Initialization and Testing
95:
96: - Provides a separate `init_test` method for testing purposes
97: - Supports modular initialization with administrative capabilities
98:
99: ## Deployment
100:
101: ### Step 1: Publish the contract
102:
103: Open the terminal and navigate to the project directory. Run the following command to publish the contract:
104:
105: ```bash
106: sui client publish ./ --gas-budget 1000000
107: ```
108:
109: ### Step 2: Add trusted emitter, etc..
110:
111: After the Bitcoin Depositor contract is initialized, the deployer has to add the trusted emitters, etc.
112:
113: ## License
114:
115: This contract is licensed under GPL-3.0-only.
116:
117: ## Interaction Flow
118:
119: 1. Bitcoin deposit is initiated on Ethereum
120: 2. Wormhole relayers transmit the deposit information
121: 3. `receiveWormholeMessages` validates and processes the message
122: 4. Gateway contract handles token redemption
123: 5. Tokens are minted or transferred to the deposit owner on Sui
124:
125: ## Dependencies
126:
127: - Relies on Wormhole for cross-chain messaging
128: - Integrates with the L2 TBTC Gateway contract
129: - Uses Sui's object and capability model
================
File: docs/gateway.md
================
1: # L2 TBTC Gateway Contract Documentation
2:
3: ## Overview
4:
5: The L2 TBTC Gateway contract is a sophisticated cross-chain token bridge implementation built on the Sui blockchain. It provides a secure mechanism for token transfers and redemption across different blockchain networks using Wormhole's messaging infrastructure.
6:
7: ## Key Structs
8:
9: ### GatewayState
10: Stores the core state of the gateway, including:
11: - Processed VAA (Verified Action Approval) hashes
12: - Trusted emitters and receivers
13: - Minting limits and current minted amount
14: - Initialization and pause status
15:
16: ### GatewayCapabilities
17: Manages critical capabilities:
18: - TBTC Minter Capability
19: - Wormhole Emitter Capability
20: - TBTC Treasury Capability
21:
22: ### AdminCap
23: A capability that grants administrative privileges to manage the gateway.
24:
25: ### WrappedTokenTreasury
26: Stores wrapped tokens for cross-chain transfers aquired from the bridge.
27:
28: ## Administrative Methods
29:
30: ### `initialize_gateway`
31: Initializes the gateway with all required capabilities.
32:
33: **Parameters:**
34: - `_: &AdminCap`: Admin capability
35: - `state: &mut GatewayState`: Gateway state object
36: - `wormhole_state: &WormholeState`: Wormhole state
37: - `minter_cap: TBTC::MinterCap`: TBTC minter capability
38: - `treasury_cap: TreasuryCap<TBTC::TBTC>`: TBTC treasury capability
39:
40: **Behavior:**
41: - Verifies the gateway is not already initialized
42: - Creates an emitter capability
43: - Shares the capabilities object
44: - Initializes the token treasury
45: - Emits a `GatewayInitialized` event
46:
47: ### `add_trusted_emitter`
48: Adds a trusted emitter from another blockchain.
49:
50: **Parameters:**
51: - `_: &AdminCap`: Admin capability
52: - `state: &mut GatewayState`: Gateway state
53: - `emitter_id: u16`: Chain ID of the emitter
54: - `emitter: vector<u8>`: External address of the emitter
55:
56: **Behavior:**
57: - Adds the emitter to the trusted emitters table
58: - Emits an `EmitterRegistered` event
59:
60: ### `add_trusted_receiver`
61: Adds a trusted receiver on another blockchain.
62:
63: **Parameters:**
64: - `_: &AdminCap`: Admin capability
65: - `state: &mut GatewayState`: Gateway state
66: - `receiver_id: u16`: Chain ID of the receiver
67: - `receiver: vector<u8>`: External address of the receiver
68:
69: **Behavior:**
70: - Adds the receiver to the trusted receivers table
71: - Emits a `ReceiverRegistered` event
72:
73: ### `pause` and `unpause`
74: Administrative methods to pause or unpause the gateway.
75:
76: **Parameters:**
77: - `_: &AdminCap`: Admin capability
78: - `state: &mut GatewayState`: Gateway state
79:
80: **Behavior:**
81: - Sets the gateway's pause status
82: - Emits `Paused` or `Unpaused` events
83:
84: ### `update_minting_limit`
85: Updates the maximum amount of tokens that can be minted.
86:
87: **Parameters:**
88: - `_: &AdminCap`: Admin capability
89: - `state: &mut GatewayState`: Gateway state
90: - `new_limit: u64`: New minting limit
91:
92: **Behavior:**
93: - Updates the minting limit
94: - Emits a `MintingLimitUpdated` event
95:
96: ### `change_admin`
97: Transfers administrative capabilities to a new admin.
98:
99: **Parameters:**
100: - `admin_cap: AdminCap`: Current admin capability
101: - `new_admin: address`: Address of the new admin
102:
103: **Behavior:**
104: - Transfers admin capability to the new admin
105: - Emits an `AdminChanged` event
106:
107: ## Token Transfer Methods
108:
109: ### `redeem_tokens`
110: Redeems tokens from a Wormhole VAA (Verified Action Approval).
111:
112: **Parameters:**
113: - Multiple state and capability objects
114: - `vaa_bytes: vector<u8>`: Encoded VAA
115: - `clock: &Clock`: Current time
116: - Other blockchain state objects
117:
118: **Behavior:**
119: - Verifies the VAA is valid and from a trusted source
120: - Checks minting limits
121: - Mints or transfers wrapped tokens to the recipient
122: - Emits a `TokensRedeemed` event
123:
124: ### `send_tokens`
125: Sends TBTC tokens to another blockchain via the token bridge.
126:
127: **Parameters:**
128: - Multiple state and capability objects
129: - `recipient_chain: u16`: Destination chain ID
130: - `recipient_address: vector<u8>`: Recipient's address
131: - `coins: Coin<TBTC::TBTC>`: Tokens to send
132: - `nonce: u32`: Unique transaction identifier
133: - `message_fee: Coin<sui::sui::SUI>`: Fee for message transmission
134:
135: **Behavior:**
136: - Burns local tokens
137: - Prepares a cross-chain transfer
138: - Publishes a message via Wormhole
139: - Emits a `TokensSent` event
140:
141: ### `send_wrapped_tokens`
142: Sends wrapped tokens to another blockchain.
143:
144: **Parameters:**
145: Similar to `send_tokens`, but works with wrapped token types.
146:
147: **Behavior:**
148: - Prepares a cross-chain transfer of wrapped tokens
149: - Publishes a message via Wormhole
150: - Emits a `TokensSent` event
151:
152: ## Helper Methods
153:
154: Several helper methods provide utility functions:
155: - `emitter_exists`: Checks if an emitter is trusted
156: - `get_emitter`: Retrieves a trusted emitter's address
157: - `receiver_exists`: Checks if a receiver is trusted
158: - `get_receiver`: Retrieves a trusted receiver's address
159: - `is_initialized`: Checks gateway initialization status
160: - `is_paused`: Checks gateway pause status
161: - `get_minting_limit`: Retrieves current minting limit
162: - `get_minted_amount`: Retrieves current minted token amount
163:
164: ## Events
165:
166: The contract emits various events to track important actions:
167: - `EmitterRegistered`
168: - `ReceiverRegistered`
169: - `Paused`
170: - `Unpaused`
171: - `MintingLimitUpdated`
172: - `TokensRedeemed`
173: - `TokensSent`
174: - `AdminChanged`
175:
176: ## Security Considerations
177:
178: - Requires admin capabilities for sensitive operations
179: - Prevents replay attacks through VAA hash tracking
180: - Implements minting limits
181: - Supports pausing the entire gateway
182: - Relies on trusted emitters and receivers
183:
184: ## Deployment
185:
186: ### Step 1: Publish the contract
187:
188: Open the terminal and navigate to the project directory. Run the following command to publish the contract:
189:
190: ```bash
191: sui client publish ./ --gas-budget 1000000
192: ```
193:
194: ### Step 2: Call the contracts methods for initialization
195:
196: In order to get the contract going the deployer has to call the init method.
197:
198: ``` move
199: /// Admin function to initialize the gateway with all required capabilities
200: /// Requires AdminCap
201: /// state - Gateway state
202: /// wormhole_state - Wormhole state
203: /// minter_cap - TBTC minter capability
204: /// treasury_cap - TBTC treasury capability
205: /// ctx - Transaction context
206: /// Emits GatewayInitialized event
207: public entry fun initialize_gateway<CoinType>(
208: _: &AdminCap,
209: state: &mut GatewayState,
210: wormhole_state: &WormholeState,
211: minter_cap: TBTC::MinterCap,
212: treasury_cap: TreasuryCap<TBTC::TBTC>,
213: ctx: &mut TxContext,
214: ) { ....}
215: ```
216:
217: ### Step 3: Add trusted emitters, receivers, change minting limit etc..
218:
219: After the gateway is initialized, the deployer has to add the trusted emitters, receivers, change minting limit etc.
220:
221: ## License
222:
223: This contract is licensed under GPL-3.0-only.
================
File: docs/tbtc.md
================
1: # TBTC Token Contract Documentation
2:
3: ## Overview
4:
5: The TBTC Token contract is a sophisticated token implementation built on the Sui blockchain. It provides a robust mechanism for managing a token with advanced access control, minting, burning, and pause functionality.
6:
7: ## Key Structs
8:
9: ### `TBTC`
10: A one-time witness struct for the coin type, used during initialization.
11:
12: ### `AdminCap`
13: A capability that grants administrative privileges to manage the token contract.
14:
15: ### `MinterCap`
16: A certificate proving an address has minting permissions.
17:
18: ### `GuardianCap`
19: A certificate proving an address has guardian permissions.
20:
21: ### `TokenState`
22: A global state object tracking:
23: - List of authorized minters
24: - List of authorized guardians
25: - Current pause status of the contract
26:
27: ## Contract Initialization
28:
29: The contract is initialized with:
30: - Creation of a new currency (TBTC)
31: - Setting up token metadata
32: - Creating an initial `TokenState`
33: - Transferring `AdminCap` to the contract deployer
34:
35: ## Administrative Methods
36:
37: ### `add_minter`
38: **Purpose:** Add a new address with minting permissions
39:
40: **Parameters:**
41: - `_: &AdminCap`: Administrative capability
42: - `state: &mut TokenState`: Global token state
43: - `minter: address`: Address to be granted minting rights
44: - `ctx: &mut TxContext`: Transaction context
45:
46: **Behavior:**
47: - Ensures the address is not already a minter
48: - Adds the address to the minters list
49: - Creates and transfers a `MinterCap` to the new minter
50: - Emits a `MinterAdded` event
51:
52: ### `remove_minter`
53: **Purpose:** Remove an existing minter's permissions
54:
55: **Parameters:**
56: - `_: &AdminCap`: Administrative capability
57: - `state: &mut TokenState`: Global token state
58: - `minter: address`: Address to remove from minters
59: - `_ctx: &mut TxContext`: Transaction context
60:
61: **Behavior:**
62: - Verifies the address is currently a minter
63: - Removes the address from the minters list
64: - Emits a `MinterRemoved` event
65:
66: ### `add_guardian`
67: **Purpose:** Add a new guardian address
68:
69: **Parameters:**
70: - `_: &AdminCap`: Administrative capability
71: - `state: &mut TokenState`: Global token state
72: - `guardian: address`: Address to be granted guardian rights
73: - `ctx: &mut TxContext`: Transaction context
74:
75: **Behavior:**
76: - Ensures the address is not already a guardian
77: - Adds the address to the guardians list
78: - Creates and transfers a `GuardianCap` to the new guardian
79: - Emits a `GuardianAdded` event
80:
81: ### `remove_guardian`
82: **Purpose:** Remove an existing guardian's permissions
83:
84: **Parameters:**
85: - `_: &AdminCap`: Administrative capability
86: - `state: &mut TokenState`: Global token state
87: - `guardian: address`: Address to remove from guardians
88: - `_ctx: &mut TxContext`: Transaction context
89:
90: **Behavior:**
91: - Verifies the address is currently a guardian
92: - Removes the address from the guardians list
93: - Emits a `GuardianRemoved` event
94:
95: ### `unpause`
96: **Purpose:** Unpause the token contract
97:
98: **Parameters:**
99: - `_: &AdminCap`: Administrative capability
100: - `state: &mut TokenState`: Global token state
101: - `ctx: &mut TxContext`: Transaction context
102:
103: **Behavior:**
104: - Ensures the contract is currently paused
105: - Sets the pause status to false
106: - Emits an `Unpaused` event
107:
108: ## Guardian Methods
109:
110: ### `pause`
111: **Purpose:** Pause the token contract
112:
113: **Parameters:**
114: - `_: &GuardianCap`: Guardian capability
115: - `state: &mut TokenState`: Global token state
116: - `ctx: &mut TxContext`: Transaction context
117:
118: **Behavior:**
119: - Verifies the caller is a guardian
120: - Ensures the contract is not already paused
121: - Sets the pause status to true
122: - Emits a `Paused` event
123:
124: ## Minter Methods
125:
126: ### `mint`
127: **Purpose:** Mint new tokens to a specified address
128:
129: **Parameters:**
130: - `_: &MinterCap`: Minter capability
131: - `treasury_cap: &mut TreasuryCap<TBTC>`: Treasury capability
132: - `state: &TokenState`: Global token state
133: - `amount: u64`: Amount of tokens to mint
134: - `recipient: address`: Address to receive minted tokens
135: - `ctx: &mut TxContext`: Transaction context
136:
137: **Behavior:**
138: - Verifies the caller is a minter
139: - Ensures the contract is not paused
140: - Mints tokens to the specified recipient
141: - Emits a `TokensMinted` event
142:
143: ## Public Methods
144:
145: ### `burn`
146: **Purpose:** Burn tokens
147:
148: **Parameters:**
149: - `treasury_cap: &mut TreasuryCap<TBTC>`: Treasury capability
150: - `state: &TokenState`: Global token state
151: - `coin: Coin<TBTC>`: Coin to be burned
152:
153: **Behavior:**
154: - Ensures the contract is not paused
155: - Burns the specified tokens
156: - Emits a `TokensBurned` event
157:
158: ## Helper Methods
159:
160: Several helper methods provide utility functions:
161: - `is_minter`: Check if an address is a minter
162: - `is_guardian`: Check if an address is a guardian
163: - `get_minters`: Retrieve all minter addresses
164: - `get_guardians`: Retrieve all guardian addresses
165: - `is_paused`: Check if the contract is paused
166:
167: ## Events
168:
169: The contract emits various events to track important actions:
170: - `MinterAdded`
171: - `MinterRemoved`
172: - `GuardianAdded`
173: - `GuardianRemoved`
174: - `Paused`
175: - `Unpaused`
176: - `TokensMinted`
177: - `TokensBurned`
178:
179: ## Security Considerations
180:
181: - Requires administrative or specific capabilities for sensitive operations
182: - Supports pausing the entire token contract
183: - Granular access control through minters and guardians
184: - Prevents unauthorized minting and burning
185:
186: ## Deployment
187:
188: ### Step 1: Publish the contract
189: ```bash
190: sui client publish ./ --gas-budget 1000000
191: ```
192:
193: ### Step 2: Add trusted minters, guardians, etc..
194: Use the contract's methods to add trusted minters, guardians, etc.
195:
196: ## License
197:
198: This contract is licensed under GPL-3.0-only.
================
File: sources/bitcoin_depositor/bitcoin_depositor.move
================
1: // SPDX-License-Identifier: GPL-3.0-only
2:
3: module l2_tbtc::BitcoinDepositor {
4:
5: use l2_tbtc::Gateway;
6: use l2_tbtc::TBTC;
7: use sui::clock::Clock;
8: use sui::event;
9: use sui::table::{Self, Table};
10: use wormhole::bytes32;
11: use wormhole::external_address::{Self, ExternalAddress};
12: use wormhole::state::State as WormholeState;
13: use wormhole::vaa;
14:
15: // === Constants ===
16:
17: const EMITTER_CHAIN_L1: u16 = 2;
18:
19: // === Error codes ===
20:
21: const INVALID_CHAIN_ID: u64 = 0;
22: const INVALID_SENDER: u64 = 1;
23: const MESSAGE_ALREADY_PROCESSED: u64 = 2;
24:
25: // === Events ===
26:
27: public struct MessageProcessed has copy, drop {
28: vaa_hash: vector<u8>,
29: }
30:
31: /// Event which is emitted as a initialization of the bitcoin deposit
32: public struct DepositInitialized has copy, drop {
33: funding_tx: vector<u8>,
34: deposit_reveal: vector<u8>,
35: deposit_owner: vector<u8>,
36: sender: vector<u8>,
37: }
38:
39: // === Types ===
40:
41: /// Object to store state
42: public struct ReceiverState has key {
43: id: UID,
44: // Store processed VAA hashes to prevent replay attacks
45: processed_vaas: Table<vector<u8>, bool>,
46: // Address of our contract on ETH side
47: trusted_emitter: ExternalAddress,
48: }
49:
50: /// Admin capabilities
51: public struct AdminCap has key, store {
52: id: UID,
53: }
54:
55: /// Initialize the receiver contract
56: fun init(ctx: &mut TxContext) {
57: // Create a dummy external address for initialization
58: // This should be properly set by the admin later
59: let mut empty_address = vector::empty<u8>();
60: vector::append(
61: &mut empty_address,
62: x"0000000000000000000000000000000000000000000000000000000000000000",
63: );
64: let sender = tx_context::sender(ctx);
65:
66: let state = ReceiverState {
67: id: object::new(ctx),
68: processed_vaas: table::new(ctx),
69: trusted_emitter: external_address::new(bytes32::new(empty_address)),
70: };
71:
72: // Create and share the admin capability
73: let admin_cap = AdminCap {
74: id: object::new(ctx),
75: };
76:
77: transfer::transfer(admin_cap, sender);
78: transfer::share_object(state);
79: }
80:
81: /// Admin function to set trusted emitter (the address of L1 BitcoinDepositor)
82: /// This should be called after deployment to set the correct address
83: /// emitter - Wormhole External Address of L1 BitcoinDepositor
84: /// ctx - Transaction context
85: /// Requires AdminCap
86: public entry fun set_trusted_emitter(
87: _: &AdminCap,
88: state: &mut ReceiverState,
89: emitter: vector<u8>,
90: _ctx: &mut TxContext,
91: ) {
92: state.trusted_emitter = external_address::new(bytes32::new(emitter));
93: }
94:
95: /// Initialize a deposit
96: /// funding_tx Bitcoin funding transaction data.
97: /// deposit_reveal Deposit reveal data.
98: /// deposit_owner Address of the L2 deposit owner.
99: public entry fun initialize_deposit(
100: funding_tx: vector<u8>,
101: deposit_reveal: vector<u8>,
102: deposit_owner: vector<u8>,
103: ctx: &mut TxContext,
104: ) {
105: let sender = tx_context::sender(ctx);
106: // Emit event for successful processing
107: event::emit(DepositInitialized {
108: funding_tx,
109: deposit_reveal,
110: deposit_owner,
111: sender: external_address::to_bytes(
112: external_address::new(bytes32::new(sui::address::to_bytes(sender))),
113: ),
114: });
115: }
116:
117: /// Function to process incoming Wormhole VAAs
118: /// receiver_state - State of the receiver contract
119: /// gateway_state - State of the gateway contract
120: /// capabilities - Gateway capabilities
121: /// treasury - Wrapped token treasury
122: /// wormhole_state - Wormhole state
123: /// token_bridge_state - Token bridge state
124: /// token_state - Token state
125: /// vaa_bytes - Raw VAA bytes
126: /// clock - Clock
127: /// ctx - Transaction context
128: public entry fun receiveWormholeMessages<CoinType>(
129: receiver_state: &mut ReceiverState,
130: gateway_state: &mut Gateway::GatewayState,
131: capabilities: &mut Gateway::GatewayCapabilities,
132: treasury: &mut Gateway::WrappedTokenTreasury<CoinType>,
133: wormhole_state: &mut WormholeState,
134: token_bridge_state: &mut token_bridge::state::State,
135: token_state: &mut TBTC::TokenState,
136: vaa_bytes: vector<u8>,
137: clock: &Clock,
138: ctx: &mut TxContext,
139: ) {
140: // Parse and verify the VAA
141: let parsed_vaa = vaa::parse_and_verify(wormhole_state, vaa_bytes, clock);
142:
143: // Get the VAA digest (hash)
144: let vaa_hash = vaa::digest(&parsed_vaa);
145: let digest_bytes = bytes32::to_bytes(vaa_hash);
146:
147: // Verify this VAA hasn't been processed before
148: assert!(
149: !table::contains(&receiver_state.processed_vaas, digest_bytes),
150: MESSAGE_ALREADY_PROCESSED,
151: );
152:
153: // Verify the emitter chain and address
154: let (emitter_chain, emitter_address, _) = vaa::take_emitter_info_and_payload(parsed_vaa);
155:
156: assert!(emitter_chain == EMITTER_CHAIN_L1, INVALID_CHAIN_ID);
157: assert!(
158: external_address::to_bytes32(emitter_address)
159: == external_address::to_bytes32(receiver_state.trusted_emitter),
160: INVALID_SENDER,
161: );
162:
163: // Mark this VAA as processed
164: table::add(&mut receiver_state.processed_vaas, digest_bytes, true);
165:
166: // Emit event for successful processing
167: event::emit(MessageProcessed {
168: vaa_hash: digest_bytes,
169: });
170:
171: // Call the gateway contract to redeem tokens
172: Gateway::redeem_tokens(
173: gateway_state,
174: capabilities,
175: wormhole_state,
176: treasury,
177: token_bridge_state,
178: token_state,
179: vaa_bytes,
180: clock,
181: ctx,
182: )
183: }
184:
185: /// For testing purposes only
186: #[test_only]
187: public fun init_test(ctx: &mut TxContext) {
188: let mut empty_address = vector::empty<u8>();
189: vector::append(
190: &mut empty_address,
191: x"0000000000000000000000000000000000000000000000000000000000000000",
192: );
193: let sender = tx_context::sender(ctx);
194:
195: let state = ReceiverState {
196: id: object::new(ctx),
197: processed_vaas: table::new(ctx),
198: trusted_emitter: external_address::new(bytes32::new(empty_address)),
199: };
200:
201: // Create and share the admin capability
202: let admin_cap = AdminCap {
203: id: object::new(ctx),
204: };
205:
206: transfer::transfer(admin_cap, sender);
207: transfer::share_object(state);
208: }
209: }
================
File: sources/gateway/helpers.move
================
1: // SPDX-License-Identifier: GPL-3.0-only
2:
3: module l2_tbtc::helpers{
4:
5: use wormhole::bytes32;
6: use wormhole::external_address::{Self, ExternalAddress};
7:
8: const E_INVALID_PAYLOAD: u64 = 9;
9:
10: public(package) fun parse_encoded_address(payload: &vector<u8>): address {
11: // Verify we have enough data in the payload
12: assert!(vector::length(payload) >= 32, E_INVALID_PAYLOAD);
13:
14: // Create a byte array to hold our address
15: let mut addr_bytes = vector::empty<u8>();
16:
17: // Copy the first 32 bytes from the payload - these are our address bytes
18: let mut i = 0;
19: while (i < 32) {
20: vector::push_back(&mut addr_bytes, *vector::borrow(payload, i));
21: i = i + 1;
22: };
23:
24: // Convert the bytes to a Sui address
25: // In Sui, addresses are 32 bytes (same as Wormhole external addresses)
26: let ext_address = external_address::new(bytes32::new(addr_bytes));
27: external_address::to_address(ext_address)
28: }
29:
30: public(package) fun encode_address(addr: ExternalAddress): vector<u8> {
31: // Convert the address to its raw bytes
32: let addr_bytes = external_address::to_bytes(addr);
33:
34: // Return the bytes
35: addr_bytes
36: }
37: }
================
File: sources/gateway/wormhole_gateway.move
================
1: // SPDX-License-Identifier: GPL-3.0-only
2:
3: module l2_tbtc::Gateway {
4:
5: use l2_tbtc::TBTC;
6: use l2_tbtc::helpers::{encode_address, parse_encoded_address};
7: use sui::clock::Clock;
8: use sui::coin::{Self, Coin, TreasuryCap};
9: use sui::event;
10: use sui::table::{Self, Table};
11: use token_bridge::complete_transfer_with_payload;
12: use token_bridge::state::verified_asset;
13: use token_bridge::transfer_tokens_with_payload::{Self, prepare_transfer};
14: use token_bridge::vaa::verify_only_once;
15: use wormhole::bytes32;
16: use wormhole::emitter::{Self, EmitterCap};
17: use wormhole::external_address::{Self, ExternalAddress};
18: use wormhole::state::State as WormholeState;
19: use wormhole::vaa;
20:
21: // === Constants ===
22:
23: const E_INVALID_CHAIN_ID: u64 = 0;
24: const E_INVALID_SENDER: u64 = 1;
25: const E_MESSAGE_ALREADY_PROCESSED: u64 = 2;
26: const E_NOT_ENOUGH_TOKENS: u64 = 4;
27: const E_ALREADY_INITIALIZED: u64 = 6;
28: const E_NOT_INITIALIZED: u64 = 7;
29: const E_PAUSED: u64 = 8;
30: const E_NOT_PAUSED: u64 = 9;
31: const E_WRONG_NONCE: u64 = 10;
32:
33: // === Events ===
34:
35: public struct EmitterRegistered has copy, drop { chain_id: u16, emitter: address }
36: public struct Paused has copy, drop {}
37: public struct Unpaused has copy, drop {}
38: public struct MintingLimitUpdated has copy, drop { new_limit: u64 }
39: public struct GatewayInitialized has copy, drop {
40: admin: address,
41: }
42: public struct ReceiverRegistered has copy, drop { chain_id: u16, receiver: address }
43: public struct EmitterRemoved has copy, drop { chain_id: u16 }
44: public struct TokensRedeemed has copy, drop {
45: vaa_hash: vector<u8>,
46: token_amount: u64,
47: recipient: address,
48: token_address: address,
49: minted: bool,
50: }
51: public struct TokensSent has copy, drop {
52: sequence: u64,
53: amount: u64,
54: recipient_chain: u16,
55: recipient: address,
56: nonce: u32,
57: }
58: public struct AdminChanged has copy, drop {
59: previous_admin: address,
60: new_admin: address,
61: }
62:
63: // === Types ===
64:
65: /// Object to store gateway state
66: public struct GatewayState has key {
67: id: UID,
68: // Store processed VAA hashes to prevent replay attacks
69: processed_vaas: Table<vector<u8>, bool>,
70: // Trusted Emitters
71: trusted_emitters: Table<u16, ExternalAddress>,
72: // Trusted Receivers
73: trusted_receivers: Table<u16, ExternalAddress>,
74: // Minting limit (useful for testing phases)
75: minting_limit: u64,
76: // Amount of tokens minted by this gateway
77: minted_amount: u64,
78: // Track if the gateway has been initialized
79: is_initialized: bool,
80: // Paused state
81: paused: bool,
82: // Nonce
83: nonce: u32,
84: }
85:
86: /// Separate object to store capabilities
87: public struct GatewayCapabilities has key {
88: id: UID,
89: // MinterCap
90: minter_cap: TBTC::MinterCap,
91: // EmitterCap
92: emitter_cap: EmitterCap,
93: // TreasuryCap
94: treasury_cap: TreasuryCap<TBTC::TBTC>,
95: }
96:
97: /// Admin capability
98: public struct AdminCap has key {
99: id: UID,
100: }
101:
102: #[allow(lint(coin_field))]
103: /// Treasury to store wrapped tokens
104: public struct WrappedTokenTreasury<phantom CoinType> has key {
105: id: UID,
106: tokens: Coin<CoinType>,
107: }
108:
109: // === Initialization ===
110:
111: /// Initialize the gateway contract with basic structure
112: fun init(ctx: &mut TxContext) {
113: // Create a dummy external address for initialization
114: let mut empty_address: vector<u8> = vector::empty<u8>();
115:
116: vector::append(
117: &mut empty_address,
118: x"0000000000000000000000000000000000000000000000000000000000000000",
119: );
120:
121: let sender = tx_context::sender(ctx);
122:
123: // Create and share the gateway state with minimal initialization
124: let state = GatewayState {
125: id: object::new(ctx),
126: processed_vaas: table::new(ctx),
127: trusted_emitters: table::new(ctx),
128: trusted_receivers: table::new(ctx),
129: minting_limit: 18446744073709551615, // u64::MAX
130: minted_amount: 0,
131: is_initialized: false,
132: paused: false,
133: nonce: 0,
134: };
135:
136: // Create and share the admin capability
137: let admin_cap = AdminCap {
138: id: object::new(ctx),
139: };
140:
141: // Share state object and transfer admin capability
142: transfer::share_object(state);
143: transfer::transfer(admin_cap, sender);
144: }
145:
146: /// Admin function to initialize the gateway with all required capabilities
147: /// Requires AdminCap
148: /// state - Gateway state
149: /// wormhole_state - Wormhole state
150: /// minter_cap - TBTC minter capability
151: /// treasury_cap - TBTC treasury capability
152: /// ctx - Transaction context
153: /// Emits GatewayInitialized event
154: public entry fun initialize_gateway<CoinType>(
155: _: &AdminCap,
156: state: &mut GatewayState,
157: wormhole_state: &WormholeState,
158: minter_cap: TBTC::MinterCap,
159: treasury_cap: TreasuryCap<TBTC::TBTC>,
160: ctx: &mut TxContext,
161: ) {
162: // Verify the gateway hasn't been initialized yet
163: assert!(!state.is_initialized, E_ALREADY_INITIALIZED);
164:
165: // Create emitter capability
166: let emitter_cap = emitter::new(wormhole_state, ctx);
167:
168: // Create and share the capabilities object
169: let capabilities = GatewayCapabilities {
170: id: object::new(ctx),
171: minter_cap,
172: emitter_cap,
173: treasury_cap,
174: };
175:
176: // Mark the gateway as initialized
177: state.is_initialized = true;
178:
179: // Share the capabilities object
180: transfer::share_object(capabilities);
181:
182: init_treasury<CoinType>(ctx);
183:
184: // Emit initialization event
185: event::emit(GatewayInitialized {
186: admin: tx_context::sender(ctx),
187: });
188: }
189:
190: /// This function initializes the treasury object
191: /// ctx - Transaction context
192: /// It is used to store the wrapped tokens
193: fun init_treasury<CoinType>(ctx: &mut TxContext) {
194: let treasury = WrappedTokenTreasury {
195: id: object::new(ctx),
196: tokens: coin::zero<CoinType>(ctx),
197: };
198: transfer::share_object(treasury);
199: }
200:
201: /// Admin function to add a trusted emitters
202: /// Requires AdminCap
203: /// state - Gateway state
204: /// emitter_id - Chain ID of the emitter
205: /// emitter - External Address of the emitter
206: /// ctx - Transaction context
207: /// Emits EmitterRegistered event
208: public entry fun add_trusted_emitter(
209: _: &AdminCap,
210: state: &mut GatewayState,
211: emitter_id: u16,
212: emitter: vector<u8>,
213: _ctx: &mut TxContext,
214: ) {
215: // Verify the gateway is initialized
216: assert!(state.is_initialized, E_NOT_INITIALIZED);
217:
218: state.trusted_emitters.add(emitter_id, external_address::new(bytes32::new(emitter)));
219: event::emit(EmitterRegistered {
220: chain_id: emitter_id,
221: emitter: external_address::to_address(external_address::new(bytes32::new(emitter))),
222: });
223: }
224:
225: /// Admin function to remove a trusted emitter
226: /// Requires AdminCap
227: /// state - Gateway state
228: /// emitter_id - Chain ID of the emitter
229: /// ctx - Transaction context
230: /// Emits EmitterRemoved event
231: public entry fun remove_trusted_emitter(
232: _: &AdminCap,
233: state: &mut GatewayState,
234: emitter_id: u16,
235: _ctx: &mut TxContext,
236: ) {
237: // Verify the gateway is initialized
238: assert!(state.is_initialized, E_NOT_INITIALIZED);
239:
240: state.trusted_emitters.remove(emitter_id);
241: event::emit(EmitterRemoved { chain_id: emitter_id });
242: }
243:
244: /// Admin function to add a trusted receiver
245: /// Requires AdminCap
246: /// state - Gateway state
247: /// receiver_id - Chain ID of the receiver
248: /// receiver - External Address of the receiver
249: /// ctx - Transaction context
250: /// Emits ReceiverRegistered event
251: public entry fun add_trusted_receiver(
252: _: &AdminCap,
253: state: &mut GatewayState,
254: receiver_id: u16,
255: receiver: vector<u8>,
256: _ctx: &mut TxContext,
257: ) {
258: // Verify the gateway is initialized
259: assert!(state.is_initialized, E_NOT_INITIALIZED);
260:
261: state.trusted_receivers.add(receiver_id, external_address::new(bytes32::new(receiver)));
262: event::emit(ReceiverRegistered {
263: chain_id: receiver_id,
264: receiver: external_address::to_address(external_address::new(bytes32::new(receiver))),
265: });
266: }
267:
268: /// Admin function to remove a trusted receiver
269: /// Requires AdminCap
270: /// state - Gateway state
271: /// receiver_id - Chain ID of the receiver
272: /// ctx - Transaction context
273: /// Emits ReceiverUnregistered event
274: public entry fun remove_trusted_receiver(
275: _: &AdminCap,
276: state: &mut GatewayState,
277: receiver_id: u16,
278: _ctx: &mut TxContext,
279: ) {
280: // Verify the gateway is initialized
281: assert!(state.is_initialized, E_NOT_INITIALIZED);
282:
283: state.trusted_receivers.remove(receiver_id);
284: }
285:
286: /// Admin function to pause the gateway
287: /// Requires AdminCap
288: /// state - Gateway state
289: /// ctx - Transaction context
290: /// Emits Paused event
291: public entry fun pause(_: &AdminCap, state: &mut GatewayState, _ctx: &mut TxContext) {
292: // Verify the gateway is initialized
293: assert!(state.is_initialized, E_NOT_INITIALIZED);
294: assert!(!state.paused, E_PAUSED);
295:
296: state.paused = true;
297: event::emit(Paused {});
298: }
299:
300: /// Admin function to unpause the gateway
301: /// Requires AdminCap
302: /// state - Gateway state
303: /// ctx - Transaction context
304: /// Emits Unpaused event
305: public entry fun unpause(_: &AdminCap, state: &mut GatewayState, _ctx: &mut TxContext) {
306: // Verify the gateway is initialized
307: assert!(state.is_initialized, E_NOT_INITIALIZED);
308: assert!(state.paused, E_NOT_PAUSED);
309:
310: state.paused = false;
311: event::emit(Unpaused {});
312: }
313:
314: /// Admin function to update minting limit
315: /// Requires AdminCap
316: /// state - Gateway state
317: /// new_limit - New minting limit
318: /// ctx - Transaction context
319: /// Emits MintingLimitUpdated event
320: public entry fun update_minting_limit(
321: _: &AdminCap,
322: state: &mut GatewayState,
323: new_limit: u64,
324: _ctx: &mut TxContext,
325: ) {
326: // Verify the gateway is initialized
327: assert!(state.is_initialized, E_NOT_INITIALIZED);
328:
329: state.minting_limit = new_limit;
330: event::emit(MintingLimitUpdated { new_limit });
331: }
332:
333: /// Admin function to change admin
334: /// Requires AdminCap
335: /// admin_cap - Admin capability
336: /// new_admin - New admin address
337: /// ctx - Transaction context
338: /// Emits AdminChanged event
339: public entry fun change_admin(admin_cap: AdminCap, new_admin: address, ctx: &mut TxContext) {
340:
341: // Share the new admin capability
342: transfer::transfer(admin_cap, new_admin);
343:
344: // Emit an event to track the admin change
345: event::emit(AdminChanged {
346: previous_admin: tx_context::sender(ctx),
347: new_admin,
348: });
349: }
350:
351: /// Main function to redeem tokens from Wormhole VAAs
352: /// This function is used to redeem tokens from Wormhole VAAs
353: /// The tokens are minted and sent to the recipient
354: /// The VAA is verified and processed only once
355: /// The function emits an event for the transaction
356: /// The function reverts if the gateway is not initialized, the VAA has been processed before, the emitter chain is not trusted, the emitter address is not trusted, the VAA is not verified, the minting limit is reached
357: public entry fun redeem_tokens<CoinType>(
358: state: &mut GatewayState,
359: capabilities: &mut GatewayCapabilities,
360: wormhole_state: &mut WormholeState,
361: treasury: &mut WrappedTokenTreasury<CoinType>,
362: token_bridge_state: &mut token_bridge::state::State,
363: token_state: &mut TBTC::TokenState,
364: vaa_bytes: vector<u8>,
365: clock: &Clock,
366: ctx: &mut TxContext,
367: ) {
368: // Check if contract is paused
369: assert!(!state.paused, E_PAUSED);
370:
371: // Verify the gateway is initialized
372: assert!(state.is_initialized, E_NOT_INITIALIZED);
373:
374: // Parse and verify the VAA
375: let verified_vaa = vaa::parse_and_verify(wormhole_state, vaa_bytes, clock);
376:
377: // Get the VAA digest (hash)
378: let vaa_hash = vaa::digest(&verified_vaa);
379: let digest_bytes = bytes32::to_bytes(vaa_hash);
380:
381: // Verify this VAA hasn't been processed before
382: assert!(!table::contains(&state.processed_vaas, digest_bytes), E_MESSAGE_ALREADY_PROCESSED);
383: let emitter_chain = verified_vaa.emitter_chain();
384: let emitter_address = verified_vaa.emitter_address();
385: let payload = verified_vaa.payload();
386:
387: // Verify the emitter chain and address from trusted_emitters table in state
388: assert!(emitter_exists(state, emitter_chain), E_INVALID_CHAIN_ID);
389: assert!(emitter_address == get_emitter(state,emitter_chain), E_INVALID_SENDER);
390:
391: // Extract payload from VAA
392:
393: // Parse our custom payload format
394: let recipient = parse_encoded_address(&payload);
395:
396: // Verify the VAA only once to prevent replay attacks
397: let msg = verify_only_once(token_bridge_state, verified_vaa);
398:
399: // Authorize the transfer
400: let receipt: complete_transfer_with_payload::RedeemerReceipt<
401: CoinType,
402: > = complete_transfer_with_payload::authorize_transfer(token_bridge_state, msg, ctx);
403:
404: // Redeem the coins
405: let (
406: bridged_coins,
407: _parsed_transfer,
408: _source_chain,
409: ) = complete_transfer_with_payload::redeem_coin(&capabilities.emitter_cap, receipt);
410:
411: // Get the amount of tokens to mint
412: let amount = coin::value(&bridged_coins);
413:
414: // Mark this VAA as processed
415: table::add(&mut state.processed_vaas, digest_bytes, true);
416:
417: // Check if we can mint new tokens or need to send the wrapped assets
418: if (state.minted_amount + amount <= state.minting_limit) {
419: // Within the limit, mint new tokens
420: state.minted_amount = state.minted_amount + amount;
421:
422: // Mint TBTC tokens
423: TBTC::mint(
424: &capabilities.minter_cap,
425: &mut capabilities.treasury_cap,
426: token_state,
427: amount,
428: recipient,
429: ctx,
430: );
431:
432: // Emit event for successful processing
433: event::emit(TokensRedeemed {
434: vaa_hash: digest_bytes,
435: token_amount: amount,
436: recipient,
437: token_address: @l2_tbtc,
438: minted: true,
439: });
440:
441: // Keep the wrapped coins in the contract
442: store_wrapped_coins<CoinType>(treasury, bridged_coins);
443: } else {
444: // We've hit the mint limit, send wrapped assets directly
445: event::emit(TokensRedeemed {
446: vaa_hash: digest_bytes,
447: token_amount: amount,
448: recipient,
449: token_address: @l2_tbtc,
450: minted: false,
451: });
452:
453: // Transfer wrapped coins directly to the recipient
454: transfer::public_transfer(bridged_coins, recipient);
455: }
456: }
457:
458: /// Send tokens to the token bridge
459: /// This function is used to send tokens to the token bridge for further processing
460: /// The tokens are sent to the token bridge and not directly to the recipient
461: /// The recipient chain is the chain where the recipient is located and where we have a receiver registered
462: /// The recipient is the wormhole's external address of the recipient so it can be parsed to the recipient address type on desired chain
463: /// The nonce is used to prevent replay attacks
464: /// The message fee is the fee to be paid for sending the message
465: /// The clock is used to get the current time
466: /// The context is used to get the sender of the transaction
467: /// The function emits an event for the transaction
468: /// The function reverts if the gateway is not initialized, is paused, the recipient chain is not trusted, the treasury does not have enough tokens, or the minted amount is not enough
469: public entry fun send_tokens<CoinType>(
470: state: &mut GatewayState,
471: capabilities: &mut GatewayCapabilities,
472: token_bridge_state: &mut token_bridge::state::State,
473: token_state: &mut TBTC::TokenState,
474: treasury: &mut WrappedTokenTreasury<CoinType>,
475: wormhole_state: &mut WormholeState,
476: recipient_chain: u16,
477: recipient_address: vector<u8>,
478: coins: Coin<TBTC::TBTC>,
479: nonce: u32,
480: message_fee: Coin<sui::sui::SUI>,
481: clock: &Clock,
482: ctx: &mut TxContext,
483: ) {
484: // Check gateway state
485: assert!(state.is_initialized, E_NOT_INITIALIZED);
486: assert!(!state.paused, E_PAUSED);
487:
488: // Check if the nonce is valid
489: assert!(state.nonce + 1 == nonce, E_WRONG_NONCE);
490:
491: // Verify recipient chain has a trusted receiver
492: assert!(receiver_exists(state, recipient_chain), E_INVALID_CHAIN_ID);
493:
494: // Get the amount of tokens to send
495: let amount_l2btc = coins.balance().value();
496:
497: // Check if treasury has enough tokens
498: let treasury_balance = coin::value(&treasury.tokens);
499: // We are reverting if the treasury does not have enough tokens
500: assert!(treasury_balance >= amount_l2btc, E_NOT_ENOUGH_TOKENS);
501:
502: // Burn the tokens from the treasury since they were minted when our gateway got the wrapped tokens in treasury and they are 1:1
503: TBTC::burn(
504: &mut capabilities.treasury_cap,
505: token_state,
506: coins,
507: );
508:
509: // We now need to withdraw the equivalent amount of wrapped tokens from the treasury
510: let wrapped_coins = coin::split(&mut treasury.tokens, amount_l2btc, ctx);
511:
512: // Update the minted amount in the state
513: if (state.minted_amount >= amount_l2btc) {
514: state.minted_amount = state.minted_amount - amount_l2btc;
515: } else {
516: // This should not happen, but just in case
517: state.minted_amount = 0;
518: };
519:
520: // Get the asset info from the token bridge
521: let asset_info: token_bridge::token_registry::VerifiedAsset<CoinType> = verified_asset(
522: token_bridge_state,
523: );
524:
525: // Get the receiver address
526: let receiver_address = get_receiver(state, recipient_chain);
527: let receiver_address_bytes = external_address::to_bytes(receiver_address);
528:
529: // Prepare the transfer
530: // The recipient is the receiver address since we are sending cannonical tokens to the token bridge and not directly to the recipient
531: // The recipient chain is the chain where the recipient is located and where we have a receiver registered
532: let (prepared_transfer, dust) = prepare_transfer(
533: &capabilities.emitter_cap,
534: asset_info,
535: wrapped_coins,
536: recipient_chain,
537: receiver_address_bytes,
538: encode_address(external_address::new(bytes32::new(recipient_address))),
539: nonce,
540: );
541:
542: // Destroy dust coins
543: // Dust is the amount of tokens that are not enough to be transferred and therefore we are destroying them
544: coin::destroy_zero(dust);
545:
546: // Prepare the message
547: let prepared_msg = transfer_tokens_with_payload::transfer_tokens_with_payload(
548: token_bridge_state,
549: prepared_transfer,
550: );
551:
552: // Publish the message to the Wormhole
553: let sequence = wormhole::publish_message::publish_message(
554: wormhole_state,
555: message_fee,
556: prepared_msg,
557: clock,
558: );
559:
560: // Increment the nonce
561: increment_nonce(state);
562:
563: // Emit an event for the transaction
564: event::emit(TokensSent {
565: sequence,
566: amount: amount_l2btc,
567: recipient_chain,
568: recipient: sui::address::from_bytes(recipient_address),
569: nonce,
570: });
571: }
572:
573: /// Send wrapped tokens to the token bridge
574: /// This function is used to send wrapped tokens to the token bridge for further processing
575: /// The tokens are sent to the token bridge and and the redeemer is reciepient himself
576: /// The recipient chain is the chain where the recipient is located and we do not mind for our gateway since the tokens are send to him directly
577: /// The recipient is the wormhole's external address of the recipient so it can be parsed to the recipient address type on desired chain
578: /// The nonce is used to prevent replay attacks
579: /// The message fee is the fee to be paid for sending the message
580: /// The clock is used to get the current time
581: /// The context is used to get the sender of the transaction
582: /// The function emits an event for the transaction
583: /// The function reverts if the gateway is not initialized, is paused, the recipient chain is not trusted
584: public entry fun send_wrapped_tokens<CoinType>(
585: state: &mut GatewayState,
586: capabilities: &mut GatewayCapabilities,
587: token_bridge_state: &mut token_bridge::state::State,
588: wormhole_state: &mut WormholeState,
589: recipient_chain: u16,
590: recipient_address: vector<u8>,
591: coins: Coin<CoinType>,
592: nonce: u32,
593: message_fee: Coin<sui::sui::SUI>,
594: clock: &Clock,
595: _ctx: &mut TxContext,
596: ) {
597: // Check gateway state
598: assert!(state.is_initialized, E_NOT_INITIALIZED);
599: assert!(!state.paused, E_PAUSED);
600:
601: // Check if the nonce is valid
602: assert!(state.nonce + 1 == nonce, E_WRONG_NONCE);
603:
604: // Verify recipient chain has a trusted receiver
605: assert!(receiver_exists(state, recipient_chain), E_INVALID_CHAIN_ID);
606:
607: // Get the amount of tokens to send
608: let amount_wrapped = coins.balance().value();
609:
610: // Get the asset info from the token bridge
611: let asset_info: token_bridge::token_registry::VerifiedAsset<CoinType> = verified_asset(
612: token_bridge_state,
613: );
614:
615: // Get the receiver address
616: let recipient_address_bytes = external_address::to_bytes(
617: external_address::new(bytes32::new(recipient_address)),
618: );
619:
620: // Prepare the transfer
621: // We are setting the recipient as redeemer address since we are sending wrapped tokens directly
622: let (prepared_transfer, dust) = prepare_transfer(
623: &capabilities.emitter_cap,
624: asset_info,
625: coins,
626: recipient_chain,
627: recipient_address_bytes,
628: encode_address(external_address::new(bytes32::new(recipient_address))),
629: nonce,
630: );
631:
632: // Destroy dust coins
633: // Dust is the amount of tokens that are not enough to be transferred and therefore we are destroying them
634: coin::destroy_zero(dust);
635:
636: // Prepare the message
637: let prepared_msg = transfer_tokens_with_payload::transfer_tokens_with_payload(
638: token_bridge_state,
639: prepared_transfer,
640: );
641:
642: // Publish the message to the Wormhole
643: let sequence = wormhole::publish_message::publish_message(
644: wormhole_state,
645: message_fee,
646: prepared_msg,
647: clock,
648: );
649:
650: // Increment the nonce
651: increment_nonce(state);
652:
653: // Emit an event for the transaction
654: event::emit(TokensSent {
655: sequence,
656: amount: amount_wrapped,
657: recipient_chain,
658: recipient: sui::address::from_bytes(recipient_address),
659: nonce,
660: });
661: }
662:
663: /// Helper to check the current nonce
664: public fun check_nonce(state: &GatewayState): u32 {
665: // Check gateway state
666: assert!(state.is_initialized, E_NOT_INITIALIZED);
667: assert!(!state.paused, E_PAUSED);
668: // Return the current nonce
669: state.nonce
670: }
671:
672: /// Helper function to increment the nonce
673: /// state - GatewayState
674: /// It is used to increment the nonce
675: /// The nonce is used to prevent replay attacks
676: /// The nonce is incremented by 1 if the current nonce is less than the max value of u32
677: /// The nonce is reset to 0 if the current nonce is equal to the max value of u32
678: /// The function reverts if the gateway is not initialized
679: /// The function reverts if the gateway is paused
680: /// The function reverts if the nonce is not valid
681: fun increment_nonce(state: &mut GatewayState) {
682: // Check gateway state
683: assert!(state.is_initialized, E_NOT_INITIALIZED);
684: assert!(!state.paused, E_PAUSED);
685:
686: // Check if nonce does not exeed the max value of u32
687: if (state.nonce == 4_294_967_293u32) {
688: // Reset the nonce to 0
689: state.nonce = 0;
690: } else {
691: // Increment the nonce
692: state.nonce = state.nonce + 1;
693: }
694: }
695:
696: /// Helper function to check if an emitter exists
697: /// gateway_state - GatewayState
698: /// chain_id - Chain ID of the emitter
699: /// Returns true if the emitter exists
700: /// Returns false if the emitter does not exist
701: public fun emitter_exists(gateway_state: &GatewayState, chain_id: u16): bool {
702: gateway_state.trusted_emitters.contains(chain_id)
703: }
704:
705: /// Helper function to get an emitter
706: /// gateway_state - GatewayState
707: /// chain_id - Chain ID of the emitter
708: /// Returns the emitter address
709: /// Reverts if the emitter does not exist
710: public fun get_emitter(gateway_state: &GatewayState, chain_id: u16): ExternalAddress {
711: *gateway_state.trusted_emitters.borrow(chain_id)
712: }
713:
714: /// Helper function to check if a receiver exists
715: /// gateway_state - GatewayState
716: /// chain_id - Chain ID of the receiver
717: /// Returns true if the receiver exists
718: public fun receiver_exists(gateway_state: &GatewayState, chain_id: u16): bool {
719: gateway_state.trusted_receivers.contains(chain_id)
720: }
721:
722: /// Helper function to get a receiver
723: /// gateway_state - GatewayState
724: /// chain_id - Chain ID of the receiver
725: /// Returns the receiver address
726: public fun get_receiver(gateway_state: &GatewayState, chain_id: u16): ExternalAddress {
727: *gateway_state.trusted_receivers.borrow(chain_id)
728: }
729:
730: /// Helper function to get the initialized state
731: /// gateway_state - GatewayState
732: /// Returns true if the gateway is initialized
733: /// Returns false if the gateway is not initialized
734: public fun is_initialized(gateway_state: &GatewayState): bool {
735: gateway_state.is_initialized
736: }
737:
738: /// Helper function to get the paused state
739: /// gateway_state - GatewayState
740: /// Returns true if the gateway is paused
741: /// Returns false if the gateway is not paused
742: public fun is_paused(gateway_state: &GatewayState): bool {
743: gateway_state.paused
744: }
745:
746: /// Helper function to get the minting limit
747: /// gateway_state - GatewayState
748: /// Returns the minting limit
749: public fun get_minting_limit(gateway_state: &GatewayState): u64 {
750: gateway_state.minting_limit
751: }
752:
753: /// Helper function to get the minted amount
754: /// gateway_state - GatewayState
755: /// Returns the minted amount
756: public fun get_minted_amount(gateway_state: &GatewayState): u64 {
757: gateway_state.minted_amount
758: }
759:
760: /// Helper function to store wrapped coins in the treasury
761: /// treasury - WrappedTokenTreasury
762: /// coins - Coin
763: /// It is used to store the wrapped tokens in the treasury
764: fun store_wrapped_coins<CoinType>(
765: treasury: &mut WrappedTokenTreasury<CoinType>,
766: coins: Coin<CoinType>,
767: ) {
768: coin::join(&mut treasury.tokens, coins);
769: }
770:
771: // // For testing purposes only
772: #[test_only]
773: public fun init_test(ctx: &mut TxContext) {
774: let gateway_state = GatewayState {
775: is_initialized: false,
776: paused: false,
777: minting_limit: 1000000000000000000,
778: minted_amount: 0,
779: processed_vaas: table::new(ctx),
780: trusted_emitters: table::new(ctx),
781: trusted_receivers: table::new(ctx),
782: id: object::new(ctx),
783: nonce: 0,
784: };
785: // Create and share the admin capability
786: let admin_cap = AdminCap {
787: id: object::new(ctx),
788: };
789:
790: // Share state object and transfer admin capability
791: transfer::transfer(admin_cap, ctx.sender());
792: transfer::share_object(gateway_state);
793: }
794:
795: /// Setter for nonce for testing purposes
796: #[test_only]
797: public fun set_nonce(state: &mut GatewayState, nonce: u32) {
798: state.nonce = nonce;
799: }
800:
801: #[test_only]
802: public fun initialize_gateway_test<CoinType>( gateway_state: &mut GatewayState, minter_cap: TBTC::MinterCap, treasury_cap: TreasuryCap<TBTC::TBTC>, ctx: &mut TxContext, emitter_cap: EmitterCap) {
803: // Verify the gateway hasn't been initialized yet
804: assert!(!gateway_state.is_initialized, E_ALREADY_INITIALIZED);
805:
806: // Create and share the capabilities object
807: let capabilities = GatewayCapabilities {
808: id: object::new(ctx),
809: minter_cap,
810: emitter_cap,
811: treasury_cap,
812: };
813:
814: // Mark the gateway as initialized
815: gateway_state.is_initialized = true;
816:
817: // Share the capabilities object
818: transfer::share_object(capabilities);
819:
820: init_treasury<CoinType>(ctx);
821:
822: // Emit initialization event
823: event::emit(GatewayInitialized {
824: admin: tx_context::sender(ctx),
825: });
826: }
827:
828: // get capability total supply
829: #[test_only]
830: public fun get_total_supply(capabilities: &mut GatewayCapabilities): u64 {
831: capabilities.treasury_cap.total_supply()
832: }
833: }
================
File: sources/token/tbtc.move
================
1: // SPDX-License-Identifier: GPL-3.0-only
2:
3: module l2_tbtc::TBTC {
4:
5: use sui::coin::{Self, Coin, TreasuryCap};
6: use sui::event;
7: use sui::url;
8:
9: // === Constants ===
10:
11: // Error codes
12: const E_NOT_MINTER: u64 = 0;
13: const E_NOT_GUARDIAN: u64 = 1;
14: const E_ALREADY_MINTER: u64 = 2;
15: const E_NOT_IN_MINTERS_LIST: u64 = 3;
16: const E_ALREADY_GUARDIAN: u64 = 4;
17: const E_NOT_IN_GUARDIANS_LIST: u64 = 5;
18: const E_PAUSED: u64 = 6;
19: const E_NOT_PAUSED: u64 = 7;
20:
21: // === Events ===
22:
23: public struct MinterAdded has copy, drop { minter: address }
24: public struct MinterRemoved has copy, drop { minter: address }
25: public struct GuardianAdded has copy, drop { guardian: address }
26: public struct GuardianRemoved has copy, drop { guardian: address }
27: public struct Paused has copy, drop { guardian: address }
28: public struct Unpaused has copy, drop { owner: address }
29: public struct TokensMinted has copy, drop { amount: u64, recipient: address }
30: public struct TokensBurned has copy, drop { amount: u64 }
31:
32: // === Types ===
33:
34: // One-Time-Witness for the coin
35: public struct TBTC has drop {}
36:
37: /// Capability representing the authority to manage the token
38: public struct AdminCap has key, store {
39: id: UID,
40: }
41:
42: /// A certificate proving an address is a minter
43: public struct MinterCap has key, store {
44: id: UID,
45: minter: address,
46: }
47:
48: /// A certificate proving an address is a guardian
49: public struct GuardianCap has key, store {
50: id: UID,
51: guardian: address,
52: }
53:
54: /// A global state object to track minters, guardians, and pause state
55: public struct TokenState has key, store {
56: id: UID,
57: minters: vector<address>,
58: guardians: vector<address>,
59: paused: bool,
60: }
61:
62: /// Module initializer
63: fun init(witness: TBTC, ctx: &mut TxContext) {
64: let (treasury_cap, metadata) = coin::create_currency(
65: witness,
66: // Setting decimals to 8 since wormhole uses max 8 decimals for wrapped tokens, and doing it this way we are avoiding normalization
67: // https://github.com/wormhole-foundation/wormhole/blob/4afcbdeb13ec03bdc45516c9be6da0091079f352/sui/token_bridge/sources/datatypes/normalized_amount.move#L25
68: 8,
69: b"TBTC",
70: b"Threshold Bitcoin",
71: b"Canonical L2/sidechain token implementation for tBTC",
72: option::some(
73: url::new_unsafe_from_bytes(
74: b"https://assets.coingecko.com/coins/images/11224/standard/0x18084fba666a33d37592fa2633fd49a74dd93a88.png",
75: ),
76: ),
77: ctx,
78: );
79:
80: let token_state = TokenState {
81: id: object::new(ctx),
82: minters: vector::empty(),
83: guardians: vector::empty(),
84: paused: false,
85: };
86:
87: let admin_cap = AdminCap {
88: id: object::new(ctx),
89: };
90:
91: let sender = tx_context::sender(ctx);
92:
93: // Transfer ownership of the coin
94: transfer::public_freeze_object(metadata);
95: transfer::public_transfer(treasury_cap, sender);
96: transfer::public_transfer(admin_cap, sender);
97: transfer::share_object(token_state);
98: }
99:
100: // ===== Owner functions =====
101:
102: /// Add a new minter address
103: /// Requires AdminCap
104: /// state - TokenState
105: /// minter - Address to add as a minter
106: /// ctx - Transaction context
107: /// Emits MinterAdded event
108: public entry fun add_minter(
109: _: &AdminCap,
110: state: &mut TokenState,
111: minter: address,
112: ctx: &mut TxContext,
113: ) {
114: assert!(!is_minter(state, minter), E_ALREADY_MINTER);
115:
116: vector::push_back(&mut state.minters, minter);
117:
118: // Create and transfer a minter capability to the minter
119: let minter_cap = MinterCap {
120: id: object::new(ctx),
121: minter,
122: };
123: transfer::public_transfer(minter_cap, minter);
124:
125: event::emit(MinterAdded { minter });
126: }
127:
128: /// Remove a minter address
129: /// Requires AdminCap
130: /// state - TokenState
131: /// minter - Address to remove from minters
132: /// ctx - Transaction context
133: /// Emits MinterRemoved event
134: public entry fun remove_minter(
135: _: &AdminCap,
136: state: &mut TokenState,
137: minter: address,
138: _ctx: &mut TxContext,
139: ) {
140: let (found, index) = vector::index_of(&state.minters, &minter);
141: assert!(found, E_NOT_IN_MINTERS_LIST);
142:
143: vector::remove(&mut state.minters, index);
144:
145: // Note: We cannot delete the MinterCap that was previously transferred,
146: // but it will no longer be valid as we've removed the minter from the state
147:
148: event::emit(MinterRemoved { minter });
149: }
150:
151: /// Add a new guardian address
152: /// Requires AdminCap
153: /// state - TokenState
154: /// guardian - Address to add as a guardian
155: /// ctx - Transaction context
156: /// Emits GuardianAdded event
157: public entry fun add_guardian(
158: _: &AdminCap,
159: state: &mut TokenState,
160: guardian: address,
161: ctx: &mut TxContext,
162: ) {
163: assert!(!is_guardian(state, guardian), E_ALREADY_GUARDIAN);
164:
165: vector::push_back(&mut state.guardians, guardian);
166:
167: // Create and transfer a guardian capability to the guardian
168: let guardian_cap = GuardianCap {
169: id: object::new(ctx),
170: guardian,
171: };
172: transfer::public_transfer(guardian_cap, guardian);
173:
174: event::emit(GuardianAdded { guardian });
175: }
176:
177: /// Remove a guardian address
178: /// Requires AdminCap
179: /// state - TokenState
180: /// guardian - Address to remove from guardians
181: /// ctx - Transaction context
182: /// Emits GuardianRemoved event
183: public entry fun remove_guardian(
184: _: &AdminCap,
185: state: &mut TokenState,
186: guardian: address,
187: _ctx: &mut TxContext,
188: ) {
189: let (found, index) = vector::index_of(&state.guardians, &guardian);
190: assert!(found, E_NOT_IN_GUARDIANS_LIST);
191:
192: vector::remove(&mut state.guardians, index);
193:
194: // Note: We cannot delete the GuardianCap that was previously transferred,
195: // but it will no longer be valid as we've removed the guardian from the state
196:
197: event::emit(GuardianRemoved { guardian });
198: }
199:
200: /// Unpause the contract (token mints and burns)
201: /// Requires AdminCap
202: /// state - TokenState
203: /// ctx - Transaction context
204: /// Emits Unpaused event
205: public entry fun unpause(_: &AdminCap, state: &mut TokenState, ctx: &mut TxContext) {
206: assert!(state.paused, E_NOT_PAUSED);
207:
208: state.paused = false;
209: event::emit(Unpaused { owner: tx_context::sender(ctx) });
210: }
211:
212: // ===== Guardian functions =====
213:
214: /// Pause the contract (token mints and burns)
215: /// Requires GuardianCap
216: /// state - TokenState
217: /// ctx - Transaction context
218: /// Emits Paused event
219: public entry fun pause(_: &GuardianCap, state: &mut TokenState, ctx: &mut TxContext) {
220: let guardian = tx_context::sender(ctx);
221: assert!(is_guardian(state, guardian), E_NOT_GUARDIAN);
222: assert!(!state.paused, E_PAUSED);
223:
224: state.paused = true;
225: event::emit(Paused { guardian });
226: }
227:
228: // ===== Minter functions =====
229:
230: /// Mint new tokens to a specified address
231: /// Requires MinterCap and TreasuryCap
232: /// state - TokenState
233: /// amount - Amount of tokens to mint
234: /// recipient - Address to mint tokens to
235: /// ctx - Transaction context
236: /// Emits TokensMinted event
237: public entry fun mint(
238: _: &MinterCap,
239: treasury_cap: &mut TreasuryCap<TBTC>,
240: state: &TokenState,
241: amount: u64,
242: recipient: address,
243: ctx: &mut TxContext,
244: ) {
245: let minter = tx_context::sender(ctx);
246: assert!(is_minter(state, minter), E_NOT_MINTER);
247: assert!(!state.paused, E_PAUSED);
248:
249: let minted_coin = coin::mint(treasury_cap, amount, ctx);
250: let minted_amount = coin::value(&minted_coin);
251: transfer::public_transfer(minted_coin, recipient);
252:
253: event::emit(TokensMinted { amount: minted_amount, recipient });
254: }
255:
256: // ===== Public functions =====
257:
258: /// Burn tokens
259: /// Requires TreasuryCap
260: /// state - TokenState
261: /// coin - Coin to burn
262: /// Emits TokensBurned event
263: public entry fun burn(
264: treasury_cap: &mut TreasuryCap<TBTC>,
265: state: &TokenState,
266: coin: Coin<TBTC>,
267: ) {
268: assert!(!state.paused, E_PAUSED);
269: let amount = coin::value(&coin);
270: coin::burn(treasury_cap, coin);
271:
272: event::emit(TokensBurned { amount});
273: }
274:
275: // ===== Test Helper functions =====
276:
277: #[test_only]
278: public fun initialize_for_test(ctx: &mut TxContext): (AdminCap, TokenState, TreasuryCap<TBTC>) {
279: // Create the TBTC token
280: let (treasury_cap, metadata) = coin::create_currency(
281: TBTC {},
282: 9, // Decimals
283: b"TBTC",
284: b"Threshold Bitcoin",
285: b"Canonical L2/sidechain token implementation for tBTC",
286: option::some(
287: url::new_unsafe_from_bytes(
288: b"https://threshold.network/images/logos/threshold-bootstrap-icon.svg",
289: ),
290: ),
291: ctx,
292: );
293:
294: // Initialize the TokenState
295: let token_state = TokenState {
296: id: object::new(ctx),
297: minters: vector::empty(),
298: guardians: vector::empty(),
299: paused: false,
300: };
301:
302: // Create the AdminCap
303: let admin_cap = AdminCap {
304: id: object::new(ctx),
305: };
306:
307: // Transfer ownership of the coin and metadata to the sender
308: transfer::public_transfer(metadata, tx_context::sender(ctx));
309:
310: // Return the created objects for testing purposes
311: (admin_cap, token_state, treasury_cap)
312: }
313:
314: // ===== Helper functions =====
315:
316: /// Check if an address is a minter
317: /// state - TokenState
318: /// addr - Address to check
319: /// Returns true if the address is a minter
320: public fun is_minter(state: &TokenState, addr: address): bool {
321: vector::contains(&state.minters, &addr)
322: }
323:
324: /// Check if an address is a guardian
325: /// state - TokenState
326: /// addr - Address to check
327: /// Returns true if the address is a guardian
328: public fun is_guardian(state: &TokenState, addr: address): bool {
329: vector::contains(&state.guardians, &addr)
330: }
331:
332: /// Get all minters
333: /// state - TokenState
334: /// Returns a vector of all minters
335: public fun get_minters(state: &TokenState): vector<address> {
336: state.minters
337: }
338:
339: /// Get all guardians
340: /// state - TokenState
341: /// Returns a vector of all guardians
342: public fun get_guardians(state: &TokenState): vector<address> {
343: state.guardians
344: }
345:
346: /// Check if the contract is paused
347: /// state - TokenState
348: /// Returns true if the contract is paused
349: public fun is_paused(state: &TokenState): bool {
350: state.paused
351: }
352: }
================
File: tests/bitcoin_depositor_tests.move
================
1: #[test_only]
2: module l2_tbtc::bitcoin_depositor_tests {
3:
4: use l2_tbtc::BitcoinDepositor;
5: use l2_tbtc::Gateway;
6: use l2_tbtc::TBTC;
7: use l2_tbtc::test_utils::message_with_payload_address;
8: use sui::clock;
9: use sui::coin::TreasuryCap;
10: use sui::test_scenario;
11: use token_bridge::coin_wrapped_12;
12: use token_bridge::setup;
13: use token_bridge::token_bridge_scenario::{
14: register_dummy_emitter,
15: set_up_wormhole_and_token_bridge,
16: two_people,
17: take_state as take_token_bridge_state,
18: return_state as return_token_bridge_state
19: };
20: use wormhole::emitter::{Self, EmitterCap};
21: use wormhole::wormhole_scenario::{
22: return_state as return_wormhole_state,
23: take_state as take_wormhole_state
24: };
25:
26: // Helper function to initialize Gateway for testing
27: public fun initialize_gateway_state(scenario: &mut test_scenario::Scenario) {
28: let treasury_cap = test_scenario::take_from_sender<TreasuryCap<TBTC::TBTC>>(scenario);
29: let minter_cap = test_scenario::take_from_sender<TBTC::MinterCap>(scenario);
30: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(scenario);
31: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(scenario);
32: let ctx = test_scenario::ctx(scenario);
33: let emitter_cap: EmitterCap = emitter::dummy();
34:
35: // Initialize gateway with wrapped token type
36: Gateway::initialize_gateway_test<coin_wrapped_12::COIN_WRAPPED_12>(
37: &mut gateway_state,
38: minter_cap,
39: treasury_cap,
40: ctx,
41: emitter_cap,
42: );
43:
44: // Verify initialization by trying to add a trusted emitter
45: let emitter_id = 2;
46: let mut emitter_address = vector::empty<u8>();
47: vector::append(
48: &mut emitter_address,
49: x"00000000000000000000000000000000000000000000000000000000deadbeef",
50: );
51:
52: Gateway::add_trusted_emitter(
53: &gateway_admin_cap,
54: &mut gateway_state,
55: emitter_id,
56: emitter_address,
57: ctx,
58: );
59:
60: assert!(Gateway::emitter_exists(&gateway_state, emitter_id));
61:
62: // Return objects
63: test_scenario::return_to_sender(scenario, gateway_admin_cap);
64: test_scenario::return_shared(gateway_state);
65: }
66:
67: // Helper function to initialize TBTC for testing
68: fun init_tbtc_for_test_scenario(scenario: &mut test_scenario::Scenario, admin: address) {
69: let (admin_cap, mut token_state, treasury_cap) = TBTC::initialize_for_test(
70: test_scenario::ctx(scenario),
71: );
72: TBTC::add_minter(&admin_cap, &mut token_state, admin, test_scenario::ctx(scenario));
73: transfer::public_transfer(token_state, admin);
74: transfer::public_transfer(treasury_cap, admin);
75: transfer::public_transfer(admin_cap, admin);
76: }
77:
78: // Helper function to initialize Wormhole for testing
79: fun init_wormhole_for_test(scenario: &mut test_scenario::Scenario, deployer: address) {
80: let expected_source_chain = 2;
81: set_up_wormhole_and_token_bridge(scenario, 1);
82: register_dummy_emitter(scenario, expected_source_chain);
83: coin_wrapped_12::init_and_register(scenario, deployer);
84: }
85:
86: #[test]
87: fun test_initialization() {
88: let (admin, coin_deployer) = two_people();
89: let mut scenario = test_scenario::begin(admin);
90:
91: // Initialize TBTC
92: test_scenario::next_tx(&mut scenario, admin);
93: {
94: init_tbtc_for_test_scenario(&mut scenario, admin);
95: };
96:
97: // Initialize Wormhole
98: test_scenario::next_tx(&mut scenario, admin);
99: {
100: init_wormhole_for_test(&mut scenario, coin_deployer);
101: };
102:
103: test_scenario::next_tx(&mut scenario, admin);
104: {
105: setup::init_test_only(test_scenario::ctx(&mut scenario));
106: };
107:
108: // Initialize BitcoinDepositor
109: test_scenario::next_tx(&mut scenario, admin);
110: {
111: BitcoinDepositor::init_test(test_scenario::ctx(&mut scenario));
112: };
113:
114: test_scenario::end(scenario);
115: }
116:
117: #[test]
118: fun test_set_trusted_emitter() {
119: let (admin, coin_deployer) = two_people();
120: let mut scenario = test_scenario::begin(admin);
121:
122: // Initialize TBTC
123: test_scenario::next_tx(&mut scenario, admin);
124: {
125: init_tbtc_for_test_scenario(&mut scenario, admin);
126: };
127:
128: // Initialize Wormhole
129: test_scenario::next_tx(&mut scenario, admin);
130: {
131: init_wormhole_for_test(&mut scenario, coin_deployer);
132: };
133:
134: test_scenario::next_tx(&mut scenario, admin);
135: {
136: setup::init_test_only(test_scenario::ctx(&mut scenario));
137: };
138:
139: // Initialize BitcoinDepositor
140: test_scenario::next_tx(&mut scenario, admin);
141: {
142: BitcoinDepositor::init_test(test_scenario::ctx(&mut scenario));
143: };
144:
145: // Set trusted emitter
146: test_scenario::next_tx(&mut scenario, admin);
147: {
148: let admin_cap = test_scenario::take_from_sender<BitcoinDepositor::AdminCap>(&scenario);
149: let mut receiver_state = test_scenario::take_shared<BitcoinDepositor::ReceiverState>(
150: &scenario,
151: );
152: let ctx = test_scenario::ctx(&mut scenario);
153:
154: let mut emitter_address = vector::empty<u8>();
155: vector::append(
156: &mut emitter_address,
157: x"0000000000000000000000000000000000000000000000000000000000000001",
158: );
159:
160: BitcoinDepositor::set_trusted_emitter(
161: &admin_cap,
162: &mut receiver_state,
163: emitter_address,
164: ctx,
165: );
166:
167: // Return objects
168: test_scenario::return_to_sender(&scenario, admin_cap);
169: test_scenario::return_shared(receiver_state);
170: };
171:
172: test_scenario::end(scenario);
173: }
174:
175: #[test]
176: fun test_initialize_deposit() {
177: let (admin, coin_deployer) = two_people();
178: let mut scenario = test_scenario::begin(admin);
179:
180: // Initialize TBTC
181: test_scenario::next_tx(&mut scenario, admin);
182: {
183: init_tbtc_for_test_scenario(&mut scenario, admin);
184: };
185:
186: // Initialize Wormhole
187: test_scenario::next_tx(&mut scenario, admin);
188: {
189: init_wormhole_for_test(&mut scenario, coin_deployer);
190: };
191:
192: test_scenario::next_tx(&mut scenario, admin);
193: {
194: setup::init_test_only(test_scenario::ctx(&mut scenario));
195: };
196:
197: // Initialize BitcoinDepositor
198: test_scenario::next_tx(&mut scenario, admin);
199: {
200: BitcoinDepositor::init_test(test_scenario::ctx(&mut scenario));
201: };
202:
203: // Initialize deposit
204: test_scenario::next_tx(&mut scenario, admin);
205: {
206: let ctx = test_scenario::ctx(&mut scenario);
207:
208: let mut funding_tx = vector::empty<u8>();
209: vector::append(&mut funding_tx, x"deadbeef");
210:
211: let mut deposit_reveal = vector::empty<u8>();
212: vector::append(&mut deposit_reveal, x"cafebabe");
213:
214: let mut deposit_owner = vector::empty<u8>();
215: vector::append(&mut deposit_owner, x"12345678");
216:
217: BitcoinDepositor::initialize_deposit(
218: funding_tx,
219: deposit_reveal,
220: deposit_owner,
221: ctx,
222: );
223: };
224:
225: test_scenario::end(scenario);
226: }
227:
228: #[test]
229: fun test_receive_wormhole_messages() {
230: let (admin, coin_deployer) = two_people();
231: let mut scenario = test_scenario::begin(admin);
232:
233: // Initialize TBTC
234: test_scenario::next_tx(&mut scenario, admin);
235: {
236: init_tbtc_for_test_scenario(&mut scenario, admin);
237: };
238:
239: // Initialize Wormhole
240: test_scenario::next_tx(&mut scenario, admin);
241: {
242: init_wormhole_for_test(&mut scenario, coin_deployer);
243: };
244:
245: test_scenario::next_tx(&mut scenario, admin);
246: {
247: setup::init_test_only(test_scenario::ctx(&mut scenario));
248: };
249:
250: // Initialize BitcoinDepositor
251: test_scenario::next_tx(&mut scenario, admin);
252: {
253: BitcoinDepositor::init_test(test_scenario::ctx(&mut scenario));
254: };
255:
256: test_scenario::next_tx(&mut scenario, admin);
257: {
258: Gateway::init_test(test_scenario::ctx(&mut scenario));
259: };
260:
261: // Initialize Gateway
262: test_scenario::next_tx(&mut scenario, admin);
263: {
264: initialize_gateway_state(&mut scenario);
265: };
266:
267: // Set trusted emitter
268: test_scenario::next_tx(&mut scenario, admin);
269: {
270: let admin_cap = test_scenario::take_from_sender<BitcoinDepositor::AdminCap>(&scenario);
271: let mut receiver_state = test_scenario::take_shared<BitcoinDepositor::ReceiverState>(
272: &scenario,
273: );
274: let ctx = test_scenario::ctx(&mut scenario);
275:
276: let mut emitter_address = vector::empty<u8>();
277: vector::append(
278: &mut emitter_address,
279: x"00000000000000000000000000000000000000000000000000000000deadbeef",
280: );
281:
282: BitcoinDepositor::set_trusted_emitter(
283: &admin_cap,
284: &mut receiver_state,
285: emitter_address,
286: ctx,
287: );
288:
289: // Return objects
290: test_scenario::return_to_sender(&scenario, admin_cap);
291: test_scenario::return_shared(receiver_state);
292: };
293:
294: // Process Wormhole message
295: test_scenario::next_tx(&mut scenario, admin);
296: {
297: let mut receiver_state = test_scenario::take_shared<BitcoinDepositor::ReceiverState>(
298: &scenario,
299: );
300: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
301: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
302: let mut treasury = test_scenario::take_shared<
303: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
304: >(&scenario);
305: let mut wormhole_state = take_wormhole_state(&scenario);
306: let mut token_bridge_state = take_token_bridge_state(&scenario);
307: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
308: let ctx = test_scenario::ctx(&mut scenario);
309:
310: let vaa_bytes = message_with_payload_address();
311:
312: let clock = clock::create_for_testing(ctx);
313:
314: BitcoinDepositor::receiveWormholeMessages<coin_wrapped_12::COIN_WRAPPED_12>(
315: &mut receiver_state,
316: &mut gateway_state,
317: &mut capabilities,
318: &mut treasury,
319: &mut wormhole_state,
320: &mut token_bridge_state,
321: &mut token_state,
322: vaa_bytes,
323: &clock,
324: ctx,
325: );
326:
327: // Return objects
328: test_scenario::return_shared(receiver_state);
329: test_scenario::return_shared(gateway_state);
330: test_scenario::return_shared(capabilities);
331: test_scenario::return_shared(treasury);
332: return_wormhole_state(wormhole_state);
333: return_token_bridge_state(token_bridge_state);
334: test_scenario::return_to_sender(&scenario, token_state);
335: clock::destroy_for_testing(clock);
336: };
337:
338: test_scenario::end(scenario);
339: }
340: }
================
File: tests/gateway_tests.move
================
1: #[test_only]
2: module l2_tbtc::gateway_tests {
3:
4: use l2_tbtc::Gateway;
5: use l2_tbtc::TBTC;
6: use l2_tbtc::test_utils::message_with_payload_address;
7: use sui::balance;
8: use sui::clock;
9: use sui::coin::{Self, TreasuryCap};
10: use sui::sui::SUI;
11: use sui::test_scenario;
12: use token_bridge::coin_wrapped_12;
13: use token_bridge::setup;
14: use token_bridge::token_bridge_scenario::{
15: register_dummy_emitter,
16: set_up_wormhole_and_token_bridge,
17: two_people,
18: take_state as take_token_bridge_state,
19: return_state as return_token_bridge_state
20: };
21: use wormhole::emitter::{Self, EmitterCap};
22: use wormhole::wormhole_scenario::{
23: return_state as return_wormhole_state,
24: take_state as take_wormhole_state
25: };
26:
27: // Helper function to initialize Wormhole for testing
28: fun init_wormhole_for_test(scenario: &mut test_scenario::Scenario, deployer: address) {
29: let expected_source_chain = 2;
30: set_up_wormhole_and_token_bridge(scenario, 1);
31: register_dummy_emitter(scenario, expected_source_chain);
32: coin_wrapped_12::init_and_register(scenario, deployer);
33: }
34:
35: fun init_tbtc_for_test_scenario(scenario: &mut test_scenario::Scenario, admin: address) {
36: let (admin_cap, mut token_state, treasury_cap) = TBTC::initialize_for_test(
37: test_scenario::ctx(scenario)
38: );
39: TBTC::add_minter(&admin_cap, &mut token_state, admin, test_scenario::ctx(scenario));
40: transfer::public_transfer(token_state, admin);
41: transfer::public_transfer(treasury_cap, admin);
42: transfer::public_transfer(admin_cap, admin);
43: }
44:
45: fun initialize_gateway_state(scenario: &mut test_scenario::Scenario) {
46: let treasury_cap = test_scenario::take_from_sender<TreasuryCap<TBTC::TBTC>>(scenario);
47: let minter_cap = test_scenario::take_from_sender<TBTC::MinterCap>(scenario);
48: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(scenario);
49: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(scenario);
50: let ctx = test_scenario::ctx(scenario);
51: let emitter_cap: EmitterCap = emitter::dummy();
52:
53: // Initialize gateway with wrapped token type
54: Gateway::initialize_gateway_test<coin_wrapped_12::COIN_WRAPPED_12>(
55: &mut gateway_state,
56: minter_cap,
57: treasury_cap,
58: ctx,
59: emitter_cap,
60: );
61:
62: // Verify initialization by trying to add a trusted emitter
63: let emitter_id = 2;
64: let mut emitter_address = vector::empty<u8>();
65: vector::append(
66: &mut emitter_address,
67: x"00000000000000000000000000000000000000000000000000000000deadbeef",
68: );
69:
70: Gateway::add_trusted_emitter(
71: &gateway_admin_cap,
72: &mut gateway_state,
73: emitter_id,
74: emitter_address,
75: ctx,
76: );
77:
78: assert!(Gateway::emitter_exists(&gateway_state, emitter_id));
79:
80: // Return objects
81: test_scenario::return_to_sender(scenario, gateway_admin_cap);
82: test_scenario::return_shared(gateway_state);
83: }
84:
85: // Function to create a Coin<SUI> for testing purposes
86: public fun create_sui_coin(amount: u64, ctx: &mut TxContext): coin::Coin<SUI> {
87: let balance = balance::create_for_testing<SUI>(amount);
88: coin::from_balance(balance, ctx)
89: }
90:
91: #[test]
92: fun test_initialization() {
93: let (admin, coin_deployer) = two_people();
94: let mut scenario = test_scenario::begin(admin);
95:
96: // Initialize TBTC
97: test_scenario::next_tx(&mut scenario, admin);
98: {
99: init_tbtc_for_test_scenario(&mut scenario, admin);
100: };
101:
102: // Initialize Wormhole
103: test_scenario::next_tx(&mut scenario, admin);
104: {
105: init_wormhole_for_test(&mut scenario, coin_deployer);
106: };
107:
108: test_scenario::next_tx(&mut scenario, admin);
109: {
110: setup::init_test_only(test_scenario::ctx(&mut scenario));
111: };
112:
113: test_scenario::next_tx(&mut scenario, admin);
114: {
115: Gateway::init_test(test_scenario::ctx(&mut scenario));
116: };
117:
118: // Initialize Gateway
119: test_scenario::next_tx(&mut scenario, admin);
120: {
121: let treasury_cap = test_scenario::take_from_sender<TreasuryCap<TBTC::TBTC>>(&scenario);
122: let minter_cap = test_scenario::take_from_sender<TBTC::MinterCap>(&scenario);
123: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
124: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
125: let wormhole_state = take_wormhole_state(&scenario);
126: let ctx = test_scenario::ctx(&mut scenario);
127:
128: // Initialize gateway with wrapped token type
129: Gateway::initialize_gateway<coin_wrapped_12::COIN_WRAPPED_12>(
130: &gateway_admin_cap,
131: &mut gateway_state,
132: &wormhole_state,
133: minter_cap,
134: treasury_cap,
135: ctx,
136: );
137:
138: // Return objects
139: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
140: test_scenario::return_shared(gateway_state);
141: return_wormhole_state(wormhole_state);
142: };
143:
144: test_scenario::end(scenario);
145: }
146:
147: #[test]
148: fun test_trusted_emitter_management() {
149: let (admin, coin_deployer) = two_people();
150: let mut scenario = test_scenario::begin(admin);
151:
152: // Initialize TBTC
153: test_scenario::next_tx(&mut scenario, admin);
154: {
155: init_tbtc_for_test_scenario(&mut scenario, admin);
156: };
157:
158: // Initialize Wormhole
159: test_scenario::next_tx(&mut scenario, admin);
160: {
161: init_wormhole_for_test(&mut scenario, coin_deployer);
162: };
163:
164: test_scenario::next_tx(&mut scenario, admin);
165: {
166: setup::init_test_only(test_scenario::ctx(&mut scenario));
167: };
168:
169: test_scenario::next_tx(&mut scenario, admin);
170: {
171: Gateway::init_test(test_scenario::ctx(&mut scenario));
172: };
173:
174: // Initialize Gateway
175: test_scenario::next_tx(&mut scenario, admin);
176: {
177: initialize_gateway_state(&mut scenario);
178: };
179:
180: test_scenario::end(scenario);
181: }
182:
183: #[test]
184: fun test_removing_emitter() {
185: let (admin, coin_deployer) = two_people();
186: let mut scenario = test_scenario::begin(admin);
187:
188: // Initialize TBTC
189: test_scenario::next_tx(&mut scenario, admin);
190: {
191: init_tbtc_for_test_scenario(&mut scenario, admin);
192: };
193:
194: // Initialize Wormhole
195: test_scenario::next_tx(&mut scenario, admin);
196: {
197: init_wormhole_for_test(&mut scenario, coin_deployer);
198: };
199:
200: test_scenario::next_tx(&mut scenario, admin);
201: {
202: setup::init_test_only(test_scenario::ctx(&mut scenario));
203: };
204:
205: test_scenario::next_tx(&mut scenario, admin);
206: {
207: Gateway::init_test(test_scenario::ctx(&mut scenario));
208: };
209:
210: // Initialize Gateway
211: test_scenario::next_tx(&mut scenario, admin);
212: {
213: initialize_gateway_state(&mut scenario);
214: };
215:
216: // Remove emitter
217: test_scenario::next_tx(&mut scenario, admin);
218: {
219: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
220: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
221: let ctx = test_scenario::ctx(&mut scenario);
222:
223: let emitter_id = 2;
224:
225: Gateway::remove_trusted_emitter(
226: &gateway_admin_cap,
227: &mut gateway_state,
228: emitter_id,
229: ctx,
230: );
231:
232: assert!(!Gateway::emitter_exists(&gateway_state, emitter_id));
233:
234: // Return objects
235: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
236: test_scenario::return_shared(gateway_state);
237: };
238:
239: test_scenario::end(scenario);
240: }
241:
242: #[test]
243: fun test_pause() {
244: let (admin, coin_deployer) = two_people();
245: let mut scenario = test_scenario::begin(admin);
246:
247: // Initialize TBTC
248: test_scenario::next_tx(&mut scenario, admin);
249: {
250: init_tbtc_for_test_scenario(&mut scenario, admin);
251: };
252:
253: // Initialize Wormhole
254: test_scenario::next_tx(&mut scenario, admin);
255: {
256: init_wormhole_for_test(&mut scenario, coin_deployer);
257: };
258:
259: test_scenario::next_tx(&mut scenario, admin);
260: {
261: setup::init_test_only(test_scenario::ctx(&mut scenario));
262: };
263:
264: test_scenario::next_tx(&mut scenario, admin);
265: {
266: Gateway::init_test(test_scenario::ctx(&mut scenario));
267: };
268:
269: // Initialize Gateway
270: test_scenario::next_tx(&mut scenario, admin);
271: {
272: initialize_gateway_state(&mut scenario);
273: };
274:
275: // Pause gateway
276: test_scenario::next_tx(&mut scenario, admin);
277: {
278: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
279: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
280: let ctx = test_scenario::ctx(&mut scenario);
281:
282: Gateway::pause(&gateway_admin_cap, &mut gateway_state, ctx);
283:
284: assert!(Gateway::is_paused(&gateway_state));
285:
286: // Return objects
287: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
288: test_scenario::return_shared(gateway_state);
289: };
290:
291: test_scenario::end(scenario);
292: }
293:
294: #[test]
295: fun test_unpause() {
296: let (admin, coin_deployer) = two_people();
297: let mut scenario = test_scenario::begin(admin);
298:
299: // Initialize TBTC
300: test_scenario::next_tx(&mut scenario, admin);
301: {
302: init_tbtc_for_test_scenario(&mut scenario, admin);
303: };
304:
305: // Initialize Wormhole
306: test_scenario::next_tx(&mut scenario, admin);
307: {
308: init_wormhole_for_test(&mut scenario, coin_deployer);
309: };
310:
311: test_scenario::next_tx(&mut scenario, admin);
312: {
313: setup::init_test_only(test_scenario::ctx(&mut scenario));
314: };
315:
316: test_scenario::next_tx(&mut scenario, admin);
317: {
318: Gateway::init_test(test_scenario::ctx(&mut scenario));
319: };
320:
321: // Initialize Gateway
322: test_scenario::next_tx(&mut scenario, admin);
323: {
324: initialize_gateway_state(&mut scenario);
325: };
326:
327: // Pause gateway
328: test_scenario::next_tx(&mut scenario, admin);
329: {
330: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
331: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
332: let ctx = test_scenario::ctx(&mut scenario);
333:
334: Gateway::pause(&gateway_admin_cap, &mut gateway_state, ctx);
335:
336: assert!(Gateway::is_paused(&gateway_state));
337:
338: // Return objects
339: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
340: test_scenario::return_shared(gateway_state);
341: };
342:
343: // Unpause gateway
344: test_scenario::next_tx(&mut scenario, admin);
345: {
346: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
347: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
348: let ctx = test_scenario::ctx(&mut scenario);
349:
350: Gateway::unpause(&gateway_admin_cap, &mut gateway_state, ctx);
351:
352: assert!(!Gateway::is_paused(&gateway_state));
353:
354: // Return objects
355: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
356: test_scenario::return_shared(gateway_state);
357: };
358:
359: test_scenario::end(scenario);
360: }
361:
362: #[test]
363: fun test_send_wrapped_tokens() {
364: let (admin, _coin_deployer) = two_people();
365: let mut scenario = test_scenario::begin(admin);
366: let wormhole_fee = 1;
367:
368: // Initialize TBTC
369: test_scenario::next_tx(&mut scenario, admin);
370: {
371: init_tbtc_for_test_scenario(&mut scenario, admin);
372: };
373:
374: // Initialize Wormhole
375: test_scenario::next_tx(&mut scenario, admin);
376: {
377: let expected_source_chain = 2;
378: set_up_wormhole_and_token_bridge(&mut scenario, wormhole_fee);
379: register_dummy_emitter(&mut scenario, expected_source_chain);
380: };
381:
382: test_scenario::next_tx(&mut scenario, admin);
383: {
384: Gateway::init_test(test_scenario::ctx(&mut scenario));
385: };
386:
387: // Initialize Gateway
388: test_scenario::next_tx(&mut scenario, admin);
389: {
390: initialize_gateway_state(&mut scenario);
391: };
392:
393: let coin_wrapped = coin_wrapped_12::init_register_and_mint(
394: &mut scenario,
395: admin,
396: 1000000000000000,
397: );
398:
399: // register receiver
400: test_scenario::next_tx(&mut scenario, admin);
401: {
402: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
403: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
404: let ctx = test_scenario::ctx(&mut scenario);
405:
406: Gateway::add_trusted_receiver(
407: &gateway_admin_cap,
408: &mut gateway_state,
409: 2,
410: x"0000000000000000000000000000000000000000000000000000000000000001",
411: ctx,
412: );
413:
414: // Return objects
415: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
416: test_scenario::return_shared(gateway_state);
417: };
418:
419: // Send wrapped tokens
420: test_scenario::next_tx(&mut scenario, admin);
421: {
422: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
423: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
424: let mut token_bridge_state = take_token_bridge_state(&scenario);
425: let mut wormhole_state = take_wormhole_state(&scenario);
426: let ctx = test_scenario::ctx(&mut scenario);
427:
428: let recipient_chain = 2;
429: let mut recipient_address = vector::empty<u8>();
430: vector::append(
431: &mut recipient_address,
432: x"0000000000000000000000000000000000000000000000000000000000000001",
433: );
434:
435: // get sui native coins
436: let sui_coin = create_sui_coin(wormhole_fee, ctx);
437: let clock = clock::create_for_testing(ctx);
438:
439: Gateway::send_wrapped_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
440: &mut gateway_state,
441: &mut capabilities,
442: &mut token_bridge_state,
443: &mut wormhole_state,
444: recipient_chain,
445: recipient_address,
446: coin::from_balance(coin_wrapped, ctx),
447: 1,
448: sui_coin,
449: &clock,
450: ctx,
451: );
452:
453: test_scenario::return_shared(gateway_state);
454: test_scenario::return_shared(capabilities);
455: clock::destroy_for_testing(clock);
456: return_wormhole_state(wormhole_state);
457: return_token_bridge_state(token_bridge_state);
458: };
459: test_scenario::end(scenario);
460: }
461:
462: // Test redeem with dummy message
463: #[test]
464: fun test_redeem_with_dummy_message() {
465: let (admin, _coin_deployer) = two_people();
466: let mut scenario = test_scenario::begin(admin);
467: let wormhole_fee = 1;
468:
469: // Initialize TBTC
470: test_scenario::next_tx(&mut scenario, admin);
471: {
472: init_tbtc_for_test_scenario(&mut scenario, admin);
473: };
474:
475: // Initialize Wormhole
476: test_scenario::next_tx(&mut scenario, admin);
477: {
478: let expected_source_chain = 2;
479: set_up_wormhole_and_token_bridge(&mut scenario, wormhole_fee);
480: register_dummy_emitter(&mut scenario, expected_source_chain);
481: coin_wrapped_12::init_and_register(&mut scenario, admin);
482: };
483:
484: test_scenario::next_tx(&mut scenario, admin);
485: {
486: Gateway::init_test(test_scenario::ctx(&mut scenario));
487: };
488:
489: // Initialize Gateway
490: test_scenario::next_tx(&mut scenario, admin);
491: {
492: initialize_gateway_state(&mut scenario);
493: };
494:
495: // redeem with dummy message
496: test_scenario::next_tx(&mut scenario, admin);
497: {
498: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
499: let mut token_bridge_state = take_token_bridge_state(&scenario);
500: let mut wormhole_state = take_wormhole_state(&scenario);
501: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
502: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
503: let mut treasury = test_scenario::take_shared<
504: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
505: >(&scenario);
506:
507: let ctx = test_scenario::ctx(&mut scenario);
508:
509: let vaa_bytes = message_with_payload_address();
510:
511: let clock = clock::create_for_testing(ctx);
512:
513: Gateway::redeem_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
514: &mut gateway_state,
515: &mut capabilities,
516: &mut wormhole_state,
517: &mut treasury,
518: &mut token_bridge_state,
519: &mut token_state,
520: vaa_bytes,
521: &clock,
522: ctx,
523: );
524:
525: test_scenario::return_shared(gateway_state);
526: test_scenario::return_shared(capabilities);
527: clock::destroy_for_testing(clock);
528: return_wormhole_state(wormhole_state);
529: return_token_bridge_state(token_bridge_state);
530: test_scenario::return_shared(treasury);
531: test_scenario::return_to_sender(&scenario, token_state);
532: };
533:
534: test_scenario::next_tx(&mut scenario, admin);
535: {
536: // get minted amount
537: let gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
538: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
539:
540: let amount = Gateway::get_minted_amount(&gateway_state);
541: assert!(amount == 3000, 100);
542:
543: // verify minted amount
544: let amount_tbtc = Gateway::get_total_supply(&mut capabilities);
545: assert!(amount_tbtc == 3000, 100);
546:
547: test_scenario::return_shared(gateway_state);
548: test_scenario::return_shared(capabilities);
549: };
550:
551: test_scenario::end(scenario);
552: }
553:
554: // Test redeem with dummy message
555: #[test]
556: fun test_redeem_wrapped_with_dummy_message() {
557: let (admin, _coin_deployer) = two_people();
558: let mut scenario = test_scenario::begin(admin);
559: let wormhole_fee = 1;
560:
561: // Initialize TBTC
562: test_scenario::next_tx(&mut scenario, admin);
563: {
564: init_tbtc_for_test_scenario(&mut scenario, admin);
565: };
566:
567: // Initialize Wormhole
568: test_scenario::next_tx(&mut scenario, admin);
569: {
570: let expected_source_chain = 2;
571: set_up_wormhole_and_token_bridge(&mut scenario, wormhole_fee);
572: register_dummy_emitter(&mut scenario, expected_source_chain);
573: coin_wrapped_12::init_and_register(&mut scenario, admin);
574: };
575:
576: test_scenario::next_tx(&mut scenario, admin);
577: {
578: Gateway::init_test(test_scenario::ctx(&mut scenario));
579: };
580:
581: // Initialize Gateway
582: test_scenario::next_tx(&mut scenario, admin);
583: {
584: initialize_gateway_state(&mut scenario);
585: };
586:
587: // Lower the minting limit
588: test_scenario::next_tx(&mut scenario, admin);
589: {
590: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
591: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
592: let ctx = test_scenario::ctx(&mut scenario);
593:
594: Gateway::update_minting_limit(&gateway_admin_cap, &mut gateway_state, 1, ctx);
595:
596: // Return objects
597: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
598: test_scenario::return_shared(gateway_state);
599: };
600:
601:
602: // redeem with dummy message
603: test_scenario::next_tx(&mut scenario, admin);
604: {
605: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
606: let mut token_bridge_state = take_token_bridge_state(&scenario);
607: let mut wormhole_state = take_wormhole_state(&scenario);
608: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
609: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
610: let mut treasury = test_scenario::take_shared<
611: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
612: >(&scenario);
613:
614: let ctx = test_scenario::ctx(&mut scenario);
615:
616: let vaa_bytes = message_with_payload_address();
617:
618: let clock = clock::create_for_testing(ctx);
619:
620: Gateway::redeem_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
621: &mut gateway_state,
622: &mut capabilities,
623: &mut wormhole_state,
624: &mut treasury,
625: &mut token_bridge_state,
626: &mut token_state,
627: vaa_bytes,
628: &clock,
629: ctx,
630: );
631:
632: test_scenario::return_shared(gateway_state);
633: test_scenario::return_shared(capabilities);
634: clock::destroy_for_testing(clock);
635: return_wormhole_state(wormhole_state);
636: return_token_bridge_state(token_bridge_state);
637: test_scenario::return_shared(treasury);
638: test_scenario::return_to_sender(&scenario, token_state);
639: };
640:
641: test_scenario::next_tx(&mut scenario, admin);
642: {
643: let gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
644: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
645:
646: // Minted amount should remain 0
647: let amount = Gateway::get_minted_amount(&gateway_state);
648: assert!(amount == 0, 100);
649:
650: // verify no tbtc minted
651: let amount_tbtc = Gateway::get_total_supply(&mut capabilities);
652: assert!(amount_tbtc == 0, 100);
653:
654: test_scenario::return_shared(gateway_state);
655: test_scenario::return_shared(capabilities);
656: };
657:
658: test_scenario::end(scenario);
659: }
660:
661: #[test]
662: #[expected_failure(abort_code = Gateway::E_MESSAGE_ALREADY_PROCESSED)]
663: fun test_redeem_wrapped_with_dummy_message_already_processed() {
664: let (admin, _coin_deployer) = two_people();
665: let mut scenario = test_scenario::begin(admin);
666: let wormhole_fee = 1;
667:
668: // Initialize TBTC
669: test_scenario::next_tx(&mut scenario, admin);
670: {
671: init_tbtc_for_test_scenario(&mut scenario, admin);
672: };
673:
674: // Initialize Wormhole
675: test_scenario::next_tx(&mut scenario, admin);
676: {
677: let expected_source_chain = 2;
678: set_up_wormhole_and_token_bridge(&mut scenario, wormhole_fee);
679: register_dummy_emitter(&mut scenario, expected_source_chain);
680: coin_wrapped_12::init_and_register(&mut scenario, admin);
681: };
682:
683: test_scenario::next_tx(&mut scenario, admin);
684: {
685: Gateway::init_test(test_scenario::ctx(&mut scenario));
686: };
687:
688: // Initialize Gateway
689: test_scenario::next_tx(&mut scenario, admin);
690: {
691: initialize_gateway_state(&mut scenario);
692: };
693:
694: // Lower the minting limit
695: test_scenario::next_tx(&mut scenario, admin);
696: {
697: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
698: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
699: let ctx = test_scenario::ctx(&mut scenario);
700:
701: Gateway::update_minting_limit(&gateway_admin_cap, &mut gateway_state, 1, ctx);
702:
703: // Return objects
704: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
705: test_scenario::return_shared(gateway_state);
706: };
707:
708: // redeem with dummy message
709: test_scenario::next_tx(&mut scenario, admin);
710: {
711: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
712: let mut token_bridge_state = take_token_bridge_state(&scenario);
713: let mut wormhole_state = take_wormhole_state(&scenario);
714: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
715: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
716: let mut treasury = test_scenario::take_shared<
717: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
718: >(&scenario);
719:
720: let ctx = test_scenario::ctx(&mut scenario);
721:
722: let vaa_bytes = message_with_payload_address();
723:
724: let clock = clock::create_for_testing(ctx);
725:
726: Gateway::redeem_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
727: &mut gateway_state,
728: &mut capabilities,
729: &mut wormhole_state,
730: &mut treasury,
731: &mut token_bridge_state,
732: &mut token_state,
733: vaa_bytes,
734: &clock,
735: ctx,
736: );
737:
738: test_scenario::return_shared(gateway_state);
739: test_scenario::return_shared(capabilities);
740: clock::destroy_for_testing(clock);
741: return_wormhole_state(wormhole_state);
742: return_token_bridge_state(token_bridge_state);
743: test_scenario::return_shared(treasury);
744: test_scenario::return_to_sender(&scenario, token_state);
745: };
746:
747: test_scenario::next_tx(&mut scenario, admin);
748: {
749: let gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
750: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
751:
752: // Minted amount should remain 0
753: let amount = Gateway::get_minted_amount(&gateway_state);
754: assert!(amount == 0, 100);
755:
756: // verify no tbtc minted
757: let amount_tbtc = Gateway::get_total_supply(&mut capabilities);
758: assert!(amount_tbtc == 0, 100);
759:
760: test_scenario::return_shared(gateway_state);
761: test_scenario::return_shared(capabilities);
762: };
763:
764: // redeem with dummy message
765: test_scenario::next_tx(&mut scenario, admin);
766: {
767: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
768: let mut token_bridge_state = take_token_bridge_state(&scenario);
769: let mut wormhole_state = take_wormhole_state(&scenario);
770: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
771: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
772: let mut treasury = test_scenario::take_shared<
773: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
774: >(&scenario);
775:
776: let ctx = test_scenario::ctx(&mut scenario);
777:
778: let vaa_bytes = message_with_payload_address();
779:
780: let clock = clock::create_for_testing(ctx);
781:
782: Gateway::redeem_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
783: &mut gateway_state,
784: &mut capabilities,
785: &mut wormhole_state,
786: &mut treasury,
787: &mut token_bridge_state,
788: &mut token_state,
789: vaa_bytes,
790: &clock,
791: ctx,
792: );
793:
794: test_scenario::return_shared(gateway_state);
795: test_scenario::return_shared(capabilities);
796: clock::destroy_for_testing(clock);
797: return_wormhole_state(wormhole_state);
798: return_token_bridge_state(token_bridge_state);
799: test_scenario::return_shared(treasury);
800: test_scenario::return_to_sender(&scenario, token_state);
801: };
802:
803: test_scenario::end(scenario);
804: }
805:
806: #[test]
807: #[expected_failure(abort_code = Gateway::E_INVALID_CHAIN_ID)]
808: fun test_invalid_chain() {
809: let (admin, _coin_deployer) = two_people();
810: let mut scenario = test_scenario::begin(admin);
811: let wormhole_fee = 1;
812:
813: // Initialize TBTC
814: test_scenario::next_tx(&mut scenario, admin);
815: {
816: init_tbtc_for_test_scenario(&mut scenario, admin);
817: };
818:
819: // Initialize Wormhole
820: test_scenario::next_tx(&mut scenario, admin);
821: {
822: let expected_source_chain = 2;
823: set_up_wormhole_and_token_bridge(&mut scenario, wormhole_fee);
824: register_dummy_emitter(&mut scenario, expected_source_chain);
825: coin_wrapped_12::init_and_register(&mut scenario, admin);
826: };
827:
828: test_scenario::next_tx(&mut scenario, admin);
829: {
830: Gateway::init_test(test_scenario::ctx(&mut scenario));
831: };
832:
833: // Initialize Gateway
834: test_scenario::next_tx(&mut scenario, admin);
835: {
836: let treasury_cap = test_scenario::take_from_sender<TreasuryCap<TBTC::TBTC>>(&scenario);
837: let minter_cap = test_scenario::take_from_sender<TBTC::MinterCap>(&scenario);
838: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
839: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
840: let ctx = test_scenario::ctx(&mut scenario);
841: let emitter_cap: EmitterCap = emitter::dummy();
842:
843: // Initialize gateway with wrapped token type
844: Gateway::initialize_gateway_test<coin_wrapped_12::COIN_WRAPPED_12>(
845: &mut gateway_state,
846: minter_cap,
847: treasury_cap,
848: ctx,
849: emitter_cap,
850: );
851:
852: // Verify initialization by trying to add a trusted emitter
853: let emitter_id = 4;
854: let mut emitter_address = vector::empty<u8>();
855: vector::append(
856: &mut emitter_address,
857: x"00000000000000000000000000000000000000000000000000000000deadbeef",
858: );
859:
860: Gateway::add_trusted_emitter(
861: &gateway_admin_cap,
862: &mut gateway_state,
863: emitter_id,
864: emitter_address,
865: ctx,
866: );
867:
868: assert!(Gateway::emitter_exists(&gateway_state, emitter_id));
869:
870: // Return objects
871: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
872: test_scenario::return_shared(gateway_state);
873: };
874:
875: // Lower the minting limit
876: test_scenario::next_tx(&mut scenario, admin);
877: {
878: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
879: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
880: let ctx = test_scenario::ctx(&mut scenario);
881:
882: Gateway::update_minting_limit(&gateway_admin_cap, &mut gateway_state, 1, ctx);
883:
884: // Return objects
885: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
886: test_scenario::return_shared(gateway_state);
887: };
888:
889: // redeem with dummy message
890: test_scenario::next_tx(&mut scenario, admin);
891: {
892: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
893: let mut token_bridge_state = take_token_bridge_state(&scenario);
894: let mut wormhole_state = take_wormhole_state(&scenario);
895: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
896: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
897: let mut treasury = test_scenario::take_shared<
898: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
899: >(&scenario);
900:
901: let ctx = test_scenario::ctx(&mut scenario);
902:
903: let vaa_bytes = message_with_payload_address();
904:
905: let clock = clock::create_for_testing(ctx);
906:
907: Gateway::redeem_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
908: &mut gateway_state,
909: &mut capabilities,
910: &mut wormhole_state,
911: &mut treasury,
912: &mut token_bridge_state,
913: &mut token_state,
914: vaa_bytes,
915: &clock,
916: ctx,
917: );
918:
919: test_scenario::return_shared(gateway_state);
920: test_scenario::return_shared(capabilities);
921: clock::destroy_for_testing(clock);
922: return_wormhole_state(wormhole_state);
923: return_token_bridge_state(token_bridge_state);
924: test_scenario::return_shared(treasury);
925: test_scenario::return_to_sender(&scenario, token_state);
926: };
927:
928: test_scenario::end(scenario);
929: }
930:
931: #[test]
932: #[expected_failure(abort_code = Gateway::E_INVALID_SENDER)]
933: fun test_invalid_sender() {
934: let (admin, _coin_deployer) = two_people();
935: let mut scenario = test_scenario::begin(admin);
936: let wormhole_fee = 1;
937:
938: // Initialize TBTC
939: test_scenario::next_tx(&mut scenario, admin);
940: {
941: init_tbtc_for_test_scenario(&mut scenario, admin);
942: };
943:
944: // Initialize Wormhole
945: test_scenario::next_tx(&mut scenario, admin);
946: {
947: let expected_source_chain = 2;
948: set_up_wormhole_and_token_bridge(&mut scenario, wormhole_fee);
949: register_dummy_emitter(&mut scenario, expected_source_chain);
950: coin_wrapped_12::init_and_register(&mut scenario, admin);
951: };
952:
953: test_scenario::next_tx(&mut scenario, admin);
954: {
955: Gateway::init_test(test_scenario::ctx(&mut scenario));
956: };
957:
958: // Initialize Gateway
959: test_scenario::next_tx(&mut scenario, admin);
960: {
961: let treasury_cap = test_scenario::take_from_sender<TreasuryCap<TBTC::TBTC>>(&scenario);
962: let minter_cap = test_scenario::take_from_sender<TBTC::MinterCap>(&scenario);
963: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
964: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
965: let ctx = test_scenario::ctx(&mut scenario);
966: let emitter_cap: EmitterCap = emitter::dummy();
967:
968: // Initialize gateway with wrapped token type
969: Gateway::initialize_gateway_test<coin_wrapped_12::COIN_WRAPPED_12>(
970: &mut gateway_state,
971: minter_cap,
972: treasury_cap,
973: ctx,
974: emitter_cap,
975: );
976:
977: // Verify initialization by trying to add a trusted emitter
978: let emitter_id = 2;
979: let mut emitter_address = vector::empty<u8>();
980: vector::append(
981: &mut emitter_address,
982: x"00000000000000000000000000000000000000000000000000000000deadbeee",
983: );
984:
985: Gateway::add_trusted_emitter(
986: &gateway_admin_cap,
987: &mut gateway_state,
988: emitter_id,
989: emitter_address,
990: ctx,
991: );
992:
993: assert!(Gateway::emitter_exists(&gateway_state, emitter_id));
994:
995: // Return objects
996: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
997: test_scenario::return_shared(gateway_state);
998: };
999:
1000: // Lower the minting limit
1001: test_scenario::next_tx(&mut scenario, admin);
1002: {
1003: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
1004: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1005: let ctx = test_scenario::ctx(&mut scenario);
1006:
1007: Gateway::update_minting_limit(&gateway_admin_cap, &mut gateway_state, 1, ctx);
1008:
1009: // Return objects
1010: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
1011: test_scenario::return_shared(gateway_state);
1012: };
1013:
1014: // redeem with dummy message
1015: test_scenario::next_tx(&mut scenario, admin);
1016: {
1017: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1018: let mut token_bridge_state = take_token_bridge_state(&scenario);
1019: let mut wormhole_state = take_wormhole_state(&scenario);
1020: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
1021: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
1022: let mut treasury = test_scenario::take_shared<
1023: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
1024: >(&scenario);
1025:
1026: let ctx = test_scenario::ctx(&mut scenario);
1027:
1028: let vaa_bytes = message_with_payload_address();
1029:
1030: let clock = clock::create_for_testing(ctx);
1031:
1032: Gateway::redeem_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
1033: &mut gateway_state,
1034: &mut capabilities,
1035: &mut wormhole_state,
1036: &mut treasury,
1037: &mut token_bridge_state,
1038: &mut token_state,
1039: vaa_bytes,
1040: &clock,
1041: ctx,
1042: );
1043:
1044: test_scenario::return_shared(gateway_state);
1045: test_scenario::return_shared(capabilities);
1046: clock::destroy_for_testing(clock);
1047: return_wormhole_state(wormhole_state);
1048: return_token_bridge_state(token_bridge_state);
1049: test_scenario::return_shared(treasury);
1050: test_scenario::return_to_sender(&scenario, token_state);
1051: };
1052:
1053: test_scenario::end(scenario);
1054: }
1055:
1056: #[test]
1057: #[expected_failure(abort_code = Gateway::E_PAUSED)]
1058: fun test_paused_error() {
1059: let (admin, coin_deployer) = two_people();
1060: let mut scenario = test_scenario::begin(admin);
1061:
1062: // Initialize TBTC
1063: test_scenario::next_tx(&mut scenario, admin);
1064: {
1065: init_tbtc_for_test_scenario(&mut scenario, admin);
1066: };
1067:
1068: // Initialize Wormhole
1069: test_scenario::next_tx(&mut scenario, admin);
1070: {
1071: init_wormhole_for_test(&mut scenario, coin_deployer);
1072: };
1073:
1074: test_scenario::next_tx(&mut scenario, admin);
1075: {
1076: setup::init_test_only(test_scenario::ctx(&mut scenario));
1077: };
1078:
1079: test_scenario::next_tx(&mut scenario, admin);
1080: {
1081: Gateway::init_test(test_scenario::ctx(&mut scenario));
1082: };
1083:
1084: // Initialize Gateway
1085: test_scenario::next_tx(&mut scenario, admin);
1086: {
1087: initialize_gateway_state(&mut scenario);
1088: };
1089:
1090: // Pause gateway
1091: test_scenario::next_tx(&mut scenario, admin);
1092: {
1093: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
1094: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1095: let ctx = test_scenario::ctx(&mut scenario);
1096:
1097: Gateway::pause(&gateway_admin_cap, &mut gateway_state, ctx);
1098:
1099: assert!(Gateway::is_paused(&gateway_state));
1100:
1101: // Return objects
1102: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
1103: test_scenario::return_shared(gateway_state);
1104: };
1105:
1106: // redeem with dummy message
1107: test_scenario::next_tx(&mut scenario, admin);
1108: {
1109: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1110: let mut token_bridge_state = take_token_bridge_state(&scenario);
1111: let mut wormhole_state = take_wormhole_state(&scenario);
1112: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
1113: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
1114: let mut treasury = test_scenario::take_shared<
1115: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
1116: >(&scenario);
1117:
1118: let ctx = test_scenario::ctx(&mut scenario);
1119:
1120: let vaa_bytes = message_with_payload_address();
1121:
1122: let clock = clock::create_for_testing(ctx);
1123:
1124: Gateway::redeem_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
1125: &mut gateway_state,
1126: &mut capabilities,
1127: &mut wormhole_state,
1128: &mut treasury,
1129: &mut token_bridge_state,
1130: &mut token_state,
1131: vaa_bytes,
1132: &clock,
1133: ctx,
1134: );
1135:
1136: test_scenario::return_shared(gateway_state);
1137: test_scenario::return_shared(capabilities);
1138: clock::destroy_for_testing(clock);
1139: return_wormhole_state(wormhole_state);
1140: return_token_bridge_state(token_bridge_state);
1141: test_scenario::return_shared(treasury);
1142: test_scenario::return_to_sender(&scenario, token_state);
1143: };
1144:
1145:
1146: test_scenario::end(scenario);
1147: }
1148:
1149: #[test]
1150: fun test_constants() {
1151: let (admin, coin_deployer) = two_people();
1152: let mut scenario = test_scenario::begin(admin);
1153:
1154: // Initialize TBTC
1155: test_scenario::next_tx(&mut scenario, admin);
1156: {
1157: init_tbtc_for_test_scenario(&mut scenario, admin);
1158: };
1159:
1160: // Initialize Wormhole
1161: test_scenario::next_tx(&mut scenario, admin);
1162: {
1163: init_wormhole_for_test(&mut scenario, coin_deployer);
1164: };
1165:
1166: test_scenario::next_tx(&mut scenario, admin);
1167: {
1168: setup::init_test_only(test_scenario::ctx(&mut scenario));
1169: };
1170:
1171: test_scenario::next_tx(&mut scenario, admin);
1172: {
1173: Gateway::init_test(test_scenario::ctx(&mut scenario));
1174: };
1175:
1176:
1177:
1178: // Test initialization state
1179: test_scenario::next_tx(&mut scenario, admin);
1180: {
1181: let gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1182: assert!(!Gateway::is_initialized(&gateway_state), 0);
1183: assert!(!Gateway::is_paused(&gateway_state), 0);
1184: assert!(Gateway::get_minting_limit(&gateway_state) == 1000000000000000000, 0);
1185: assert!(Gateway::get_minted_amount(&gateway_state) == 0, 0);
1186: test_scenario::return_shared(gateway_state);
1187: };
1188:
1189: test_scenario::end(scenario);
1190: }
1191:
1192: #[test]
1193: #[expected_failure(abort_code = Gateway::E_NOT_INITIALIZED)]
1194: fun test_not_initialized_error() {
1195: let (admin, coin_deployer) = two_people();
1196: let mut scenario = test_scenario::begin(admin);
1197:
1198: // Initialize TBTC
1199: test_scenario::next_tx(&mut scenario, admin);
1200: {
1201: init_tbtc_for_test_scenario(&mut scenario, admin);
1202: };
1203:
1204: // Initialize Wormhole
1205: test_scenario::next_tx(&mut scenario, admin);
1206: {
1207: init_wormhole_for_test(&mut scenario, coin_deployer);
1208: };
1209:
1210: test_scenario::next_tx(&mut scenario, admin);
1211: {
1212: setup::init_test_only(test_scenario::ctx(&mut scenario));
1213: };
1214:
1215: test_scenario::next_tx(&mut scenario, admin);
1216: {
1217: Gateway::init_test(test_scenario::ctx(&mut scenario));
1218: };
1219:
1220: // Try to add trusted emitter before initialization
1221: test_scenario::next_tx(&mut scenario, admin);
1222: {
1223: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
1224: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1225: let ctx = test_scenario::ctx(&mut scenario);
1226:
1227: let emitter_id = 2;
1228: let mut emitter_address = vector::empty<u8>();
1229: vector::append(
1230: &mut emitter_address,
1231: x"00000000000000000000000000000000000000000000000000000000deadbeef",
1232: );
1233:
1234: Gateway::add_trusted_emitter(
1235: &gateway_admin_cap,
1236: &mut gateway_state,
1237: emitter_id,
1238: emitter_address,
1239: ctx,
1240: );
1241:
1242: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
1243: test_scenario::return_shared(gateway_state);
1244: };
1245:
1246: test_scenario::end(scenario);
1247: }
1248:
1249:
1250: #[test]
1251: fun test_minting_limit_management() {
1252: let (admin, coin_deployer) = two_people();
1253: let mut scenario = test_scenario::begin(admin);
1254:
1255: // Initialize TBTC
1256: test_scenario::next_tx(&mut scenario, admin);
1257: {
1258: init_tbtc_for_test_scenario(&mut scenario, admin);
1259: };
1260:
1261: // Initialize Wormhole
1262: test_scenario::next_tx(&mut scenario, admin);
1263: {
1264: init_wormhole_for_test(&mut scenario, coin_deployer);
1265: };
1266:
1267: test_scenario::next_tx(&mut scenario, admin);
1268: {
1269: setup::init_test_only(test_scenario::ctx(&mut scenario));
1270: };
1271:
1272: test_scenario::next_tx(&mut scenario, admin);
1273: {
1274: Gateway::init_test(test_scenario::ctx(&mut scenario));
1275: };
1276:
1277: // Initialize Gateway
1278: test_scenario::next_tx(&mut scenario, admin);
1279: {
1280: initialize_gateway_state(&mut scenario);
1281: };
1282:
1283: // Update minting limit
1284: test_scenario::next_tx(&mut scenario, admin);
1285: {
1286: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
1287: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1288: let ctx = test_scenario::ctx(&mut scenario);
1289:
1290: let new_limit = 1000;
1291: Gateway::update_minting_limit(&gateway_admin_cap, &mut gateway_state, new_limit, ctx);
1292:
1293: assert!(Gateway::get_minting_limit(&gateway_state) == new_limit, 0);
1294:
1295: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
1296: test_scenario::return_shared(gateway_state);
1297: };
1298:
1299: test_scenario::end(scenario);
1300: }
1301:
1302: #[test]
1303: fun test_treasury_operations() {
1304: let (admin, coin_deployer) = two_people();
1305: let mut scenario = test_scenario::begin(admin);
1306:
1307: // Initialize TBTC
1308: test_scenario::next_tx(&mut scenario, admin);
1309: {
1310: init_tbtc_for_test_scenario(&mut scenario, admin);
1311: };
1312:
1313: // Initialize Wormhole
1314: test_scenario::next_tx(&mut scenario, admin);
1315: {
1316: init_wormhole_for_test(&mut scenario, coin_deployer);
1317: };
1318:
1319: test_scenario::next_tx(&mut scenario, admin);
1320: {
1321: setup::init_test_only(test_scenario::ctx(&mut scenario));
1322: };
1323:
1324: test_scenario::next_tx(&mut scenario, admin);
1325: {
1326: Gateway::init_test(test_scenario::ctx(&mut scenario));
1327: };
1328:
1329: // Initialize Gateway
1330: test_scenario::next_tx(&mut scenario, admin);
1331: {
1332: initialize_gateway_state(&mut scenario);
1333: };
1334:
1335: // Test treasury operations through redeem with dummy message
1336: test_scenario::next_tx(&mut scenario, admin);
1337: {
1338: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1339: let mut token_bridge_state = take_token_bridge_state(&scenario);
1340: let mut wormhole_state = take_wormhole_state(&scenario);
1341: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
1342: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
1343: let mut treasury = test_scenario::take_shared<
1344: Gateway::WrappedTokenTreasury<coin_wrapped_12::COIN_WRAPPED_12>,
1345: >(&scenario);
1346:
1347: let ctx = test_scenario::ctx(&mut scenario);
1348:
1349: let vaa_bytes = message_with_payload_address();
1350:
1351: let clock = clock::create_for_testing(ctx);
1352:
1353: Gateway::redeem_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
1354: &mut gateway_state,
1355: &mut capabilities,
1356: &mut wormhole_state,
1357: &mut treasury,
1358: &mut token_bridge_state,
1359: &mut token_state,
1360: vaa_bytes,
1361: &clock,
1362: ctx,
1363: );
1364:
1365: test_scenario::return_shared(gateway_state);
1366: test_scenario::return_shared(capabilities);
1367: clock::destroy_for_testing(clock);
1368: return_wormhole_state(wormhole_state);
1369: return_token_bridge_state(token_bridge_state);
1370: test_scenario::return_shared(treasury);
1371: test_scenario::return_to_sender(&scenario, token_state);
1372: };
1373:
1374: test_scenario::end(scenario);
1375: }
1376:
1377: #[test]
1378: fun test_admin_capabilities() {
1379: let (admin, coin_deployer) = two_people();
1380: let mut scenario = test_scenario::begin(admin);
1381:
1382: // Initialize TBTC
1383: test_scenario::next_tx(&mut scenario, admin);
1384: {
1385: init_tbtc_for_test_scenario(&mut scenario, admin);
1386: };
1387:
1388: // Initialize Wormhole
1389: test_scenario::next_tx(&mut scenario, admin);
1390: {
1391: init_wormhole_for_test(&mut scenario, coin_deployer);
1392: };
1393:
1394: test_scenario::next_tx(&mut scenario, admin);
1395: {
1396: setup::init_test_only(test_scenario::ctx(&mut scenario));
1397: };
1398:
1399: test_scenario::next_tx(&mut scenario, admin);
1400: {
1401: Gateway::init_test(test_scenario::ctx(&mut scenario));
1402: };
1403:
1404: // Initialize Gateway
1405: test_scenario::next_tx(&mut scenario, admin);
1406: {
1407: initialize_gateway_state(&mut scenario);
1408: };
1409:
1410: // Test admin capabilities
1411: test_scenario::next_tx(&mut scenario, admin);
1412: {
1413: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
1414: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1415: let ctx = test_scenario::ctx(&mut scenario);
1416:
1417: // Test pause
1418: Gateway::pause(&gateway_admin_cap, &mut gateway_state, ctx);
1419: assert!(Gateway::is_paused(&gateway_state), 0);
1420:
1421: // Test unpause
1422: Gateway::unpause(&gateway_admin_cap, &mut gateway_state, ctx);
1423: assert!(!Gateway::is_paused(&gateway_state), 0);
1424:
1425: // Test update minting limit
1426: let new_limit = 1000;
1427: Gateway::update_minting_limit(&gateway_admin_cap, &mut gateway_state, new_limit, ctx);
1428: assert!(Gateway::get_minting_limit(&gateway_state) == new_limit, 0);
1429:
1430: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
1431: test_scenario::return_shared(gateway_state);
1432: };
1433:
1434: test_scenario::end(scenario);
1435: }
1436:
1437: #[test]
1438: #[expected_failure(abort_code = Gateway::E_WRONG_NONCE)]
1439: fun test_wrong_nonce() {
1440: let (admin, _coin_deployer) = two_people();
1441: let mut scenario = test_scenario::begin(admin);
1442: let wormhole_fee = 1;
1443:
1444: // Initialize TBTC
1445: test_scenario::next_tx(&mut scenario, admin);
1446: {
1447: init_tbtc_for_test_scenario(&mut scenario, admin);
1448: };
1449:
1450: // Initialize Wormhole
1451: test_scenario::next_tx(&mut scenario, admin);
1452: {
1453: let expected_source_chain = 2;
1454: set_up_wormhole_and_token_bridge(&mut scenario, wormhole_fee);
1455: register_dummy_emitter(&mut scenario, expected_source_chain);
1456: };
1457:
1458: test_scenario::next_tx(&mut scenario, admin);
1459: {
1460: Gateway::init_test(test_scenario::ctx(&mut scenario));
1461: };
1462:
1463: // Initialize Gateway
1464: test_scenario::next_tx(&mut scenario, admin);
1465: {
1466: initialize_gateway_state(&mut scenario);
1467: };
1468:
1469: let coin_wrapped = coin_wrapped_12::init_register_and_mint(
1470: &mut scenario,
1471: admin,
1472: 1000000000000000,
1473: );
1474:
1475: // register receiver
1476: test_scenario::next_tx(&mut scenario, admin);
1477: {
1478: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
1479: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1480: let ctx = test_scenario::ctx(&mut scenario);
1481:
1482: Gateway::add_trusted_receiver(
1483: &gateway_admin_cap,
1484: &mut gateway_state,
1485: 2,
1486: x"0000000000000000000000000000000000000000000000000000000000000001",
1487: ctx,
1488: );
1489:
1490: // Return objects
1491: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
1492: test_scenario::return_shared(gateway_state);
1493: };
1494:
1495: // Send wrapped tokens
1496: test_scenario::next_tx(&mut scenario, admin);
1497: {
1498: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1499: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
1500: let mut token_bridge_state = take_token_bridge_state(&scenario);
1501: let mut wormhole_state = take_wormhole_state(&scenario);
1502: let ctx = test_scenario::ctx(&mut scenario);
1503:
1504: let recipient_chain = 2;
1505: let mut recipient_address = vector::empty<u8>();
1506: vector::append(
1507: &mut recipient_address,
1508: x"0000000000000000000000000000000000000000000000000000000000000001",
1509: );
1510:
1511: // get sui native coins
1512: let sui_coin = create_sui_coin(wormhole_fee, ctx);
1513: let clock = clock::create_for_testing(ctx);
1514:
1515: Gateway::send_wrapped_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
1516: &mut gateway_state,
1517: &mut capabilities,
1518: &mut token_bridge_state,
1519: &mut wormhole_state,
1520: recipient_chain,
1521: recipient_address,
1522: coin::from_balance(coin_wrapped, ctx),
1523: 2,
1524: sui_coin,
1525: &clock,
1526: ctx,
1527: );
1528:
1529: test_scenario::return_shared(gateway_state);
1530: test_scenario::return_shared(capabilities);
1531: clock::destroy_for_testing(clock);
1532: return_wormhole_state(wormhole_state);
1533: return_token_bridge_state(token_bridge_state);
1534: };
1535: test_scenario::end(scenario);
1536: }
1537:
1538: #[test]
1539: fun test_nonce_reset() {
1540: let (admin, _coin_deployer) = two_people();
1541: let mut scenario = test_scenario::begin(admin);
1542: let wormhole_fee = 1;
1543:
1544: // Initialize TBTC
1545: test_scenario::next_tx(&mut scenario, admin);
1546: {
1547: init_tbtc_for_test_scenario(&mut scenario, admin);
1548: };
1549:
1550: // Initialize Wormhole
1551: test_scenario::next_tx(&mut scenario, admin);
1552: {
1553: let expected_source_chain = 2;
1554: set_up_wormhole_and_token_bridge(&mut scenario, wormhole_fee);
1555: register_dummy_emitter(&mut scenario, expected_source_chain);
1556: };
1557:
1558: test_scenario::next_tx(&mut scenario, admin);
1559: {
1560: Gateway::init_test(test_scenario::ctx(&mut scenario));
1561: };
1562:
1563: // Initialize Gateway
1564: test_scenario::next_tx(&mut scenario, admin);
1565: {
1566: initialize_gateway_state(&mut scenario);
1567: };
1568:
1569: let coin_wrapped = coin_wrapped_12::init_register_and_mint(
1570: &mut scenario,
1571: admin,
1572: 1000000000000000,
1573: );
1574:
1575: // register receiver
1576: test_scenario::next_tx(&mut scenario, admin);
1577: {
1578: let gateway_admin_cap = test_scenario::take_from_sender<Gateway::AdminCap>(&scenario);
1579: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1580: let ctx = test_scenario::ctx(&mut scenario);
1581:
1582: Gateway::add_trusted_receiver(
1583: &gateway_admin_cap,
1584: &mut gateway_state,
1585: 2,
1586: x"0000000000000000000000000000000000000000000000000000000000000001",
1587: ctx,
1588: );
1589:
1590: // Return objects
1591: test_scenario::return_to_sender(&scenario, gateway_admin_cap);
1592: test_scenario::return_shared(gateway_state);
1593: };
1594:
1595: // Set nonce to max
1596: test_scenario::next_tx(&mut scenario, admin);
1597: {
1598: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1599:
1600: Gateway::set_nonce(&mut gateway_state, 4_294_967_293u32);
1601:
1602: test_scenario::return_shared(gateway_state);
1603: };
1604:
1605: // Send wrapped tokens
1606: test_scenario::next_tx(&mut scenario, admin);
1607: {
1608: let mut gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1609: let mut capabilities = test_scenario::take_shared<Gateway::GatewayCapabilities>(&scenario);
1610: let mut token_bridge_state = take_token_bridge_state(&scenario);
1611: let mut wormhole_state = take_wormhole_state(&scenario);
1612: let ctx = test_scenario::ctx(&mut scenario);
1613:
1614: let recipient_chain = 2;
1615: let mut recipient_address = vector::empty<u8>();
1616: vector::append(
1617: &mut recipient_address,
1618: x"0000000000000000000000000000000000000000000000000000000000000001",
1619: );
1620:
1621: // get sui native coins
1622: let sui_coin = create_sui_coin(wormhole_fee, ctx);
1623: let clock = clock::create_for_testing(ctx);
1624:
1625: Gateway::send_wrapped_tokens<coin_wrapped_12::COIN_WRAPPED_12>(
1626: &mut gateway_state,
1627: &mut capabilities,
1628: &mut token_bridge_state,
1629: &mut wormhole_state,
1630: recipient_chain,
1631: recipient_address,
1632: coin::from_balance(coin_wrapped, ctx),
1633: 4_294_967_294u32,
1634: sui_coin,
1635: &clock,
1636: ctx,
1637: );
1638:
1639: test_scenario::return_shared(gateway_state);
1640: test_scenario::return_shared(capabilities);
1641: clock::destroy_for_testing(clock);
1642: return_wormhole_state(wormhole_state);
1643: return_token_bridge_state(token_bridge_state);
1644: };
1645:
1646: // check if nonce is reset
1647: test_scenario::next_tx(&mut scenario, admin);
1648: {
1649: let gateway_state = test_scenario::take_shared<Gateway::GatewayState>(&scenario);
1650:
1651: assert!(Gateway::check_nonce(&gateway_state) == 0, 0);
1652:
1653: test_scenario::return_shared(gateway_state);
1654: };
1655: test_scenario::end(scenario);
1656: }
1657: }
================
File: tests/tbtc_tests.move
================
1: #[test_only]
2: module l2_tbtc::l2_tbtc_tests {
3:
4: use l2_tbtc::TBTC;
5: use sui::coin::{TreasuryCap, Coin};
6: use sui::test_scenario;
7: use token_bridge::token_bridge_scenario::{two_people, person};
8:
9: // Helper function to initialize TBTC for testing
10: fun init_tbtc_for_test_scenario(scenario: &mut test_scenario::Scenario, admin: address) {
11: let (admin_cap, mut token_state, treasury_cap) = TBTC::initialize_for_test(
12: test_scenario::ctx(scenario)
13: );
14: TBTC::add_minter(&admin_cap, &mut token_state, admin, test_scenario::ctx(scenario));
15: transfer::public_transfer(token_state, admin);
16: transfer::public_transfer(treasury_cap, admin);
17: transfer::public_transfer(admin_cap, admin);
18: }
19:
20: #[test]
21: fun test_initialization() {
22: let (admin, _) = two_people();
23: let mut scenario = test_scenario::begin(admin);
24:
25: // Initialize TBTC
26: test_scenario::next_tx(&mut scenario, admin);
27: {
28: init_tbtc_for_test_scenario(&mut scenario, admin);
29: };
30:
31: test_scenario::end(scenario);
32: }
33:
34: #[test]
35: fun test_add_remove_minter() {
36: let (admin, new_minter) = two_people();
37: let mut scenario = test_scenario::begin(admin);
38:
39: // Initialize TBTC
40: test_scenario::next_tx(&mut scenario, admin);
41: {
42: init_tbtc_for_test_scenario(&mut scenario, admin);
43: };
44:
45: // Add minter
46: test_scenario::next_tx(&mut scenario, admin);
47: {
48: let admin_cap = test_scenario::take_from_sender<TBTC::AdminCap>(&scenario);
49: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
50: let ctx = test_scenario::ctx(&mut scenario);
51:
52: TBTC::add_minter(
53: &admin_cap,
54: &mut token_state,
55: new_minter,
56: ctx,
57: );
58: assert!(TBTC::is_minter(&token_state, new_minter), 0);
59:
60: test_scenario::return_to_sender(&scenario, admin_cap);
61: test_scenario::return_to_sender(&scenario, token_state);
62: };
63:
64: // Remove minter
65: test_scenario::next_tx(&mut scenario, admin);
66: {
67: let admin_cap = test_scenario::take_from_sender<TBTC::AdminCap>(&scenario);
68: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
69: let ctx = test_scenario::ctx(&mut scenario);
70:
71: TBTC::remove_minter(
72: &admin_cap,
73: &mut token_state,
74: new_minter,
75: ctx,
76: );
77: assert!(!TBTC::is_minter(&token_state, new_minter), 0);
78:
79: test_scenario::return_to_sender(&scenario, admin_cap);
80: test_scenario::return_to_sender(&scenario, token_state);
81: };
82:
83: test_scenario::end(scenario);
84: }
85:
86: #[test]
87: fun test_mint() {
88: let (admin, new_minter) = two_people();
89: let mut scenario = test_scenario::begin(admin);
90:
91: // Initialize TBTC
92: test_scenario::next_tx(&mut scenario, admin);
93: {
94: init_tbtc_for_test_scenario(&mut scenario, admin);
95: };
96:
97: // Mint tokens
98: test_scenario::next_tx(&mut scenario, admin);
99: {
100: let minter_cap = test_scenario::take_from_sender<TBTC::MinterCap>(&scenario);
101: let token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
102: let mut treasury_cap = test_scenario::take_from_sender<TreasuryCap<TBTC::TBTC>>(&scenario);
103: let ctx = test_scenario::ctx(&mut scenario);
104:
105: let mint_amount = 1000;
106: TBTC::mint(
107: &minter_cap,
108: &mut treasury_cap,
109: &token_state,
110: mint_amount,
111: new_minter,
112: ctx,
113: );
114:
115: test_scenario::return_to_sender(&scenario, minter_cap);
116: test_scenario::return_to_sender(&scenario, token_state);
117: test_scenario::return_to_sender(&scenario, treasury_cap);
118: };
119:
120: test_scenario::end(scenario);
121: }
122:
123:
124: #[test]
125: #[expected_failure(abort_code = TBTC::E_ALREADY_MINTER)]
126: fun test_already_minter() {
127: let (admin, new_minter) = two_people();
128: let mut scenario = test_scenario::begin(admin);
129:
130: // Initialize TBTC
131: test_scenario::next_tx(&mut scenario, admin);
132: {
133: init_tbtc_for_test_scenario(&mut scenario, admin);
134: };
135:
136: // Add minter
137: test_scenario::next_tx(&mut scenario, admin);
138: {
139: let admin_cap = test_scenario::take_from_sender<TBTC::AdminCap>(&scenario);
140: let mut token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
141: let ctx = test_scenario::ctx(&mut scenario);
142:
143: TBTC::add_minter(
144: &admin_cap,
145: &mut token_state,
146: admin,
147: ctx,
148: );
149: assert!(TBTC::is_minter(&token_state, new_minter), 0);
150:
151: test_scenario::return_to_sender(&scenario, admin_cap);
152: test_scenario::return_to_sender(&scenario, token_state);
153: };
154:
155: test_scenario::end(scenario);
156: }
157:
158: #[test]
159: #[expected_failure(abort_code = test_scenario::EEmptyInventory)]
160: fun test_non_minter_cannot_mint() {
161: let (admin, non_minter) = two_people();
162: let mut scenario = test_scenario::begin(admin);
163:
164: // Initialize TBTC
165: test_scenario::next_tx(&mut scenario, admin);
166: {
167: init_tbtc_for_test_scenario(&mut scenario, admin);
168: };
169:
170: // Try to mint without being a minter
171: test_scenario::next_tx(&mut scenario, non_minter);
172: {
173: let minter_cap = test_scenario::take_from_sender<TBTC::MinterCap>(&scenario);
174: // Cannot mint without being a minter, which means having a minter capability
175: // This will abort with EEmptyInventory
176: test_scenario::return_to_sender(&scenario, minter_cap);
177: };
178:
179: test_scenario::end(scenario);
180: }
181:
182: #[test]
183: fun test_burn() {
184: let admin = person();
185: let mut scenario = test_scenario::begin(admin);
186:
187: // Initialize TBTC
188: test_scenario::next_tx(&mut scenario, admin);
189: {
190: init_tbtc_for_test_scenario(&mut scenario, admin);
191: };
192:
193: // Mint tokens
194: test_scenario::next_tx(&mut scenario, admin);
195: {
196: let minter_cap = test_scenario::take_from_sender<TBTC::MinterCap>(&scenario);
197: let token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
198: let mut treasury_cap = test_scenario::take_from_sender<TreasuryCap<TBTC::TBTC>>(&scenario);
199: let ctx = test_scenario::ctx(&mut scenario);
200:
201: let mint_amount = 1000;
202: TBTC::mint(
203: &minter_cap,
204: &mut treasury_cap,
205: &token_state,
206: mint_amount,
207: admin,
208: ctx,
209: );
210:
211: test_scenario::return_to_sender(&scenario, minter_cap);
212: test_scenario::return_to_sender(&scenario, token_state);
213: test_scenario::return_to_sender(&scenario, treasury_cap);
214: };
215:
216: // Burn tokens
217: test_scenario::next_tx(&mut scenario, admin);
218: {
219: let mut treasury_cap = test_scenario::take_from_sender<TreasuryCap<TBTC::TBTC>>(&scenario);
220: let token_state = test_scenario::take_from_sender<TBTC::TokenState>(&scenario);
221: let coins = test_scenario::take_from_sender<Coin<TBTC::TBTC>>(&scenario);
222:
223: TBTC::burn(
224: &mut treasury_cap,
225: &token_state,
226: coins,
227:
228: );
229:
230: test_scenario::return_to_sender(&scenario, treasury_cap);
231: test_scenario::return_to_sender(&scenario, token_state);
232: };
233:
234: test_scenario::end(scenario);
235: }
236:
237: }
================
File: tests/utils.move
================
1: module l2_tbtc::test_utils {
2: public fun message_with_payload_address(): vector<u8> {
3: // emitterChain: 2,
4: // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef',
5: // amount: 3000n,
6: // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface',
7: // tokenChain: 2,
8: // toAddress: '0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409',
9: // chain: 21,
10: // fromAddress: '0x000000000000000000000000000000000000000000000000000000000badc0de',
11: // payload: 'All your base are belong to us.'
12: x"0100000000010054968c9be4059d7dc373fff8e80dfc9083c485663517534807d61d11abec64896c4185a2bdd71e3caa713d082c78f5d8b1586c56bd5042dfaba1de0ca0d978a0010000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f030000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f4090015000000000000000000000000000000000000000000000000000000000badc0de416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e"
13: }
14:
15: }
================
File: .gitattributes
================
1: # Auto detect text files and perform LF normalization
2: * text=auto
================
File: Move.devnet.toml
================
1: [package]
2: name = "l2_tBTC"
3: edition = "2024.beta" # Use 'legacy' if '2024.beta' causes issues
4: license = "GPL-3.0-only"
5: authors = ["The tBTC team"]
6: version = "0.1.0"
7:
8: [dependencies]
9: Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6" }
10: Wormhole = { git = "https://github.com/wormhole-foundation/wormhole.git", subdir = "sui/wormhole", rev = "main" }
11: TokenBridge = { git = "https://github.com/wormhole-foundation/wormhole.git", subdir = "sui/token_bridge", rev = "main" }
12:
13: [addresses]
14: token_bridge = "0xa6a3da85bbe05da5bfd953708d56f1a3a023e7fb58e5a824a3d4de3791e8f690"
15: l2_tbtc = "_"
16: wormhole = "0x5a5160ca3c2037f4b4051344096ef7a48ebf4400b3f385e57ea90e1628a8bde0"
================
File: Move.lock
================
1: # @generated by Move, please check-in and do not edit manually.
2:
3: [move]
4: version = 3
5: manifest_digest = "EDD172A358220306FC5885902FAFEAAE97C674771B06FCB6FB69BC497107AB69"
6: deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3"
7: dependencies = [
8: { id = "Sui", name = "Sui" },
9: { id = "TokenBridge", name = "TokenBridge" },
10: { id = "Wormhole", name = "Wormhole" },
11: ]
12:
13: [[move.package]]
14: id = "MoveStdlib"
15: source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/move-stdlib" }
16:
17: [[move.package]]
18: id = "Sui"
19: source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/sui-framework" }
20:
21: dependencies = [
22: { id = "MoveStdlib", name = "MoveStdlib" },
23: ]
24:
25: [[move.package]]
26: id = "TokenBridge"
27: source = { local = "../token_bridge" }
28:
29: dependencies = [
30: { id = "Sui", name = "Sui" },
31: ]
32:
33: dev-dependencies = [
34: { id = "Wormhole", name = "Wormhole" },
35: ]
36:
37: [[move.package]]
38: id = "Wormhole"
39: source = { local = "../wormhole" }
40:
41: dependencies = [
42: { id = "Sui", name = "Sui" },
43: ]
44:
45: [move.toolchain-version]
46: compiler-version = "1.40.3"
47: edition = "2024.beta"
48: flavor = "sui"
49:
50: [env]
51:
52: [env.testnet]
53: chain-id = "4c78adac"
54: original-published-id = "0x06fb016afe31ddafcd4d3c43654c478179200d335eae1da5f4d2cebab35a630e"
55: latest-published-id = "0x06fb016afe31ddafcd4d3c43654c478179200d335eae1da5f4d2cebab35a630e"
56: published-version = "1"
================
File: Move.testnet.toml
================
1: [package]
2: name = "l2_tBTC"
3: edition = "2024.beta" # Use 'legacy' if '2024.beta' causes issues
4: license = "GPL-3.0-only"
5: authors = ["The tBTC team"]
6: version = "0.1.0"
7: published-at ="0x06fb016afe31ddafcd4d3c43654c478179200d335eae1da5f4d2cebab35a630e"
8:
9: [dependencies]
10: Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6" }
11: Wormhole = { git = "https://github.com/wormhole-foundation/wormhole.git", subdir = "sui/wormhole", rev = "main" }
12: TokenBridge = { git = "https://github.com/wormhole-foundation/wormhole.git", subdir = "sui/token_bridge", rev = "main" }
13:
14: [addresses]
15: token_bridge = "0x562760fc51d90d4ae1835bac3e91e0e6987d3497b06f066941d3e51f6e8d76d0"
16: l2_tbtc = "0x0" # Replace with the actual address where l2_tbtc is deployed
17: wormhole = "0xf47329f4344f3bf0f8e436e2f7b485466cff300f12a166563995d3888c296a94"
================
File: Move.toml
================
1: [package]
2: name = "l2_tBTC"
3: edition = "2024.beta" # Use 'legacy' if '2024.beta' causes issues
4: license = "GPL-3.0-only"
5: authors = ["The tBTC team"]
6: version = "0.1.0"
7:
8: [dependencies]
9: Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6" }
10: Wormhole = { local = "../wormhole" }
11: TokenBridge = { local = "../token_bridge" }
12:
13: [addresses]
14: token_bridge = "0x562760fc51d90d4ae1835bac3e91e0e6987d3497b06f066941d3e51f6e8d76d0"
15: l2_tbtc = "0x0" # Replace with the actual address where l2_tbtc is deployed
16: wormhole = "0xf47329f4344f3bf0f8e436e2f7b485466cff300f12a166563995d3888c296a94"
17:
18: [dev-addresses]
19: # The dev-addresses section allows overwriting named addresses for the `--test`
20: # and `--dev` modes.
21: # token_bridge = "0xa6a3da85bbe05da5bfd953708d56f1a3a023e7fb58e5a824a3d4de3791e8f690"
22: # wormhole = "0xde0036a9600559e295d5f6802ef6f3f802f510366e0c23912b0655d972166017"
23: # l2_tbtc = "0x402"
================
File: README.md
================
1: # tBTC Sui Move Contracts
2:
3: This repository contains Sui Move contracts for integrating tBTC functionality, including:
4:
5: - **tBTC Coin Module**: Defines the tBTC coin type and related operations.
6: - **Wormhole Gateway Module**: Facilitates cross-chain interactions using the Wormhole protocol.
7: - **Bitcoin Depositor Module**: Manages Bitcoin deposit processes within the Sui network.
8:
9: ## Documentation
10:
11: For detailed documentation and usage instructions of the specific contract check their respective documentation files.
12:
13: - [Bitcoin Depositor Contract](/docs/bitcoin_depositor.md)
14: - [Gateway Contract](/docs/gateway.md)
15: - [tBTC Coin Contract](/docs/tbtc.md)
16:
17:
18: ## Prerequisites
19:
20: Before deploying these contracts, ensure you have the following installed:
21:
22: - **Sui CLI**: Command-line interface for interacting with the Sui network. [Installation Guide](https://docs.sui.io/guides/getting-started)
23: - **Move Analyzer**: Tool for analyzing and testing Move modules. [Move Analyzer Tutorial](https://blog.sui.io/move-analyzer-tutorial/)
24:
25: ## Project Structure
26:
27: ```
28: tbtc-sui-move/
29: ├── docs/
30: │ ├── bitcoin_depositor.md
31: │ ├── tbtc.md
32: │ ├── gateway.md
33: ├── sources/
34: │ ├── token
35: │ ├── ├── tbtc.move
36: │ ├── gateway
37: │ | ├── helpers.move
38: │ | ├── wormhole_gateway.move
39: │ ├── bitcoin_depositor
40: │ | ├── bitcoin_depositor.move
41: │ ├── ...
42: ├── tests/
43: │ ├── l2_tbtc_tests.move
44: │ ├── other tests...
45: ├── Move.toml
46: └── README.md
47: ```
48:
49: - **docs/**: Contains project documentation in Markdown format.
50: - **sources/**: Contains the Move modules for tBTC coin, Wormhole gateway, and Bitcoin depositor.
51: - **tests/**: Includes test scripts for the respective modules.
52: - **Move.toml**: Package manifest file detailing dependencies and addresses.
53:
54: ## Building and testing Steps
55:
56: 1. **Clone the Repository and get Wormhole stuff**:
57:
58: ```bash
59: git clone https://github.com/4-point-0/tbtc-sui-integration.git
60: ```
61:
62: 2. **Clone the Wormhole Repository**:
63:
64: Clone the Wormhole repository from GitHub in the same directory as tbtc-sui-move:
65:
66: ```bash
67: git clone https://github.com/wormhole-foundation/wormhole.git
68: cd tbtc-sui-move
69: ```
70:
71: 3. **Copy two files from wormhole repo to the shared directory**:
72:
73: ```
74: ./wormhole/sui/wormhole
75:
76: ./wormhole/sui/token_bridge
77: ```
78:
79: 4. **Attach published-at**
80:
81: In both of these we need to locate move.toml and change/add the published_at field to their respective addresses where they are deployed.
82:
83: example for testnet for both of them:
84:
85: ```bash
86: #token_bridge
87: published-at = "0x562760fc51d90d4ae1835bac3e91e0e6987d3497b06f066941d3e51f6e8d76d0"
88:
89: #wormhole
90: published-at = "0xf47329f4344f3bf0f8e436e2f7b485466cff300f12a166563995d3888c296a94"
91: ```
92:
93: 5. **Build the Modules**:
94:
95: Compile the Move modules to ensure there are no errors:
96:
97: ```bash
98: cd tbtc-sui-integration
99: sui move build -d
100: ```
101:
102: 6. **Run Tests**:
103:
104: Execute the test scripts to validate module functionality:
105:
106: ```bash
107: sui move test
108: ```
109:
110: 7. **Publish Modules**:
111:
112: Deploy the modules to the Sui network:
113:
114: ```bash
115: sui client publish ./ --gas-budget 1000000
116: ```
117:
118: ## Usage
119:
120: After deployment, you can interact with the modules using the Sui CLI or integrate them into your applications. Refer to the module documentation for available entry functions and their parameters.
121:
122: ## Resources
123:
124: - [Sui Documentation](https://docs.sui.io/)
125: - [Move Language Overview](https://move-language.github.io/move/)
126: - [Wormhole Protocol Documentation](https://wormhole.com/docs/)
================================================================
End of Codebase
================================================================
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment