From 735a310cbfd14187de614afaf430b80a63cf461b Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 29 Jan 2025 16:04:27 +0100 Subject: [PATCH] fix(genesis-geremony): fix examine-genesis-checkpoint (#4829) (#5080) * fix: examine genesis checkpoint * chore: clippy and fmt * refactor: is_maybe to try_from * chore: remove unused functions * refactor: remove unwrap from func * chore: remove unused to_bytes funcs * chore: cargo fmt * chore: correct spelling * fix(genesis-inspector): consider TimelockedStakedIota as a stake * refactor(genesis-inspector): get migration objects as iterator * add timelock balance assert * fixes coming from different review comments --------- Co-authored-by: Nonast <29281463+nonast@users.noreply.github.com> Co-authored-by: Mirko Zichichi Co-authored-by: miker83z --- crates/iota-genesis-builder/src/lib.rs | 16 +- crates/iota-types/src/base_types.rs | 29 ++- .../iota-types/src/stardust/output/alias.rs | 38 +++- .../iota-types/src/stardust/output/basic.rs | 37 +++- crates/iota-types/src/stardust/output/nft.rs | 38 +++- crates/iota-types/src/timelock/timelock.rs | 47 ++++- crates/iota/src/genesis_ceremony.rs | 2 +- crates/iota/src/genesis_inspector.rs | 193 ++++++++++++++++-- 8 files changed, 368 insertions(+), 32 deletions(-) diff --git a/crates/iota-genesis-builder/src/lib.rs b/crates/iota-genesis-builder/src/lib.rs index 69de4c1e0be..bd6818b6c9e 100644 --- a/crates/iota-genesis-builder/src/lib.rs +++ b/crates/iota-genesis-builder/src/lib.rs @@ -149,11 +149,25 @@ impl Builder { } } + /// Return an iterator of migration objects if this genesis is with + /// migration objects. + pub fn tx_migration_objects(&self) -> impl Iterator + '_ { + self.migration_tx_data + .as_ref() + .map(|tx_data| tx_data.get_objects()) + .into_iter() + .flatten() + } + + /// Set the genesis delegation to be a `OneToAll` kind and set the + /// delegator address. pub fn with_delegator(mut self, delegator: IotaAddress) -> Self { self.delegation = Some(GenesisDelegation::OneToAll(delegator)); self } + /// Set the genesis delegation to be a `ManyToMany` kind and set the + /// delegator map. pub fn with_delegations(mut self, delegations: Delegations) -> Self { self.delegation = Some(GenesisDelegation::ManyToMany(delegations)); self @@ -259,7 +273,7 @@ impl Builder { self.built_genesis.clone() } - fn load_migration_sources(&mut self) -> anyhow::Result<()> { + pub fn load_migration_sources(&mut self) -> anyhow::Result<()> { for source in &self.migration_sources { tracing::info!("Adding migration objects from {:?}", source); self.migration_objects diff --git a/crates/iota-types/src/base_types.rs b/crates/iota-types/src/base_types.rs index 9266636417c..ca710ebbb6e 100644 --- a/crates/iota-types/src/base_types.rs +++ b/crates/iota-types/src/base_types.rs @@ -55,7 +55,7 @@ use crate::{ object::{Object, Owner}, parse_iota_struct_tag, signature::GenericSignature, - stardust::output::Nft, + stardust::output::{AliasOutput, BasicOutput, Nft, NftOutput}, timelock::{ timelock::{self, TimeLock}, timelocked_staked_iota::TimelockedStakedIota, @@ -380,6 +380,33 @@ impl MoveObjectType { } } + pub fn is_alias_output(&self) -> bool { + match &self.0 { + MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => { + false + } + MoveObjectType_::Other(s) => AliasOutput::is_alias_output(s), + } + } + + pub fn is_basic_output(&self) -> bool { + match &self.0 { + MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => { + false + } + MoveObjectType_::Other(s) => BasicOutput::is_basic_output(s), + } + } + + pub fn is_nft_output(&self) -> bool { + match &self.0 { + MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => { + false + } + MoveObjectType_::Other(s) => NftOutput::is_nft_output(s), + } + } + pub fn try_extract_field_name(&self, type_: &DynamicFieldType) -> IotaResult { match &self.0 { MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => { diff --git a/crates/iota-types/src/stardust/output/alias.rs b/crates/iota-types/src/stardust/output/alias.rs index 92d6716abd7..c24659f7774 100644 --- a/crates/iota-types/src/stardust/output/alias.rs +++ b/crates/iota-types/src/stardust/output/alias.rs @@ -8,10 +8,11 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; use crate::{ - STARDUST_PACKAGE_ID, TypeTag, + STARDUST_ADDRESS, TypeTag, balance::Balance, base_types::{IotaAddress, ObjectID, SequenceNumber, TxContext}, collection_types::Bag, + error::IotaError, id::UID, object::{Data, MoveObject, Object, Owner}, stardust::{coin_type::CoinType, stardust_to_iota_address}, @@ -54,7 +55,7 @@ impl Alias { /// [`Alias`] in its move package. pub fn tag() -> StructTag { StructTag { - address: STARDUST_PACKAGE_ID.into(), + address: STARDUST_ADDRESS, module: ALIAS_MODULE_NAME.to_owned(), name: ALIAS_STRUCT_NAME.to_owned(), type_params: Vec::new(), @@ -154,7 +155,7 @@ impl AliasOutput { /// [`AliasOutput`] in its move package. pub fn tag(type_param: TypeTag) -> StructTag { StructTag { - address: STARDUST_PACKAGE_ID.into(), + address: STARDUST_ADDRESS, module: ALIAS_OUTPUT_MODULE_NAME.to_owned(), name: ALIAS_OUTPUT_STRUCT_NAME.to_owned(), type_params: vec![type_param], @@ -201,4 +202,35 @@ impl AliasOutput { Ok(move_alias_output_object) } + + /// Create an `AliasOutput` from BCS bytes. + pub fn from_bcs_bytes(content: &[u8]) -> Result { + bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization { + error: format!("Unable to deserialize AliasOutput object: {:?}", err), + }) + } + + pub fn is_alias_output(s: &StructTag) -> bool { + s.address == STARDUST_ADDRESS + && s.module.as_ident_str() == ALIAS_OUTPUT_MODULE_NAME + && s.name.as_ident_str() == ALIAS_OUTPUT_STRUCT_NAME + } +} + +impl TryFrom<&Object> for AliasOutput { + type Error = IotaError; + fn try_from(object: &Object) -> Result { + match &object.data { + Data::Move(o) => { + if o.type_().is_alias_output() { + return AliasOutput::from_bcs_bytes(o.contents()); + } + } + Data::Package(_) => {} + } + + Err(IotaError::Type { + error: format!("Object type is not an AliasOutput: {:?}", object), + }) + } } diff --git a/crates/iota-types/src/stardust/output/basic.rs b/crates/iota-types/src/stardust/output/basic.rs index 8d294ccf2ef..90039751e66 100644 --- a/crates/iota-types/src/stardust/output/basic.rs +++ b/crates/iota-types/src/stardust/output/basic.rs @@ -14,11 +14,12 @@ use super::unlock_conditions::{ ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition, }; use crate::{ - STARDUST_PACKAGE_ID, TypeTag, + STARDUST_ADDRESS, TypeTag, balance::Balance, base_types::{IotaAddress, MoveObjectType, ObjectID, SequenceNumber, TxContext}, coin::Coin, collection_types::Bag, + error::IotaError, id::UID, object::{Data, MoveObject, Object, Owner}, stardust::{coin_type::CoinType, stardust_to_iota_address}, @@ -108,7 +109,7 @@ impl BasicOutput { /// Returns the struct tag of the BasicOutput struct pub fn tag(type_param: TypeTag) -> StructTag { StructTag { - address: STARDUST_PACKAGE_ID.into(), + address: STARDUST_ADDRESS, module: BASIC_OUTPUT_MODULE_NAME.to_owned(), name: BASIC_OUTPUT_STRUCT_NAME.to_owned(), type_params: vec![type_param], @@ -181,6 +182,20 @@ impl BasicOutput { coin_type, ) } + + /// Create a `BasicOutput` from BCS bytes. + pub fn from_bcs_bytes(content: &[u8]) -> Result { + bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization { + error: format!("Unable to deserialize BasicOutput object: {:?}", err), + }) + } + + /// Whether the given `StructTag` represents a `BasicOutput`. + pub fn is_basic_output(s: &StructTag) -> bool { + s.address == STARDUST_ADDRESS + && s.module.as_ident_str() == BASIC_OUTPUT_MODULE_NAME + && s.name.as_ident_str() == BASIC_OUTPUT_STRUCT_NAME + } } pub(crate) fn create_coin( @@ -209,3 +224,21 @@ pub(crate) fn create_coin( tx_context.digest(), )) } + +impl TryFrom<&Object> for BasicOutput { + type Error = IotaError; + fn try_from(object: &Object) -> Result { + match &object.data { + Data::Move(o) => { + if o.type_().is_basic_output() { + return BasicOutput::from_bcs_bytes(o.contents()); + } + } + Data::Package(_) => {} + } + + Err(IotaError::Type { + error: format!("Object type is not a BasicOutput: {:?}", object), + }) + } +} diff --git a/crates/iota-types/src/stardust/output/nft.rs b/crates/iota-types/src/stardust/output/nft.rs index 1af82fafc00..0c73437d0cc 100644 --- a/crates/iota-types/src/stardust/output/nft.rs +++ b/crates/iota-types/src/stardust/output/nft.rs @@ -15,10 +15,11 @@ use super::unlock_conditions::{ ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition, }; use crate::{ - STARDUST_PACKAGE_ID, TypeTag, + STARDUST_ADDRESS, TypeTag, balance::Balance, base_types::{IotaAddress, ObjectID, SequenceNumber, TxContext}, collection_types::{Bag, Entry, VecMap}, + error::IotaError, id::UID, object::{Data, MoveObject, Object, Owner}, stardust::{coin_type::CoinType, stardust_to_iota_address}, @@ -261,7 +262,7 @@ impl Nft { /// [`Nft`] in its move package. pub fn tag() -> StructTag { StructTag { - address: STARDUST_PACKAGE_ID.into(), + address: STARDUST_ADDRESS, module: NFT_MODULE_NAME.to_owned(), name: NFT_STRUCT_NAME.to_owned(), type_params: Vec::new(), @@ -404,7 +405,7 @@ impl NftOutput { /// [`NftOutput`] in its move package. pub fn tag(type_param: TypeTag) -> StructTag { StructTag { - address: STARDUST_PACKAGE_ID.into(), + address: STARDUST_ADDRESS, module: NFT_OUTPUT_MODULE_NAME.to_owned(), name: NFT_OUTPUT_STRUCT_NAME.to_owned(), type_params: vec![type_param], @@ -469,4 +470,35 @@ impl NftOutput { Ok(move_nft_output_object) } + + /// Create an `NftOutput` from BCS bytes. + pub fn from_bcs_bytes(content: &[u8]) -> Result { + bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization { + error: format!("Unable to deserialize NftOutput object: {:?}", err), + }) + } + + pub fn is_nft_output(s: &StructTag) -> bool { + s.address == STARDUST_ADDRESS + && s.module.as_ident_str() == NFT_OUTPUT_MODULE_NAME + && s.name.as_ident_str() == NFT_OUTPUT_STRUCT_NAME + } +} + +impl TryFrom<&Object> for NftOutput { + type Error = IotaError; + fn try_from(object: &Object) -> Result { + match &object.data { + Data::Move(o) => { + if o.type_().is_nft_output() { + return NftOutput::from_bcs_bytes(o.contents()); + } + } + Data::Package(_) => {} + } + + Err(IotaError::Type { + error: format!("Object type is not a NftOutput: {:?}", object), + }) + } } diff --git a/crates/iota-types/src/timelock/timelock.rs b/crates/iota-types/src/timelock/timelock.rs index 840a3d2ede2..1c5d03f1a5e 100644 --- a/crates/iota-types/src/timelock/timelock.rs +++ b/crates/iota-types/src/timelock/timelock.rs @@ -17,7 +17,8 @@ use crate::{ IOTA_FRAMEWORK_ADDRESS, balance::Balance, base_types::{IotaAddress, MoveObjectType, ObjectID, SequenceNumber, TxContext}, - error::ExecutionError, + error::{ExecutionError, IotaError}, + gas_coin::GasCoin, id::UID, object::{Data, MoveObject, Object, Owner}, }; @@ -202,8 +203,10 @@ where T: Serialize + Deserialize<'de>, { /// Create a `TimeLock` from BCS bytes. - pub fn from_bcs_bytes(content: &'de [u8]) -> Result { - bcs::from_bytes(content) + pub fn from_bcs_bytes(content: &'de [u8]) -> Result { + bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization { + error: format!("Unable to deserialize TimeLock object: {:?}", err), + }) } /// Serialize a `TimeLock` as a `Vec` of BCS. @@ -234,3 +237,41 @@ pub fn is_timelocked_balance(other: &StructTag) -> bool { _ => false, } } + +/// Is this other StructTag representing a `TimeLock>`? +pub fn is_timelocked_gas_balance(other: &StructTag) -> bool { + if !is_timelock(other) { + return false; + } + + if other.type_params.len() != 1 { + return false; + } + + match &other.type_params[0] { + TypeTag::Struct(tag) => GasCoin::is_gas_balance(tag), + _ => false, + } +} + +impl<'de, T> TryFrom<&'de Object> for TimeLock +where + T: Serialize + Deserialize<'de>, +{ + type Error = IotaError; + + fn try_from(object: &'de Object) -> Result { + match &object.data { + Data::Move(o) => { + if o.type_().is_timelock() { + return TimeLock::from_bcs_bytes(o.contents()); + } + } + Data::Package(_) => {} + } + + Err(IotaError::Type { + error: format!("Object type is not a TimeLock: {:?}", object), + }) + } +} diff --git a/crates/iota/src/genesis_ceremony.rs b/crates/iota/src/genesis_ceremony.rs index c235d0f159c..06d47d8a0c7 100644 --- a/crates/iota/src/genesis_ceremony.rs +++ b/crates/iota/src/genesis_ceremony.rs @@ -305,7 +305,7 @@ pub async fn run(cmd: Ceremony) -> Result<()> { )); }; - examine_genesis_checkpoint(unsigned_genesis); + examine_genesis_checkpoint(unsigned_genesis, builder.tx_migration_objects()); } CeremonyCommand::VerifyAndSign { key_file } => { diff --git a/crates/iota/src/genesis_inspector.rs b/crates/iota/src/genesis_inspector.rs index dc8e049ec87..3ec6954deae 100644 --- a/crates/iota/src/genesis_inspector.rs +++ b/crates/iota/src/genesis_inspector.rs @@ -7,19 +7,30 @@ use std::collections::BTreeMap; use inquire::Select; use iota_config::genesis::UnsignedGenesis; use iota_types::{ + balance::Balance, base_types::ObjectID, coin::CoinMetadata, gas_coin::{GasCoin, IotaTreasuryCap, NANOS_PER_IOTA}, governance::StakedIota, iota_system_state::IotaValidatorGenesis, move_package::MovePackage, - object::{MoveObject, Owner}, + object::{MoveObject, Object, Owner}, + stardust::output::{AliasOutput, BasicOutput, NftOutput}, + timelock::{ + timelock::{TimeLock, is_timelocked_gas_balance}, + timelocked_staked_iota::TimelockedStakedIota, + }, }; const STR_ALL: &str = "All"; const STR_EXIT: &str = "Exit"; const STR_IOTA: &str = "Iota"; const STR_STAKED_IOTA: &str = "StakedIota"; +const STR_TIMELOCKED_IOTA: &str = "TimelockedIota"; +const STR_TIMELOCKED_STAKED_IOTA: &str = "TimelockedStakedIota"; +const STR_ALIAS_OUTPUT_IOTA: &str = "AliasOutputIota"; +const STR_BASIC_OUTPUT_IOTA: &str = "BasicOutputIota"; +const STR_NFT_OUTPUT_IOTA: &str = "NftOutputIota"; const STR_PACKAGE: &str = "Package"; const STR_COIN_METADATA: &str = "CoinMetadata"; const STR_OTHER: &str = "Other"; @@ -27,7 +38,10 @@ const STR_IOTA_DISTRIBUTION: &str = "IOTA Distribution"; const STR_OBJECTS: &str = "Objects"; const STR_VALIDATORS: &str = "Validators"; -pub(crate) fn examine_genesis_checkpoint(genesis: UnsignedGenesis) { +pub(crate) fn examine_genesis_checkpoint( + genesis: UnsignedGenesis, + migration_objects: impl Iterator, +) { let system_object = genesis .iota_system_object() .into_genesis_version_for_tooling(); @@ -65,10 +79,19 @@ pub(crate) fn examine_genesis_checkpoint(genesis: UnsignedGenesis) { let mut package_map = BTreeMap::new(); let mut iota_map = BTreeMap::new(); let mut staked_iota_map = BTreeMap::new(); + let mut timelocked_iota_map = BTreeMap::new(); + let mut timelocked_staked_iota_map = BTreeMap::new(); + let mut alias_output_iota_map = BTreeMap::new(); + let mut basic_output_iota_map = BTreeMap::new(); + let mut nft_output_iota_map = BTreeMap::new(); let mut coin_metadata_map = BTreeMap::new(); let mut other_object_map = BTreeMap::new(); - for object in genesis.objects() { + let additional_objects = genesis.objects(); + let tot_additional_objects = additional_objects.len(); + let mut total_objects = 0; + for object in additional_objects.iter().cloned().chain(migration_objects) { + total_objects += 1; let object_id = object.id(); let object_id_str = object_id.to_string(); assert_eq!(object.storage_rebate, 0); @@ -76,15 +99,15 @@ pub(crate) fn examine_genesis_checkpoint(genesis: UnsignedGenesis) { match &object.data { iota_types::object::Data::Move(move_object) => { - if let Ok(gas) = GasCoin::try_from(object) { + if let Ok(gas) = GasCoin::try_from(&object) { let entry = iota_distribution .entry(object.owner.to_string()) .or_default(); entry.insert(object_id_str.clone(), (STR_IOTA, gas.value())); iota_map.insert(object.id(), gas); - } else if let Ok(coin_metadata) = CoinMetadata::try_from(object) { + } else if let Ok(coin_metadata) = CoinMetadata::try_from(&object) { coin_metadata_map.insert(object.id(), coin_metadata); - } else if let Ok(staked_iota) = StakedIota::try_from(object) { + } else if let Ok(staked_iota) = StakedIota::try_from(&object) { let entry = iota_distribution .entry(object.owner.to_string()) .or_default(); @@ -92,21 +115,78 @@ pub(crate) fn examine_genesis_checkpoint(genesis: UnsignedGenesis) { // Assert pool id is associated with a known validator. let validator = validator_pool_id_map.get(&staked_iota.pool_id()).unwrap(); assert_eq!(validator.staking_pool.id, staked_iota.pool_id()); - staked_iota_map.insert(object.id(), staked_iota); + } else if let Ok(timelock_balance) = TimeLock::::try_from(&object) { + assert!(is_timelocked_gas_balance( + &object.struct_tag().expect("should not be a package") + )); + let entry = iota_distribution + .entry(object.owner.to_string()) + .or_default(); + entry.insert( + object_id_str, + (STR_TIMELOCKED_IOTA, timelock_balance.locked().value()), + ); + timelocked_iota_map.insert(object.id(), timelock_balance); + } else if let Ok(alias_output) = AliasOutput::try_from(&object) { + let entry = iota_distribution + .entry(object.owner.to_string()) + .or_default(); + entry.insert( + object_id_str, + (STR_ALIAS_OUTPUT_IOTA, alias_output.balance.value()), + ); + alias_output_iota_map.insert(object.id(), alias_output); + } else if let Ok(basic_output) = BasicOutput::try_from(&object) { + let entry = iota_distribution + .entry(object.owner.to_string()) + .or_default(); + entry.insert( + object_id_str, + (STR_BASIC_OUTPUT_IOTA, basic_output.balance.value()), + ); + basic_output_iota_map.insert(object.id(), basic_output); + } else if let Ok(nft_output) = NftOutput::try_from(&object) { + let entry = iota_distribution + .entry(object.owner.to_string()) + .or_default(); + entry.insert( + object_id_str, + (STR_NFT_OUTPUT_IOTA, nft_output.balance.value()), + ); + nft_output_iota_map.insert(object.id(), nft_output); + } else if let Ok(timelocked_staked_iota) = TimelockedStakedIota::try_from(&object) { + let entry = iota_distribution + .entry(object.owner.to_string()) + .or_default(); + entry.insert( + object_id_str, + ( + STR_TIMELOCKED_STAKED_IOTA, + timelocked_staked_iota.principal(), + ), + ); + // Assert pool id is associated with a known validator. + let validator = validator_pool_id_map + .get(&timelocked_staked_iota.pool_id()) + .unwrap(); + assert_eq!(validator.staking_pool.id, timelocked_staked_iota.pool_id()); + timelocked_staked_iota_map.insert(object.id(), timelocked_staked_iota); } else { - other_object_map.insert(object.id(), move_object); + other_object_map.insert(object.id(), move_object.clone()); } } iota_types::object::Data::Package(p) => { - package_map.insert(object.id(), p); + package_map.insert(object.id(), p.clone()); } } } + println!( - "Total Number of Objects/Packages: {}", - genesis.objects().len() + "Total Number of Migration Objects: {}", + total_objects - tot_additional_objects ); + println!("Total Number of Objects/Packages: {}", total_objects); // Always check the Total Supply examine_total_supply(&system_object.iota_treasury_cap, &iota_distribution, false); @@ -128,13 +208,18 @@ pub(crate) fn examine_genesis_checkpoint(genesis: UnsignedGenesis) { examine_validators(&validator_options, &validator_map); } Ok(name) if name == STR_OBJECTS => { - println!("Examine Objects (total: {})", genesis.objects().len()); + println!("Examine Objects (total: {})", total_objects); examine_object( &owner_map, &validator_pool_id_map, &package_map, &iota_map, &staked_iota_map, + &timelocked_iota_map, + &timelocked_staked_iota_map, + &alias_output_iota_map, + &basic_output_iota_map, + &nft_output_iota_map, &coin_metadata_map, &other_object_map, ); @@ -179,15 +264,25 @@ fn examine_validators( fn examine_object( owner_map: &BTreeMap, validator_pool_id_map: &BTreeMap, - package_map: &BTreeMap, + package_map: &BTreeMap, iota_map: &BTreeMap, staked_iota_map: &BTreeMap, + timelocked_iota_map: &BTreeMap>, + timelocked_staked_iota: &BTreeMap, + alias_output_iota_map: &BTreeMap, + basic_output_iota_map: &BTreeMap, + nft_output_iota_map: &BTreeMap, coin_metadata_map: &BTreeMap, - other_object_map: &BTreeMap, + other_object_map: &BTreeMap, ) { let object_options: Vec<&str> = vec![ STR_IOTA, STR_STAKED_IOTA, + STR_TIMELOCKED_IOTA, + STR_TIMELOCKED_STAKED_IOTA, + STR_ALIAS_OUTPUT_IOTA, + STR_BASIC_OUTPUT_IOTA, + STR_NFT_OUTPUT_IOTA, STR_COIN_METADATA, STR_PACKAGE, STR_OTHER, @@ -213,6 +308,40 @@ fn examine_object( } print_divider(STR_STAKED_IOTA); } + Ok(name) if name == STR_TIMELOCKED_IOTA => { + for timelocked in timelocked_iota_map.values() { + println!("{:#?}", timelocked); + } + print_divider(STR_TIMELOCKED_IOTA); + } + Ok(name) if name == STR_TIMELOCKED_STAKED_IOTA => { + for timelocked_staked in timelocked_staked_iota.values() { + display_timelocked_staked_iota( + timelocked_staked, + validator_pool_id_map, + owner_map, + ); + } + print_divider(STR_TIMELOCKED_STAKED_IOTA); + } + Ok(name) if name == STR_ALIAS_OUTPUT_IOTA => { + for alias_output in alias_output_iota_map.values() { + println!("{:#?}", alias_output); + } + print_divider(STR_ALIAS_OUTPUT_IOTA); + } + Ok(name) if name == STR_BASIC_OUTPUT_IOTA => { + for basic_output in basic_output_iota_map.values() { + println!("{:#?}", basic_output); + } + print_divider(STR_BASIC_OUTPUT_IOTA); + } + Ok(name) if name == STR_NFT_OUTPUT_IOTA => { + for nft_output in nft_output_iota_map.values() { + println!("{:#?}", nft_output); + } + print_divider(STR_NFT_OUTPUT_IOTA); + } Ok(name) if name == STR_PACKAGE => { for package in package_map.values() { println!("Package ID: {}", package.id()); @@ -255,7 +384,7 @@ fn examine_total_supply( let mut amount_sum = 0; for (owner, value) in coins.values() { amount_sum += value; - if *owner == STR_STAKED_IOTA { + if *owner == STR_STAKED_IOTA || *owner == STR_TIMELOCKED_STAKED_IOTA { total_staked_iota += value; } } @@ -269,7 +398,6 @@ fn examine_total_supply( println!("{:#?}\n", coins); } } - assert_eq!(total_iota, iota_treasury_cap.total_supply().value); // Always print this. println!( "Total Supply of Iota: {total_iota} NANOS or {} IOTA", @@ -279,6 +407,7 @@ fn examine_total_supply( "Total Amount of StakedIota: {total_staked_iota} NANOS or {} IOTA\n", total_staked_iota / NANOS_PER_IOTA ); + assert_eq!(total_iota, iota_treasury_cap.total_supply().value); if print { print_divider("IOTA Distribution"); } @@ -352,13 +481,41 @@ fn display_staked_iota( validator_pool_id_map: &BTreeMap, owner_map: &BTreeMap, ) { - let validator = validator_pool_id_map.get(&staked_iota.pool_id()).unwrap(); println!("{:#?}", staked_iota); + display_staked( + &staked_iota.pool_id(), + &staked_iota.id(), + validator_pool_id_map, + owner_map, + ); +} + +fn display_timelocked_staked_iota( + timelocked_staked_iota: &TimelockedStakedIota, + validator_pool_id_map: &BTreeMap, + owner_map: &BTreeMap, +) { + println!("{:#?}", timelocked_staked_iota); + display_staked( + &timelocked_staked_iota.pool_id(), + &timelocked_staked_iota.id(), + validator_pool_id_map, + owner_map, + ); +} + +fn display_staked( + pool_id: &ObjectID, + staked_object_id: &ObjectID, + validator_pool_id_map: &BTreeMap, + owner_map: &BTreeMap, +) { + let validator = validator_pool_id_map.get(pool_id).unwrap(); println!( "Staked to Validator: {}", validator.verified_metadata().name ); - println!("Owner: {}\n", owner_map.get(&staked_iota.id()).unwrap()); + println!("Owner: {}\n", owner_map.get(staked_object_id).unwrap()); } fn print_divider(title: &str) {