iota_genesis_builder/stardust/migration/verification/
foundry.rs

1// Copyright (c) 2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    // Amount coin value and owner
49    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    // Native token coin value
63    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    // The minted native token coin should be owned by `0x0`
72    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    // Package
95    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    // Adjusted Token Scheme
148    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    // Coin Manager
158    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    // Coin Metadata
178    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    // Maximum Supply
219    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    // Alias Address Unlock Condition
241    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}