iota_genesis_builder/stardust/migration/verification/
nft.rs1use std::collections::HashMap;
5
6use anyhow::{anyhow, ensure};
7use iota_sdk::types::block::output::{NftOutput, OutputId, TokenId};
8use iota_types::{
9 TypeTag,
10 balance::Balance,
11 base_types::ObjectID,
12 dynamic_field::{DynamicFieldInfo, Field, derive_dynamic_field_id},
13 in_memory_storage::InMemoryStorage,
14 object::Owner,
15 stardust::output::{NFT_DYNAMIC_OBJECT_FIELD_KEY, NFT_DYNAMIC_OBJECT_FIELD_KEY_TYPE},
16};
17
18use crate::stardust::{
19 migration::{
20 executor::FoundryLedgerData,
21 verification::{
22 created_objects::CreatedObjects,
23 util::{
24 TokensAmountCounter, verify_address_owner, verify_expiration_unlock_condition,
25 verify_issuer_feature, verify_metadata_feature, verify_native_tokens,
26 verify_parent, verify_sender_feature, verify_storage_deposit_unlock_condition,
27 verify_tag_feature, verify_timelock_unlock_condition,
28 },
29 },
30 },
31 types::address_swap_map::AddressSwapMap,
32};
33
34pub(super) fn verify_nft_output(
35 output_id: OutputId,
36 output: &NftOutput,
37 created_objects: &CreatedObjects,
38 foundry_data: &HashMap<TokenId, FoundryLedgerData>,
39 storage: &InMemoryStorage,
40 tokens_counter: &mut TokensAmountCounter,
41 address_swap_map: &AddressSwapMap,
42) -> anyhow::Result<()> {
43 let created_output_obj = created_objects.output().and_then(|id| {
44 storage
45 .get_object(id)
46 .ok_or_else(|| anyhow!("missing nft output object for {output_id}"))
47 })?;
48 let created_output = created_output_obj
49 .to_rust::<iota_types::stardust::output::NftOutput>()
50 .ok_or_else(|| anyhow!("invalid nft output object for {output_id}"))?;
51
52 let created_nft_obj = storage
53 .get_object(&ObjectID::new(*output.nft_id_non_null(&output_id)))
54 .ok_or_else(|| anyhow!("missing nft object for {output_id}"))?;
55 let created_nft = created_nft_obj
56 .to_rust::<iota_types::stardust::output::Nft>()
57 .ok_or_else(|| anyhow!("invalid nft object for {output_id}"))?;
58
59 if output.unlock_conditions().expiration().is_some() {
62 ensure!(
63 matches!(created_output_obj.owner, Owner::Shared { .. }),
64 "nft output owner mismatch: found {:?}, expected Shared",
65 created_output_obj.owner,
66 );
67 } else {
68 verify_address_owner(
69 output.address(),
70 created_output_obj,
71 "nft output",
72 address_swap_map,
73 )?;
74 }
75
76 let expected_nft_owner = Owner::ObjectOwner(
78 derive_dynamic_field_id(
79 created_output_obj.id(),
80 &DynamicFieldInfo::dynamic_object_field_wrapper(
81 NFT_DYNAMIC_OBJECT_FIELD_KEY_TYPE.parse::<TypeTag>()?,
82 )
83 .into(),
84 &bcs::to_bytes(NFT_DYNAMIC_OBJECT_FIELD_KEY)?,
85 )?
86 .into(),
87 );
88
89 ensure!(
90 created_nft_obj.owner == expected_nft_owner,
91 "nft owner mismatch: found {}, expected {}",
92 created_nft_obj.owner,
93 expected_nft_owner
94 );
95
96 ensure!(
98 created_output.balance.value() == output.amount(),
99 "amount mismatch: found {}, expected {}",
100 created_output.balance.value(),
101 output.amount()
102 );
103 tokens_counter.update_total_value_for_iota(created_output.balance.value());
104
105 verify_native_tokens::<Field<String, Balance>>(
107 output.native_tokens(),
108 foundry_data,
109 created_output.native_tokens,
110 created_objects.native_tokens().ok(),
111 storage,
112 tokens_counter,
113 )?;
114
115 verify_storage_deposit_unlock_condition(
117 output.unlock_conditions().storage_deposit_return(),
118 created_output.storage_deposit_return.as_ref(),
119 )?;
120
121 verify_timelock_unlock_condition(
123 output.unlock_conditions().timelock(),
124 created_output.timelock.as_ref(),
125 )?;
126
127 verify_expiration_unlock_condition(
129 output.unlock_conditions().expiration(),
130 created_output.expiration.as_ref(),
131 output.address(),
132 )?;
133
134 verify_metadata_feature(output.features().metadata(), created_nft.metadata.as_ref())?;
136
137 verify_tag_feature(output.features().tag(), created_nft.tag.as_ref())?;
139
140 verify_sender_feature(output.features().sender(), created_nft.legacy_sender)?;
142
143 verify_issuer_feature(
145 output.immutable_features().issuer(),
146 created_nft.immutable_issuer,
147 )?;
148
149 ensure!(
151 iota_types::stardust::output::Nft::convert_immutable_metadata(output)?
152 == created_nft.immutable_metadata,
153 "metadata mismatch: found {:x?}, expected {:x?}",
154 iota_types::stardust::output::Nft::convert_immutable_metadata(output)?,
155 created_nft.immutable_metadata
156 );
157
158 verify_parent(&output_id, output.address(), storage)?;
159
160 ensure!(created_objects.coin().is_err(), "unexpected coin found");
161
162 ensure!(
163 created_objects.native_token_coin().is_err(),
164 "unexpected native token coin found"
165 );
166
167 ensure!(
168 created_objects.coin_manager().is_err(),
169 "unexpected coin manager found"
170 );
171
172 ensure!(
173 created_objects.coin_manager_treasury_cap().is_err(),
174 "unexpected coin manager treasury cap found"
175 );
176
177 ensure!(
178 created_objects.package().is_err(),
179 "unexpected package found"
180 );
181
182 Ok(())
183}