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