1use std::{
5 collections::{BTreeSet, HashMap},
6 sync::Arc,
7};
8
9use anyhow::Result;
10use iota_adapter_latest::{
11 adapter::new_move_vm, execution_mode, gas_charger::GasCharger, programmable_transactions,
12 temporary_store::TemporaryStore,
13};
14use iota_framework::BuiltInFramework;
15use iota_move_build::CompiledPackage;
16use iota_move_natives_latest::all_natives;
17use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
18use iota_sdk::types::block::output::{
19 AliasOutput, BasicOutput, FoundryOutput, NativeTokens, NftOutput, OutputId, TokenId,
20};
21use iota_types::{
22 IOTA_FRAMEWORK_PACKAGE_ID, STARDUST_PACKAGE_ID, TypeTag,
23 balance::Balance,
24 base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber, TxContext},
25 coin_manager::{CoinManager, CoinManagerTreasuryCap},
26 collection_types::Bag,
27 dynamic_field::Field,
28 id::UID,
29 in_memory_storage::InMemoryStorage,
30 inner_temporary_store::InnerTemporaryStore,
31 metrics::LimitsMetrics,
32 move_package::{MovePackage, TypeOrigin, UpgradeCap},
33 object::Object,
34 programmable_transaction_builder::ProgrammableTransactionBuilder,
35 stardust::{
36 coin_type::CoinType,
37 output::{Nft, foundry::create_foundry_amount_coin},
38 },
39 timelock::timelock,
40 transaction::{
41 Argument, CheckedInputObjects, Command, InputObjectKind, InputObjects, ObjectArg,
42 ObjectReadResult, ProgrammableTransaction,
43 },
44};
45use move_core_types::{ident_str, language_storage::StructTag};
46use move_vm_runtime_latest::move_vm::MoveVM;
47
48use crate::{
49 process_package,
50 stardust::{
51 migration::{
52 MigrationTargetNetwork, PACKAGE_DEPS, create_migration_context, package_module_bytes,
53 verification::created_objects::CreatedObjects,
54 },
55 types::{
56 address_swap_map::AddressSwapMap, output_header::OutputHeader,
57 token_scheme::SimpleTokenSchemeU64,
58 },
59 },
60};
61
62pub(super) struct Executor {
66 protocol_config: ProtocolConfig,
67 tx_context: TxContext,
68 store: InMemoryStorage,
70 system_packages_and_objects: BTreeSet<ObjectID>,
73 move_vm: Arc<MoveVM>,
74 metrics: Arc<LimitsMetrics>,
75 native_tokens: HashMap<TokenId, FoundryLedgerData>,
78 coin_type: CoinType,
81}
82
83impl Executor {
84 pub(super) fn new(
87 protocol_version: ProtocolVersion,
88 target_network: MigrationTargetNetwork,
89 coin_type: CoinType,
90 ) -> Result<Self> {
91 let mut tx_context = create_migration_context(&coin_type, target_network);
92 let metrics = Arc::new(LimitsMetrics::new(&prometheus::Registry::new()));
94 let mut store = InMemoryStorage::new(Vec::new());
95 let protocol_config = ProtocolConfig::get_for_version(protocol_version, Chain::Unknown);
99 let system_packages =
103 iota_framework_snapshot::load_bytecode_snapshot(protocol_version.as_u64())
104 .unwrap_or_else(|_| BuiltInFramework::iter_system_packages().cloned().collect());
105
106 let silent = true;
107 let executor = iota_execution::executor(&protocol_config, silent, None)
108 .expect("Creating an executor should not fail here");
109 for system_package in system_packages.into_iter() {
110 process_package(
111 &mut store,
112 executor.as_ref(),
113 &mut tx_context,
114 &system_package.modules(),
115 system_package.dependencies,
116 &protocol_config,
117 metrics.clone(),
118 )?;
119 }
120 let move_vm = Arc::new(new_move_vm(
121 all_natives(silent, &protocol_config),
122 &protocol_config,
123 None,
124 )?);
125
126 let system_packages_and_objects = store.objects().keys().copied().collect();
127 Ok(Self {
128 protocol_config,
129 tx_context,
130 store,
131 system_packages_and_objects,
132 move_vm,
133 metrics,
134 native_tokens: Default::default(),
135 coin_type,
136 })
137 }
138
139 pub fn store(&self) -> &InMemoryStorage {
140 &self.store
141 }
142
143 pub(crate) fn native_tokens(&self) -> &HashMap<TokenId, FoundryLedgerData> {
144 &self.native_tokens
145 }
146
147 pub(super) fn into_objects(self) -> Vec<Object> {
153 self.store
154 .into_inner()
155 .into_values()
156 .filter(|object| !self.system_packages_and_objects.contains(&object.id()))
157 .collect()
158 }
159
160 pub(crate) fn load_input_objects(
163 &self,
164 object_refs: impl IntoIterator<Item = ObjectRef> + 'static,
165 ) -> impl Iterator<Item = ObjectReadResult> + '_ {
166 object_refs.into_iter().filter_map(|object_ref| {
167 Some(ObjectReadResult::new(
168 InputObjectKind::ImmOrOwnedMoveObject(object_ref),
169 self.store.get_object(&object_ref.0)?.clone().into(),
170 ))
171 })
172 }
173
174 pub(crate) fn load_packages(
177 &self,
178 object_ids: impl IntoIterator<Item = ObjectID> + 'static,
179 ) -> impl Iterator<Item = ObjectReadResult> + '_ {
180 object_ids.into_iter().filter_map(|object_id| {
181 Some(ObjectReadResult::new(
182 InputObjectKind::MovePackage(object_id),
183 self.store.get_object(&object_id)?.clone().into(),
184 ))
185 })
186 }
187
188 fn checked_system_packages(&self) -> CheckedInputObjects {
189 CheckedInputObjects::new_for_genesis(self.load_packages(PACKAGE_DEPS).collect())
190 }
191
192 pub(crate) fn execute_pt_unmetered(
193 &mut self,
194 input_objects: CheckedInputObjects,
195 pt: ProgrammableTransaction,
196 ) -> Result<InnerTemporaryStore> {
197 let input_objects = input_objects.into_inner();
198 let epoch_id = 0; let mut temporary_store = TemporaryStore::new(
200 &self.store,
201 input_objects,
202 vec![],
203 self.tx_context.digest(),
204 &self.protocol_config,
205 epoch_id,
206 );
207 let mut gas_charger = GasCharger::new_unmetered(self.tx_context.digest());
208 programmable_transactions::execution::execute::<execution_mode::Normal>(
209 &self.protocol_config,
210 self.metrics.clone(),
211 &self.move_vm,
212 &mut temporary_store,
213 &mut self.tx_context,
214 &mut gas_charger,
215 pt,
216 &mut None,
217 )?;
218 temporary_store.update_object_version_and_prev_tx();
219 Ok(temporary_store.into_inner())
220 }
221
222 pub(super) fn create_foundries<'a>(
229 &mut self,
230 foundries: impl IntoIterator<Item = (&'a OutputHeader, &'a FoundryOutput, CompiledPackage)>,
231 ) -> Result<Vec<(OutputId, CreatedObjects)>> {
232 let mut res = Vec::new();
233 for (header, foundry, pkg) in foundries {
234 let mut created_objects = CreatedObjects::default();
235 let modules = package_module_bytes(&pkg)?;
236 let deps = self.checked_system_packages();
237 let pt = {
238 let mut builder = ProgrammableTransactionBuilder::new();
239 let upgrade_cap = builder.command(Command::Publish(modules, PACKAGE_DEPS.into()));
240 builder.transfer_arg(Default::default(), upgrade_cap);
245 builder.finish()
246 };
247 let InnerTemporaryStore { written, .. } = self.execute_pt_unmetered(deps, pt)?;
248 let mut native_token_coin_id = None::<ObjectID>;
250 let mut foundry_package = None::<&MovePackage>;
251 for object in written.values() {
252 if object.is_package() {
253 foundry_package = Some(
254 object
255 .data
256 .try_as_package()
257 .expect("already verified this is a package"),
258 );
259 created_objects.set_package(object.id())?;
260 } else if object.is_coin() {
261 native_token_coin_id = Some(object.id());
262 created_objects.set_native_token_coin(object.id())?;
263 } else if let Some(tag) = object.struct_tag() {
264 if CoinManager::is_coin_manager(&tag) {
265 created_objects.set_coin_manager(object.id())?;
266 } else if CoinManagerTreasuryCap::is_coin_manager_treasury_cap(&tag) {
267 created_objects.set_coin_manager_treasury_cap(object.id())?;
268 }
269 }
270 }
271 let (native_token_coin_id, foundry_package) = (
272 native_token_coin_id.expect("a native token coin must have been minted"),
273 foundry_package.expect("there should be a published package"),
274 );
275 self.native_tokens.insert(
276 foundry.token_id(),
277 FoundryLedgerData::new(
278 native_token_coin_id,
279 foundry_package,
280 SimpleTokenSchemeU64::try_from(foundry.token_scheme().as_simple())?,
281 ),
282 );
283
284 let amount_coin = create_foundry_amount_coin(
286 &header.output_id(),
287 foundry,
288 &self.tx_context,
289 foundry_package.version(),
290 &self.protocol_config,
291 &self.coin_type,
292 )?;
293 created_objects.set_coin(amount_coin.id())?;
294 self.store.insert_object(amount_coin);
295
296 self.store.finish(
297 written
298 .into_iter()
299 .filter(|(_, object)| object.struct_tag() != Some(UpgradeCap::type_()))
301 .collect(),
302 );
303 res.push((header.output_id(), created_objects));
304 }
305 Ok(res)
306 }
307
308 pub(super) fn create_alias_objects(
309 &mut self,
310 header: &OutputHeader,
311 alias: &AliasOutput,
312 coin_type: CoinType,
313 address_swap_map: &mut AddressSwapMap,
314 ) -> Result<CreatedObjects> {
315 let mut created_objects = CreatedObjects::default();
316
317 let alias_id = ObjectID::new(*alias.alias_id().or_from_output_id(&header.output_id()));
320 let move_alias = iota_types::stardust::output::Alias::try_from_stardust(alias_id, alias)?;
321
322 let alias_output_owner =
324 address_swap_map.swap_stardust_to_iota_address_owner(alias.governor_address())?;
325
326 let package_deps = InputObjects::new(self.load_packages(PACKAGE_DEPS).collect());
327 let version = package_deps.lamport_timestamp(&[]);
328
329 let move_alias_object = move_alias.to_genesis_object(
330 alias_output_owner,
331 &self.protocol_config,
332 &self.tx_context,
333 version,
334 )?;
335 let move_alias_object_ref = move_alias_object.compute_object_reference();
336
337 self.store.insert_object(move_alias_object);
338
339 let (bag, version, fields) = self.create_bag_with_pt(alias.native_tokens())?;
340 created_objects.set_native_tokens(fields)?;
341
342 let move_alias_output = iota_types::stardust::output::AliasOutput::try_from_stardust(
343 self.tx_context.fresh_id(),
344 alias,
345 bag,
346 )?;
347
348 let move_alias_output_object = move_alias_output.to_genesis_object(
351 alias_output_owner,
352 &self.protocol_config,
353 &self.tx_context,
354 version,
355 coin_type,
356 )?;
357 let move_alias_output_object_ref = move_alias_output_object.compute_object_reference();
358
359 created_objects.set_output(move_alias_output_object.id())?;
360 self.store.insert_object(move_alias_output_object);
361
362 let pt = {
365 let mut builder = ProgrammableTransactionBuilder::new();
366
367 let alias_output_arg =
368 builder.obj(ObjectArg::ImmOrOwnedObject(move_alias_output_object_ref))?;
369 let alias_arg = builder.obj(ObjectArg::ImmOrOwnedObject(move_alias_object_ref))?;
370
371 builder.programmable_move_call(
372 STARDUST_PACKAGE_ID,
373 ident_str!("alias_output").into(),
374 ident_str!("attach_alias").into(),
375 vec![coin_type.to_type_tag()],
376 vec![alias_output_arg, alias_arg],
377 );
378
379 builder.finish()
380 };
381
382 let input_objects = CheckedInputObjects::new_for_genesis(
383 self.load_input_objects([move_alias_object_ref, move_alias_output_object_ref])
384 .chain(self.load_packages(PACKAGE_DEPS))
385 .collect(),
386 );
387
388 let InnerTemporaryStore { written, .. } = self.execute_pt_unmetered(input_objects, pt)?;
389 self.store.finish(written);
390
391 Ok(created_objects)
392 }
393
394 pub(crate) fn create_bag_with_pt(
397 &mut self,
398 native_tokens: &NativeTokens,
399 ) -> Result<(Bag, SequenceNumber, Vec<ObjectID>)> {
400 let mut object_deps = Vec::with_capacity(native_tokens.len());
401 let mut foundry_package_deps = Vec::with_capacity(native_tokens.len());
402 let pt = {
403 let mut builder = ProgrammableTransactionBuilder::new();
404 let bag = pt::bag_new(&mut builder);
405 for token in native_tokens.iter() {
406 let Some(foundry_ledger_data) = self.native_tokens.get_mut(token.token_id()) else {
407 anyhow::bail!("foundry for native token has not been published");
408 };
409
410 let Some(foundry_coin) = self
411 .store
412 .get_object(&foundry_ledger_data.native_token_coin_id)
413 else {
414 anyhow::bail!("foundry coin should exist");
415 };
416 let object_ref = foundry_coin.compute_object_reference();
417
418 object_deps.push(object_ref);
419 foundry_package_deps.push(foundry_ledger_data.package_id);
420
421 let token_type =
422 foundry_ledger_data.to_canonical_string(true);
423 let bag_key = foundry_ledger_data.to_canonical_string(false);
424
425 let adjusted_amount = foundry_ledger_data
426 .token_scheme_u64
427 .adjust_tokens(token.amount());
428
429 foundry_ledger_data.minted_value = foundry_ledger_data
430 .minted_value
431 .checked_sub(adjusted_amount)
432 .ok_or_else(|| anyhow::anyhow!("underflow splitting native token balance"))?;
433
434 let balance = pt::coin_balance_split(
435 &mut builder,
436 object_ref,
437 token_type.parse()?,
438 adjusted_amount,
439 )?;
440 pt::bag_add(&mut builder, bag, bag_key, balance, token_type)?;
441 }
442
443 builder.transfer_arg(Default::default(), bag);
450 builder.finish()
451 };
452 let checked_input_objects = CheckedInputObjects::new_for_genesis(
453 self.load_packages(PACKAGE_DEPS)
454 .chain(self.load_packages(foundry_package_deps))
455 .chain(self.load_input_objects(object_deps))
456 .collect(),
457 );
458 let InnerTemporaryStore {
459 mut written,
460 input_objects,
461 ..
462 } = self.execute_pt_unmetered(checked_input_objects, pt)?;
463 let bag_object = written
464 .iter()
465 .find_map(|(id, object)| {
468 (!input_objects.contains_key(id) && !object.is_child_object()).then_some(id)
469 })
470 .copied()
471 .and_then(|id| written.remove(&id))
472 .ok_or_else(|| anyhow::anyhow!("the bag should have been created"))?;
473 written.remove(&bag_object.id());
474 let field_ids = written
475 .iter()
476 .filter_map(|(id, object)| object.to_rust::<Field<String, Balance>>().map(|_| *id))
477 .collect();
478 self.store.finish(written);
480 let bag = bcs::from_bytes(
482 bag_object
483 .data
484 .try_as_move()
485 .expect("this should be a move object")
486 .contents(),
487 )
488 .expect("this should be a valid Bag Move object");
489 Ok((bag, bag_object.version(), field_ids))
490 }
491
492 fn create_native_token_coins(
494 &mut self,
495 native_tokens: &NativeTokens,
496 owner: IotaAddress,
497 ) -> Result<Vec<ObjectID>> {
498 let mut object_deps = Vec::with_capacity(native_tokens.len());
499 let mut foundry_package_deps = Vec::with_capacity(native_tokens.len());
500 let mut foundry_coins = Vec::with_capacity(native_tokens.len());
501 let pt = {
502 let mut builder = ProgrammableTransactionBuilder::new();
503 for token in native_tokens.iter() {
504 let Some(foundry_ledger_data) = self.native_tokens.get_mut(token.token_id()) else {
505 anyhow::bail!("foundry for native token has not been published");
506 };
507
508 let Some(foundry_coin) = self
509 .store
510 .get_object(&foundry_ledger_data.native_token_coin_id)
511 else {
512 anyhow::bail!("foundry coin should exist");
513 };
514 let object_ref = foundry_coin.compute_object_reference();
515 foundry_coins.push(foundry_coin.id());
516
517 object_deps.push(object_ref);
518 foundry_package_deps.push(foundry_ledger_data.package_id);
519
520 let adjusted_amount = foundry_ledger_data
522 .token_scheme_u64
523 .adjust_tokens(token.amount());
524
525 foundry_ledger_data.minted_value = foundry_ledger_data
526 .minted_value
527 .checked_sub(adjusted_amount)
528 .ok_or_else(|| anyhow::anyhow!("underflow splitting native token balance"))?;
529
530 builder.pay(vec![object_ref], vec![owner], vec![adjusted_amount])?;
531 }
532
533 builder.finish()
534 };
535 let checked_input_objects = CheckedInputObjects::new_for_genesis(
536 self.load_packages(PACKAGE_DEPS)
537 .chain(self.load_packages(foundry_package_deps))
538 .chain(self.load_input_objects(object_deps))
539 .collect(),
540 );
541 let InnerTemporaryStore { written, .. } =
543 self.execute_pt_unmetered(checked_input_objects, pt)?;
544
545 let coin_ids = written
546 .keys()
547 .filter(|id| !foundry_coins.contains(id))
550 .copied()
551 .collect();
552
553 self.store.finish(written);
555 Ok(coin_ids)
556 }
557
558 pub(super) fn create_basic_objects(
561 &mut self,
562 header: &OutputHeader,
563 basic_output: &BasicOutput,
564 target_milestone_timestamp_sec: u32,
565 coin_type: &CoinType,
566 address_swap_map: &mut AddressSwapMap,
567 ) -> Result<CreatedObjects> {
568 let mut basic =
569 iota_types::stardust::output::BasicOutput::new(header.new_object_id(), basic_output)?;
570
571 let basic_objects_owner =
572 address_swap_map.swap_stardust_to_iota_address(basic_output.address())?;
573
574 let mut created_objects = CreatedObjects::default();
575
576 let package_deps = InputObjects::new(self.load_packages(PACKAGE_DEPS).collect());
578 let mut version = package_deps.lamport_timestamp(&[]);
579
580 let object = if basic.is_simple_coin(target_milestone_timestamp_sec) {
581 if !basic_output.native_tokens().is_empty() {
582 let coins = self
583 .create_native_token_coins(basic_output.native_tokens(), basic_objects_owner)?;
584 created_objects.set_native_tokens(coins)?;
585 }
586 let amount_coin = basic.into_genesis_coin_object(
587 basic_objects_owner,
588 &self.protocol_config,
589 &self.tx_context,
590 version,
591 coin_type,
592 )?;
593 created_objects.set_coin(amount_coin.id())?;
594 amount_coin
595 } else {
596 if !basic_output.native_tokens().is_empty() {
597 let fields;
598 (basic.native_tokens, version, fields) =
601 self.create_bag_with_pt(basic_output.native_tokens())?;
602 created_objects.set_native_tokens(fields)?;
603 } else {
604 basic.native_tokens.id = UID::new(self.tx_context.fresh_id());
607 }
608 let object = basic.to_genesis_object(
609 basic_objects_owner,
610 &self.protocol_config,
611 &self.tx_context,
612 version,
613 coin_type,
614 )?;
615 created_objects.set_output(object.id())?;
616 object
617 };
618
619 self.store.insert_object(object);
620 Ok(created_objects)
621 }
622
623 pub(super) fn create_timelock_object(
627 &mut self,
628 output_id: OutputId,
629 basic_output: &BasicOutput,
630 target_milestone_timestamp: u32,
631 address_swap_map: &mut AddressSwapMap,
632 ) -> Result<CreatedObjects> {
633 let mut created_objects = CreatedObjects::default();
634
635 let basic_output_owner =
636 address_swap_map.swap_stardust_to_iota_address(basic_output.address())?;
637
638 let package_deps = InputObjects::new(self.load_packages(PACKAGE_DEPS).collect());
639 let version = package_deps.lamport_timestamp(&[]);
640
641 let timelock =
642 timelock::try_from_stardust(output_id, basic_output, target_milestone_timestamp)?;
643
644 let object = timelock::to_genesis_object(
645 timelock,
646 basic_output_owner,
647 &self.protocol_config,
648 &self.tx_context,
649 version,
650 )?;
651
652 created_objects.set_output(object.id())?;
653
654 self.store.insert_object(object);
655 Ok(created_objects)
656 }
657
658 pub(super) fn create_nft_objects(
659 &mut self,
660 header: &OutputHeader,
661 nft: &NftOutput,
662 coin_type: CoinType,
663 address_swap_map: &mut AddressSwapMap,
664 ) -> Result<CreatedObjects> {
665 let mut created_objects = CreatedObjects::default();
666
667 let nft_id = ObjectID::new(*nft.nft_id().or_from_output_id(&header.output_id()));
670 let move_nft = Nft::try_from_stardust(nft_id, nft)?;
671
672 let nft_output_owner_address =
674 address_swap_map.swap_stardust_to_iota_address(nft.address())?;
675
676 let nft_output_owner =
677 address_swap_map.swap_stardust_to_iota_address_owner(nft.address())?;
678
679 let package_deps = InputObjects::new(self.load_packages(PACKAGE_DEPS).collect());
680 let version = package_deps.lamport_timestamp(&[]);
681 let move_nft_object = move_nft.to_genesis_object(
682 nft_output_owner,
683 &self.protocol_config,
684 &self.tx_context,
685 version,
686 )?;
687
688 let move_nft_object_ref = move_nft_object.compute_object_reference();
689 self.store.insert_object(move_nft_object);
690
691 let (bag, version, fields) = self.create_bag_with_pt(nft.native_tokens())?;
692 created_objects.set_native_tokens(fields)?;
693 let move_nft_output = iota_types::stardust::output::NftOutput::try_from_stardust(
694 self.tx_context.fresh_id(),
695 nft,
696 bag,
697 )?;
698
699 let move_nft_output_object = move_nft_output.to_genesis_object(
702 nft_output_owner_address,
703 &self.protocol_config,
704 &self.tx_context,
705 version,
706 coin_type,
707 )?;
708 let move_nft_output_object_ref = move_nft_output_object.compute_object_reference();
709 created_objects.set_output(move_nft_output_object.id())?;
710 self.store.insert_object(move_nft_output_object);
711
712 let pt = {
715 let mut builder = ProgrammableTransactionBuilder::new();
716
717 let nft_output_arg =
718 builder.obj(ObjectArg::ImmOrOwnedObject(move_nft_output_object_ref))?;
719 let nft_arg = builder.obj(ObjectArg::ImmOrOwnedObject(move_nft_object_ref))?;
720 builder.programmable_move_call(
721 STARDUST_PACKAGE_ID,
722 ident_str!("nft_output").into(),
723 ident_str!("attach_nft").into(),
724 vec![coin_type.to_type_tag()],
725 vec![nft_output_arg, nft_arg],
726 );
727
728 builder.finish()
729 };
730
731 let input_objects = CheckedInputObjects::new_for_genesis(
732 self.load_input_objects([move_nft_object_ref, move_nft_output_object_ref])
733 .chain(self.load_packages(PACKAGE_DEPS))
734 .collect(),
735 );
736
737 let InnerTemporaryStore { written, .. } = self.execute_pt_unmetered(input_objects, pt)?;
738 self.store.finish(written);
739
740 Ok(created_objects)
741 }
742}
743
744#[cfg(test)]
745impl Executor {
746 pub(crate) fn with_tx_context(mut self, tx_context: TxContext) -> Self {
748 self.tx_context = tx_context;
749 self
750 }
751
752 pub(crate) fn with_store(mut self, store: InMemoryStorage) -> Self {
754 self.store = store;
755 self
756 }
757}
758
759mod pt {
760 use super::*;
761 use crate::stardust::migration::NATIVE_TOKEN_BAG_KEY_TYPE;
762
763 pub fn coin_balance_split(
764 builder: &mut ProgrammableTransactionBuilder,
765 foundry_coin_ref: ObjectRef,
766 token_type_tag: TypeTag,
767 amount: u64,
768 ) -> Result<Argument> {
769 let foundry_coin_ref = builder.obj(ObjectArg::ImmOrOwnedObject(foundry_coin_ref))?;
770 let amount = builder.pure(amount)?;
771 let coin = builder.programmable_move_call(
772 IOTA_FRAMEWORK_PACKAGE_ID,
773 ident_str!("coin").into(),
774 ident_str!("split").into(),
775 vec![token_type_tag.clone()],
776 vec![foundry_coin_ref, amount],
777 );
778 Ok(builder.programmable_move_call(
779 IOTA_FRAMEWORK_PACKAGE_ID,
780 ident_str!("coin").into(),
781 ident_str!("into_balance").into(),
782 vec![token_type_tag],
783 vec![coin],
784 ))
785 }
786
787 pub fn bag_add(
788 builder: &mut ProgrammableTransactionBuilder,
789 bag: Argument,
790 bag_key: String,
791 balance: Argument,
792 token_type: String,
793 ) -> Result<()> {
794 let key_type: StructTag = NATIVE_TOKEN_BAG_KEY_TYPE.parse()?;
795 let value_type = Balance::type_(token_type.parse::<TypeTag>()?);
796 let bag_key_arg = builder.pure(bag_key)?;
797 builder.programmable_move_call(
798 IOTA_FRAMEWORK_PACKAGE_ID,
799 ident_str!("bag").into(),
800 ident_str!("add").into(),
801 vec![key_type.into(), value_type.into()],
802 vec![bag, bag_key_arg, balance],
803 );
804 Ok(())
805 }
806
807 pub fn bag_new(builder: &mut ProgrammableTransactionBuilder) -> Argument {
808 builder.programmable_move_call(
809 IOTA_FRAMEWORK_PACKAGE_ID,
810 ident_str!("bag").into(),
811 ident_str!("new").into(),
812 vec![],
813 vec![],
814 )
815 }
816}
817
818pub(crate) struct FoundryLedgerData {
821 pub(crate) native_token_coin_id: ObjectID,
822 pub(crate) coin_type_origin: TypeOrigin,
823 pub(crate) package_id: ObjectID,
824 pub(crate) token_scheme_u64: SimpleTokenSchemeU64,
825 pub(crate) minted_value: u64,
826}
827
828impl FoundryLedgerData {
829 fn new(
836 native_token_coin_id: ObjectID,
837 foundry_package: &MovePackage,
838 token_scheme_u64: SimpleTokenSchemeU64,
839 ) -> Self {
840 Self {
841 native_token_coin_id,
842 coin_type_origin: foundry_package.type_origin_table()[0].clone(),
844 package_id: foundry_package.id(),
845 minted_value: token_scheme_u64.circulating_supply(),
846 token_scheme_u64,
847 }
848 }
849
850 pub(crate) fn to_canonical_string(&self, with_prefix: bool) -> String {
851 format!(
852 "{}::{}::{}",
853 self.coin_type_origin
854 .package
855 .to_canonical_string(with_prefix),
856 self.coin_type_origin.module_name,
857 self.coin_type_origin.datatype_name
858 )
859 }
860}