iota_genesis_builder/stardust/migration/verification/
basic.rs1use std::collections::HashMap;
5
6use anyhow::{Result, anyhow, ensure};
7use iota_sdk::types::block::output::{BasicOutput, OutputId, TokenId};
8use iota_types::{
9 TypeTag,
10 balance::Balance,
11 coin::Coin,
12 dynamic_field::Field,
13 in_memory_storage::InMemoryStorage,
14 object::Owner,
15 timelock::{
16 stardust_upgrade_label::STARDUST_UPGRADE_LABEL_VALUE,
17 timelock::{TimeLock, is_timelocked_vested_reward},
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_coin,
28 verify_expiration_unlock_condition, 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,
35};
36
37pub(super) fn verify_basic_output(
38 output_id: OutputId,
39 output: &BasicOutput,
40 created_objects: &CreatedObjects,
41 foundry_data: &HashMap<TokenId, FoundryLedgerData>,
42 target_milestone_timestamp: u32,
43 storage: &InMemoryStorage,
44 tokens_counter: &mut TokensAmountCounter,
45 address_swap_map: &AddressSwapMap,
46) -> Result<()> {
47 if is_timelocked_vested_reward(output_id, output, target_milestone_timestamp) {
49 let created_timelock = created_objects
50 .output()
51 .and_then(|id| {
52 storage
53 .get_object(id)
54 .ok_or_else(|| anyhow!("missing timelock object"))
55 })?
56 .to_rust::<TimeLock<Balance>>()
57 .ok_or_else(|| anyhow!("invalid timelock object"))?;
58
59 let output_timelock_timestamp =
61 output.unlock_conditions().timelock().unwrap().timestamp() as u64 * 1000;
62 ensure!(
63 created_timelock.expiration_timestamp_ms() == output_timelock_timestamp,
64 "timelock timestamp mismatch: found {}, expected {}",
65 created_timelock.expiration_timestamp_ms(),
66 output_timelock_timestamp
67 );
68
69 ensure!(
71 created_timelock.locked().value() == output.amount(),
72 "locked amount mismatch: found {}, expected {}",
73 created_timelock.locked().value(),
74 output.amount()
75 );
76 tokens_counter.update_total_value_for_iota(created_timelock.locked().value());
77
78 let label = created_timelock
80 .label()
81 .as_ref()
82 .ok_or_else(|| anyhow!("timelock label must be initialized"))?;
83 let expected_label = STARDUST_UPGRADE_LABEL_VALUE;
84
85 ensure!(
86 label == expected_label,
87 "timelock label mismatch: found {}, expected {}",
88 label,
89 expected_label
90 );
91
92 ensure!(
93 created_objects.native_token_coin().is_err(),
94 "unexpected native token coin found"
95 );
96
97 ensure!(
98 created_objects.coin_manager().is_err(),
99 "unexpected coin manager found"
100 );
101
102 ensure!(
103 created_objects.coin_manager_treasury_cap().is_err(),
104 "unexpected coin manager cap found"
105 );
106
107 ensure!(
108 created_objects.package().is_err(),
109 "unexpected package found"
110 );
111
112 return Ok(());
113 }
114
115 if output.unlock_conditions().expiration().is_some()
118 || output
119 .unlock_conditions()
120 .storage_deposit_return()
121 .is_some()
122 || output
123 .unlock_conditions()
124 .is_time_locked(target_milestone_timestamp)
125 || !output.features().is_empty()
126 {
127 ensure!(created_objects.coin().is_err(), "unexpected coin created");
128
129 let created_output_obj = created_objects.output().and_then(|id| {
130 storage
131 .get_object(id)
132 .ok_or_else(|| anyhow!("missing basic output object"))
133 })?;
134 let created_output = created_output_obj
135 .to_rust::<iota_types::stardust::output::BasicOutput>()
136 .ok_or_else(|| anyhow!("invalid basic output object"))?;
137
138 if output.unlock_conditions().expiration().is_some() {
141 ensure!(
142 matches!(created_output_obj.owner, Owner::Shared { .. }),
143 "basic output owner mismatch: found {:?}, expected Shared",
144 created_output_obj.owner,
145 );
146 } else {
147 verify_address_owner(
148 output.address(),
149 created_output_obj,
150 "basic output",
151 address_swap_map,
152 )?;
153 }
154
155 ensure!(
157 created_output.balance.value() == output.amount(),
158 "amount mismatch: found {}, expected {}",
159 created_output.balance.value(),
160 output.amount()
161 );
162 tokens_counter.update_total_value_for_iota(created_output.balance.value());
163
164 verify_native_tokens::<Field<String, Balance>>(
166 output.native_tokens(),
167 foundry_data,
168 created_output.native_tokens,
169 created_objects.native_tokens().ok(),
170 storage,
171 tokens_counter,
172 )?;
173
174 verify_storage_deposit_unlock_condition(
176 output.unlock_conditions().storage_deposit_return(),
177 created_output.storage_deposit_return.as_ref(),
178 )?;
179
180 verify_timelock_unlock_condition(
182 output.unlock_conditions().timelock(),
183 created_output.timelock.as_ref(),
184 )?;
185
186 verify_expiration_unlock_condition(
188 output.unlock_conditions().expiration(),
189 created_output.expiration.as_ref(),
190 output.address(),
191 )?;
192
193 verify_metadata_feature(
195 output.features().metadata(),
196 created_output.metadata.as_ref(),
197 )?;
198
199 verify_tag_feature(output.features().tag(), created_output.tag.as_ref())?;
201
202 verify_sender_feature(output.features().sender(), created_output.sender)?;
204
205 } else {
209 ensure!(
210 created_objects.output().is_err(),
211 "unexpected output object created for simple deposit"
212 );
213
214 let created_coin_obj = created_objects.coin().and_then(|id| {
216 storage
217 .get_object(id)
218 .ok_or_else(|| anyhow!("missing coin"))
219 })?;
220 let created_coin = created_coin_obj
221 .as_coin_maybe()
222 .ok_or_else(|| anyhow!("expected a coin"))?;
223
224 verify_address_owner(output.address(), created_coin_obj, "coin", address_swap_map)?;
225 verify_coin(output.amount(), &created_coin)?;
226 tokens_counter.update_total_value_for_iota(created_coin.value());
227
228 verify_native_tokens::<(TypeTag, Coin)>(
230 output.native_tokens(),
231 foundry_data,
232 None,
233 created_objects.native_tokens().ok(),
234 storage,
235 tokens_counter,
236 )?;
237 }
238
239 verify_parent(&output_id, output.address(), storage)?;
240
241 ensure!(
242 created_objects.native_token_coin().is_err(),
243 "unexpected native token coin found"
244 );
245
246 ensure!(
247 created_objects.coin_manager().is_err(),
248 "unexpected coin manager found"
249 );
250
251 ensure!(
252 created_objects.coin_manager_treasury_cap().is_err(),
253 "unexpected coin manager cap found"
254 );
255
256 ensure!(
257 created_objects.package().is_err(),
258 "unexpected package found"
259 );
260
261 Ok(())
262}