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