diff --git a/crates/iota-genesis-builder/examples/snapshot_test_outputs.rs b/crates/iota-genesis-builder/examples/snapshot_test_outputs.rs index 1bedab191d0..6eb1de74775 100644 --- a/crates/iota-genesis-builder/examples/snapshot_test_outputs.rs +++ b/crates/iota-genesis-builder/examples/snapshot_test_outputs.rs @@ -10,7 +10,7 @@ use iota_genesis_builder::stardust::{ }; use iota_types::gas_coin::TOTAL_SUPPLY_IOTA; -fn parse_snapshot, const VERIFY: bool>(path: P) -> anyhow::Result<()> { +fn parse_snapshot(path: impl AsRef) -> anyhow::Result<()> { let file = File::open(path)?; let mut parser = HornetSnapshotParser::new::(file)?; @@ -43,11 +43,11 @@ async fn main() -> anyhow::Result<()> { new_path.push_str(¤t_path); } - parse_snapshot::<_, true>(¤t_path)?; + parse_snapshot::(¤t_path)?; - add_snapshot_test_outputs::<_, true>(¤t_path, &new_path).await?; + add_snapshot_test_outputs::(¤t_path, &new_path).await?; - parse_snapshot::<_, true>(&new_path)?; + parse_snapshot::(&new_path)?; Ok(()) } diff --git a/crates/iota-genesis-builder/src/stardust/migration/tests/executor.rs b/crates/iota-genesis-builder/src/stardust/migration/tests/executor.rs index a5eb345425f..ebf913a48e7 100644 --- a/crates/iota-genesis-builder/src/stardust/migration/tests/executor.rs +++ b/crates/iota-genesis-builder/src/stardust/migration/tests/executor.rs @@ -38,7 +38,7 @@ fn create_bag_with_pt() { .with_unlock_conditions([UnlockCondition::from( ImmutableAliasAddressUnlockCondition::new(owner), )]) - .finish_with_params(supply) + .finish() .unwrap(); let foundry_id = foundry.id(); let foundry_package_data = NativeTokenPackageData::new( diff --git a/crates/iota-genesis-builder/src/stardust/test_outputs/alias_ownership.rs b/crates/iota-genesis-builder/src/stardust/test_outputs/alias_ownership.rs new file mode 100644 index 00000000000..f31a4d8d25a --- /dev/null +++ b/crates/iota-genesis-builder/src/stardust/test_outputs/alias_ownership.rs @@ -0,0 +1,193 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::VecDeque; + +use iota_sdk::{ + client::secret::{mnemonic::MnemonicSecretManager, SecretManage}, + types::block::{ + address::{Address, AliasAddress}, + output::{ + feature::{Irc27Metadata, IssuerFeature, MetadataFeature}, + unlock_condition::{ + AddressUnlockCondition, GovernorAddressUnlockCondition, + ImmutableAliasAddressUnlockCondition, StateControllerAddressUnlockCondition, + }, + AliasId, AliasOutput, AliasOutputBuilder, BasicOutput, BasicOutputBuilder, Feature, + FoundryOutput, FoundryOutputBuilder, NftId, NftOutput, NftOutputBuilder, Output, + SimpleTokenScheme, UnlockCondition, OUTPUT_INDEX_RANGE, + }, + }, +}; +use rand::{rngs::StdRng, Rng, SeedableRng}; + +use crate::stardust::{ + test_outputs::{MERGE_MILESTONE_INDEX, MERGE_TIMESTAMP_SECS}, + types::{output_header::OutputHeader, output_index::OutputIndex}, +}; + +const MNEMONIC: &str = "few hood high omit camp keep burger give happy iron evolve draft few dawn pulp jazz box dash load snake gown bag draft car"; +const COIN_TYPE: u32 = 4218; +const OWNING_ALIAS_COUNT: u32 = 10; + +pub(crate) async fn outputs() -> anyhow::Result> { + let mut outputs = Vec::new(); + let secret_manager = MnemonicSecretManager::try_from_mnemonic(MNEMONIC)?; + + // create a randomized ownership dependency tree + let randomness_seed = rand::random(); + let mut rng = StdRng::seed_from_u64(randomness_seed); + println!("alias_ownership randomness seed: {randomness_seed}"); + + let alias_owners = secret_manager + .generate_ed25519_addresses(COIN_TYPE, 0, 0..OWNING_ALIAS_COUNT, None) + .await?; + + // create 10 different alias outputs with each owning various other assets + for alias_owner in alias_owners { + let alias_output_header = random_output_header(&mut rng); + + let alias_output = AliasOutputBuilder::new_with_amount( + 1_000_000, + (&alias_output_header.output_id()).into(), + ) + .add_unlock_condition(GovernorAddressUnlockCondition::new(alias_owner)) + .add_unlock_condition(StateControllerAddressUnlockCondition::new(alias_owner)) + .finish()?; + let alias_address = AliasAddress::new(*alias_output.alias_id()); + + // let this alias own various other assets, that may themselves own other assets + let max_depth = rng.gen_range(1usize..5); + let mut owning_addresses: VecDeque<(usize, Address)> = + vec![(0, alias_address.into())].into(); + + while let Some((depth, owning_addr)) = owning_addresses.pop_front() { + if depth > max_depth { + continue; + } + let mut serial_number = 1; + // create a random number of random assets + for _ in 0usize..rng.gen_range(1..=5) { + match rng.gen_range(0..=3) { + 0 => { + // alias + let (output_header, alias) = random_alias_output(&mut rng, owning_addr)?; + owning_addresses + .push_back((depth + 1, AliasAddress::new(*alias.alias_id()).into())); + outputs.push((output_header, alias.into())); + } + 1 => { + // nft + let (output_header, nft) = random_nft_output(&mut rng, owning_addr)?; + owning_addresses.push_back(( + depth + 1, + nft.nft_address(&output_header.output_id()).into(), + )); + outputs.push((output_header, nft.into())); + } + 2 => { + // basic + let (output_header, basic) = random_basic_output(&mut rng, owning_addr)?; + outputs.push((output_header, basic.into())); + } + 3 => { + // foundry + if let Address::Alias(owning_addr) = owning_addr { + let (output_header, foundry) = + random_foundry_output(&mut rng, &mut serial_number, owning_addr)?; + outputs.push((output_header, foundry.into())); + } + } + _ => unreachable!(), + } + } + } + } + Ok(outputs) +} + +fn random_basic_output( + rng: &mut StdRng, + owner: impl Into
, +) -> anyhow::Result<(OutputHeader, BasicOutput)> { + let basic_output_header = random_output_header(rng); + + let amount = rng.gen_range(1_000_000..10_000_000); + let basic_output = BasicOutputBuilder::new_with_amount(amount) + .add_unlock_condition(AddressUnlockCondition::new(owner)) + .finish()?; + + Ok((basic_output_header, basic_output)) +} + +fn random_nft_output( + rng: &mut StdRng, + owner: impl Into
, +) -> anyhow::Result<(OutputHeader, NftOutput)> { + let owner = owner.into(); + let nft_output_header = random_output_header(rng); + let nft_metadata = Irc27Metadata::new("image/png", "https://nft.org/nft.png".parse()?, "NFT") + .with_issuer_name("issuer_name") + .with_collection_name("collection_name") + .with_description("description"); + + let amount = rng.gen_range(1_000_000..10_000_000); + let nft_output = NftOutputBuilder::new_with_amount(amount, NftId::new(rng.gen())) + .add_unlock_condition(AddressUnlockCondition::new(owner.clone())) + .with_immutable_features(vec![ + Feature::Metadata(MetadataFeature::new(serde_json::to_vec(&nft_metadata)?)?), + Feature::Issuer(IssuerFeature::new(owner)), + ]) + .finish()?; + + Ok((nft_output_header, nft_output)) +} + +fn random_alias_output( + rng: &mut StdRng, + owner: impl Into
, +) -> anyhow::Result<(OutputHeader, AliasOutput)> { + let owner = owner.into(); + let alias_output_header = random_output_header(rng); + + let amount = rng.gen_range(1_000_000..10_000_000); + let alias_output = AliasOutputBuilder::new_with_amount(amount, AliasId::new(rng.gen())) + .add_unlock_condition(GovernorAddressUnlockCondition::new(owner.clone())) + .add_unlock_condition(StateControllerAddressUnlockCondition::new(owner)) + .finish()?; + + Ok((alias_output_header, alias_output)) +} + +fn random_foundry_output( + rng: &mut StdRng, + serial_number: &mut u32, + owner: impl Into, +) -> anyhow::Result<(OutputHeader, FoundryOutput)> { + let foundry_output_header = random_output_header(rng); + + let amount = rng.gen_range(1_000_000..10_000_000); + let supply = rng.gen_range(1_000_000..100_000_000); + let token_scheme = SimpleTokenScheme::new(supply, 0, supply)?; + let foundry_output = + FoundryOutputBuilder::new_with_amount(amount, *serial_number, token_scheme.into()) + .with_unlock_conditions([UnlockCondition::from( + ImmutableAliasAddressUnlockCondition::new(owner), + )]) + .finish()?; + + *serial_number += 1; + + Ok((foundry_output_header, foundry_output)) +} + +fn random_output_header(rng: &mut StdRng) -> OutputHeader { + OutputHeader::new_testing( + rng.gen(), + OutputIndex::new(rng.gen_range(OUTPUT_INDEX_RANGE)) + .expect("range is guaranteed to be valid"), + rng.gen(), + MERGE_MILESTONE_INDEX, + MERGE_TIMESTAMP_SECS, + ) +} diff --git a/crates/iota-genesis-builder/src/stardust/test_outputs/mod.rs b/crates/iota-genesis-builder/src/stardust/test_outputs/mod.rs index f28ad357d59..13cc2138d68 100644 --- a/crates/iota-genesis-builder/src/stardust/test_outputs/mod.rs +++ b/crates/iota-genesis-builder/src/stardust/test_outputs/mod.rs @@ -1,16 +1,12 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod alias_ownership; mod stardust_mix; mod vesting_schedule_entity; mod vesting_schedule_iota_airdrop; -use std::{ - fs::{File, OpenOptions}, - io::BufWriter, - path::Path, - str::FromStr, -}; +use std::{fs::File, io::BufWriter, path::Path, str::FromStr}; use iota_sdk::types::block::{ address::Ed25519Address, @@ -65,16 +61,13 @@ pub(crate) fn new_vested_output( } /// Adds outputs to test specific and intricate scenario in the full snapshot. -pub async fn add_snapshot_test_outputs + core::fmt::Debug, const VERIFY: bool>( - current_path: P, - new_path: P, +pub async fn add_snapshot_test_outputs( + current_path: impl AsRef + core::fmt::Debug, + new_path: impl AsRef + core::fmt::Debug, ) -> anyhow::Result<()> { let current_file = File::open(current_path)?; - let new_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(new_path)?; + let new_file = File::create(new_path)?; + let mut writer = IoPacker::new(BufWriter::new(new_file)); let mut parser = HornetSnapshotParser::new::(current_file)?; let output_to_decrease_amount_from = OutputId::from_str(OUTPUT_TO_DECREASE_AMOUNT_FROM)?; @@ -82,6 +75,7 @@ pub async fn add_snapshot_test_outputs + core::fmt::Debug, const let mut vested_index = u32::MAX; let new_outputs = [ + alias_ownership::outputs().await?, stardust_mix::outputs(&mut vested_index).await?, vesting_schedule_entity::outputs(&mut vested_index).await?, vesting_schedule_iota_airdrop::outputs(&mut vested_index).await?,