iota_genesis_builder/stardust/migration/verification/
nft.rs

1// Copyright (c) 2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    // Output Owner
63    // If there is an expiration unlock condition, the NFT is shared.
64    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    // NFT Owner
80    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    // Amount
100    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    // Native Tokens
109    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    // Storage Deposit Return Unlock Condition
119    verify_storage_deposit_unlock_condition(
120        output.unlock_conditions().storage_deposit_return(),
121        created_output.storage_deposit_return.as_ref(),
122    )?;
123
124    // Timelock Unlock Condition
125    verify_timelock_unlock_condition(
126        output.unlock_conditions().timelock(),
127        created_output.timelock.as_ref(),
128    )?;
129
130    // Expiration Unlock Condition
131    verify_expiration_unlock_condition(
132        output.unlock_conditions().expiration(),
133        created_output.expiration.as_ref(),
134        output.address(),
135    )?;
136
137    // Metadata Feature
138    verify_metadata_feature(output.features().metadata(), created_nft.metadata.as_ref())?;
139
140    // Tag Feature
141    verify_tag_feature(output.features().tag(), created_nft.tag.as_ref())?;
142
143    // Sender Feature
144    verify_sender_feature(output.features().sender(), created_nft.legacy_sender)?;
145
146    // Issuer Feature
147    verify_issuer_feature(
148        output.immutable_features().issuer(),
149        created_nft.immutable_issuer,
150    )?;
151
152    // Immutable Metadata Feature
153    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}