iota_genesis_builder/stardust/migration/verification/
foundry.rs1use std::collections::HashMap;
5
6use anyhow::{Result, anyhow, ensure};
7use iota_sdk::types::block::output::{FoundryOutput, OutputId, TokenId};
8use iota_types::{
9 Identifier, base_types::IotaAddress, coin_manager::CoinManager,
10 in_memory_storage::InMemoryStorage, object::Owner,
11};
12use move_core_types::language_storage::ModuleId;
13
14use crate::stardust::{
15 migration::{
16 executor::FoundryLedgerData,
17 verification::{
18 CreatedObjects,
19 util::{
20 TokensAmountCounter, truncate_to_max_allowed_u64_supply, verify_address_owner,
21 verify_coin, verify_parent, verify_shared_object,
22 },
23 },
24 },
25 native_token::package_data::NativeTokenPackageData,
26 types::{address_swap_map::AddressSwapMap, token_scheme::SimpleTokenSchemeU64},
27};
28
29pub(super) fn verify_foundry_output(
30 output_id: OutputId,
31 output: &FoundryOutput,
32 created_objects: &CreatedObjects,
33 foundry_data: &HashMap<TokenId, FoundryLedgerData>,
34 storage: &InMemoryStorage,
35 tokens_counter: &mut TokensAmountCounter,
36 address_swap_map: &AddressSwapMap,
37) -> Result<()> {
38 let foundry_data = foundry_data
39 .get(&output.token_id())
40 .ok_or_else(|| anyhow!("missing foundry data"))?;
41
42 let alias_address = output
43 .unlock_conditions()
44 .immutable_alias_address()
45 .expect("foundry outputs always have an immutable alias address")
46 .address();
47
48 let created_coin_obj = created_objects.coin().and_then(|id| {
50 storage
51 .get_object(id)
52 .ok_or_else(|| anyhow!("missing coin"))
53 })?;
54 let created_coin = created_coin_obj
55 .as_coin_maybe()
56 .ok_or_else(|| anyhow!("expected a coin"))?;
57
58 verify_address_owner(alias_address, created_coin_obj, "coin", address_swap_map)?;
59 verify_coin(output.amount(), &created_coin)?;
60 tokens_counter.update_total_value_for_iota(created_coin.value());
61
62 let native_token_coin_id = created_objects.native_token_coin()?;
64 let native_token_coin_obj = storage
65 .get_object(native_token_coin_id)
66 .ok_or_else(|| anyhow!("missing native token coin"))?;
67 let native_token_coin = native_token_coin_obj
68 .as_coin_maybe()
69 .ok_or_else(|| anyhow!("expected a native token coin"))?;
70
71 let expected_owner = Owner::AddressOwner(IotaAddress::default());
73 ensure!(
74 native_token_coin_obj.owner == expected_owner,
75 "native token coin owner mismatch: found {}, expected {}",
76 native_token_coin_obj.owner,
77 expected_owner
78 );
79
80 ensure!(
81 foundry_data.native_token_coin_id == *native_token_coin_id,
82 "coin ID mismatch: found {}, expected {}",
83 foundry_data.native_token_coin_id,
84 native_token_coin_id
85 );
86
87 ensure!(
88 native_token_coin.value() == foundry_data.minted_value,
89 "minted coin amount mismatch: found {}, expected {}",
90 native_token_coin.value(),
91 foundry_data.minted_value
92 );
93
94 let package_id = created_objects.package()?;
96 let created_package = storage
97 .get_object(package_id)
98 .ok_or_else(|| anyhow!("missing package"))?
99 .data
100 .try_as_package()
101 .ok_or_else(|| anyhow!("expected a package"))?;
102
103 ensure!(
104 foundry_data.package_id == *package_id,
105 "foundry data package ID mismatch: found {}, expected {}",
106 foundry_data.package_id,
107 package_id
108 );
109
110 let expected_package_data = NativeTokenPackageData::try_from(output)?;
111
112 let module_id = ModuleId::new(
113 created_package.id().into(),
114 Identifier::new(expected_package_data.module().module_name.as_ref())?,
115 );
116
117 ensure!(
118 created_package.get_module(&module_id).is_some(),
119 "package did not create expected module `{}`",
120 expected_package_data.module().module_name
121 );
122
123 let type_origin_map = created_package.type_origin_map();
124
125 ensure!(
126 type_origin_map.contains_key(&(
127 expected_package_data.module().module_name.clone(),
128 expected_package_data.module().otw_name.clone()
129 )),
130 "package did not create expected OTW type `{}` within module `{}`",
131 expected_package_data.module().otw_name,
132 expected_package_data.module().module_name,
133 );
134 ensure!(
135 foundry_data.coin_type_origin.module_name == expected_package_data.module().module_name,
136 "foundry data module name mismatch: found {}, expected {}",
137 foundry_data.coin_type_origin.module_name,
138 expected_package_data.module().module_name
139 );
140 ensure!(
141 foundry_data.coin_type_origin.datatype_name == expected_package_data.module().otw_name,
142 "foundry data OTW struct name mismatch: found {}, expected {}",
143 foundry_data.coin_type_origin.datatype_name,
144 expected_package_data.module().otw_name
145 );
146
147 let expected_token_scheme_u64 =
149 SimpleTokenSchemeU64::try_from(output.token_scheme().as_simple())?;
150 ensure!(
151 expected_token_scheme_u64 == foundry_data.token_scheme_u64,
152 "foundry data token scheme mismatch: found {:?}, expected: {:?}",
153 foundry_data.token_scheme_u64,
154 expected_token_scheme_u64
155 );
156
157 let coin_manager = created_objects.coin_manager().and_then(|id| {
159 storage
160 .get_object(id)
161 .ok_or(anyhow!("missing coin manager"))
162 .and_then(|obj| {
163 verify_shared_object(obj, "coin manager").map(|_| {
164 obj.to_rust::<CoinManager>()
165 .ok_or(anyhow!("expected a coin manager"))
166 })?
167 })
168 })?;
169
170 let coin_manager_treasury_cap_obj =
171 created_objects.coin_manager_treasury_cap().and_then(|id| {
172 storage
173 .get_object(id)
174 .ok_or_else(|| anyhow!("missing coin manager treasury cap"))
175 })?;
176
177 let coin_metadata = coin_manager
179 .metadata
180 .ok_or(anyhow!("missing coin metadata"))?;
181
182 ensure!(
183 coin_metadata.decimals == expected_package_data.module().decimals,
184 "coin decimals mismatch: expected {}, found {}",
185 expected_package_data.module().decimals,
186 coin_metadata.decimals
187 );
188 ensure!(
189 coin_metadata.name == expected_package_data.module().coin_name,
190 "coin name mismatch: expected {}, found {}",
191 expected_package_data.module().coin_name,
192 coin_metadata.name
193 );
194 ensure!(
195 coin_metadata.symbol == expected_package_data.module().symbol,
196 "coin symbol mismatch: expected {}, found {}",
197 expected_package_data.module().symbol,
198 coin_metadata.symbol
199 );
200 ensure!(
201 coin_metadata.description == expected_package_data.module().coin_description,
202 "coin description mismatch: expected {}, found {}",
203 expected_package_data.module().coin_description,
204 coin_metadata.description
205 );
206 ensure!(
207 coin_metadata.icon_url
208 == expected_package_data
209 .module()
210 .icon_url
211 .as_ref()
212 .map(|u| u.to_string()),
213 "coin icon url mismatch: expected {:?}, found {:?}",
214 expected_package_data.module().icon_url,
215 coin_metadata.icon_url
216 );
217
218 let max_supply = coin_manager
220 .maximum_supply
221 .ok_or(anyhow!("missing max supply"))?;
222
223 ensure!(
224 max_supply == expected_package_data.module().maximum_supply,
225 "maximum supply mismatch: expected {}, found {}",
226 expected_package_data.module().maximum_supply,
227 max_supply
228 );
229 let circulating_supply =
230 truncate_to_max_allowed_u64_supply(output.token_scheme().as_simple().circulating_supply());
231 ensure!(
232 coin_manager.treasury_cap.total_supply.value == circulating_supply,
233 "treasury total supply mismatch: found {}, expected {}",
234 coin_manager.treasury_cap.total_supply.value,
235 circulating_supply
236 );
237 tokens_counter
238 .update_total_value_max(&foundry_data.to_canonical_string(false), circulating_supply);
239
240 verify_address_owner(
242 alias_address,
243 coin_manager_treasury_cap_obj,
244 "coin manager treasury cap",
245 address_swap_map,
246 )?;
247
248 verify_parent(&output_id, alias_address, storage)?;
249
250 ensure!(
251 created_objects.output().is_err(),
252 "unexpected output object found"
253 );
254
255 Ok(())
256}