Created
April 24, 2025 20:15
-
-
Save lrsaturnino/9a5df9c3fc2a21a2ffacd17dd5c291b6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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