iota_genesis_builder/stardust/native_token/
package_builder.rs1use std::{fs, path::Path};
8
9use anyhow::Result;
10use iota_move_build::{BuildConfig, CompiledPackage, IotaPackageHooks};
11use tempfile::tempdir;
12
13use crate::stardust::native_token::package_data::NativeTokenPackageData;
14
15const IOTA_FRAMEWORK_GENESIS_REVISION: &str = "framework/genesis/mainnet";
16
17const MODULE_CONTENT: &str = r#"// Copyright (c) 2024 IOTA Stiftung
18// SPDX-License-Identifier: Apache-2.0
19
20#[allow(lint(share_owned))]
21module 0x0::$MODULE_NAME {
22 use iota::coin;
23 use iota::coin_manager;
24 use iota::url::Url;
25
26 /// The type identifier of coin. The coin will have a type
27 /// tag of kind: `Coin<package_object::$MODULE_NAME::$OTW`
28 /// Make sure that the name of the type matches the module's name.
29 public struct $OTW has drop {}
30
31 /// Module initializer is called once on module publish. A treasury
32 /// cap is sent to the publisher, who then controls minting and burning
33 fun init(witness: $OTW, ctx: &mut TxContext) {
34 let icon_url = $ICON_URL;
35
36 // Create the currency
37 let (mut treasury_cap, metadata) = coin::create_currency<$OTW>(
38 witness,
39 $COIN_DECIMALS,
40 b"$COIN_SYMBOL",
41 $COIN_NAME,
42 $COIN_DESCRIPTION,
43 icon_url,
44 ctx
45 );
46
47 // Mint the tokens and transfer them to the publisher
48 let minted_coins = coin::mint(&mut treasury_cap, $CIRCULATING_SUPPLY, ctx);
49 transfer::public_transfer(minted_coins, ctx.sender());
50
51 // Create a coin manager
52 let (cm_treasury_cap, cm_metadata_cap, mut coin_manager) = coin_manager::new(treasury_cap, metadata, ctx);
53 cm_treasury_cap.enforce_maximum_supply(&mut coin_manager, $MAXIMUM_SUPPLY);
54
55 // Make the metadata immutable
56 cm_metadata_cap.renounce_metadata_ownership(&mut coin_manager);
57
58 // Publicly sharing the `CoinManager` object for convenient usage by anyone interested
59 transfer::public_share_object(coin_manager);
60
61 // Transfer the coin manager treasury capability to the alias address
62 transfer::public_transfer(cm_treasury_cap, iota::address::from_ascii_bytes(&b"$ALIAS"));
63 }
64}
65"#;
66
67const TOML_CONTENT: &str = r#"[package]
68name = "$PACKAGE_NAME"
69version = "0.0.1"
70edition = "2024.beta"
71
72[dependencies]
73Iota = { git = "https://github.com/iotaledger/iota.git", subdir = "crates/iota-framework/packages/iota-framework", rev = "$GENESIS_REVISION" }
74"#;
75
76pub fn build_and_compile(package: NativeTokenPackageData) -> Result<CompiledPackage> {
78 let tmp_dir = tempdir()?;
80 let package_path = tmp_dir.path().join("native_token_package");
81 fs::create_dir_all(&package_path).expect("Failed to create native_token_package directory");
82
83 write_move_toml(&package_path, &package, IOTA_FRAMEWORK_GENESIS_REVISION)?;
85
86 write_native_token_module(&package_path, &package)?;
88
89 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
91 let compiled_package = BuildConfig::default().build(&package_path)?;
92
93 tmp_dir.close()?;
95
96 Ok(compiled_package)
97}
98
99fn write_move_toml(
101 package_path: &Path,
102 package: &NativeTokenPackageData,
103 iota_framework_genesis_revision: &str,
104) -> Result<()> {
105 let cargo_toml_path = package_path.join("Move.toml");
106 let new_contents = TOML_CONTENT
107 .replace("$PACKAGE_NAME", package.package_name())
108 .replace("$GENESIS_REVISION", iota_framework_genesis_revision);
109 fs::write(&cargo_toml_path, new_contents)?;
110
111 Ok(())
112}
113
114fn write_native_token_module(package_path: &Path, package: &NativeTokenPackageData) -> Result<()> {
116 let move_source_path = package_path.join("sources");
117 fs::create_dir_all(&move_source_path).expect("Failed to create sources directory");
118 let new_move_file_name = format!("{}.move", package.module().module_name);
119 let new_move_file_path = move_source_path.join(new_move_file_name);
120
121 let icon_url = match &package.module().icon_url {
122 Some(url) => format!(
123 "option::some<Url>(iota::url::new_unsafe_from_bytes({}))",
124 format_string_as_move_vector(url.as_str())
125 ),
126 None => "option::none<Url>()".to_string(),
127 };
128
129 let new_contents = MODULE_CONTENT
130 .replace("$MODULE_NAME", &package.module().module_name)
131 .replace("$OTW", &package.module().otw_name)
132 .replace("$COIN_DECIMALS", &package.module().decimals.to_string())
133 .replace("$COIN_SYMBOL", &package.module().symbol)
134 .replace(
135 "$CIRCULATING_SUPPLY",
136 &package.module().circulating_supply.to_string(),
137 )
138 .replace(
139 "$MAXIMUM_SUPPLY",
140 &package.module().maximum_supply.to_string(),
141 )
142 .replace(
143 "$COIN_NAME",
144 format_string_as_move_vector(package.module().coin_name.as_str()).as_str(),
145 )
146 .replace(
147 "$COIN_DESCRIPTION",
148 format_string_as_move_vector(package.module().coin_description.as_str()).as_str(),
149 )
150 .replace("$ICON_URL", &icon_url)
151 .replace(
152 "$ALIAS",
153 &package.module().alias_address.to_string().replace("0x", ""),
155 );
156
157 fs::write(&new_move_file_path, new_contents)?;
158
159 Ok(())
160}
161
162fn format_string_as_move_vector(string: &str) -> String {
168 let mut byte_string = String::new();
169 byte_string.push_str("/* The utf-8 bytes of '");
170 byte_string.push_str(string);
171 byte_string.push_str("' */\n");
172
173 byte_string.push_str(" vector<u8>[");
174
175 for (idx, byte) in string.as_bytes().iter().enumerate() {
176 byte_string.push_str(&format!("{byte:#x}"));
177
178 if idx != string.len() - 1 {
179 byte_string.push_str(", ");
180 }
181 }
182
183 byte_string.push(']');
184
185 byte_string
186}
187
188#[cfg(test)]
189mod tests {
190
191 use super::*;
192
193 #[test]
194 fn string_to_move_vector() {
195 let tests = [
196 ("", "vector<u8>[]"),
197 ("a", "vector<u8>[0x61]"),
198 ("ab", "vector<u8>[0x61, 0x62]"),
199 ("abc", "vector<u8>[0x61, 0x62, 0x63]"),
200 (
201 "\nöäü",
202 "vector<u8>[0xa, 0xc3, 0xb6, 0xc3, 0xa4, 0xc3, 0xbc]",
203 ),
204 ];
205
206 for (test_input, expected_result) in tests {
207 let move_string = format_string_as_move_vector(test_input);
208 let actual_result = move_string.split('\n').next_back().unwrap().trim_start();
210 assert_eq!(expected_result, actual_result);
211 }
212 }
213}