Skip to content

Commit

Permalink
feat(iota-genesis-builder): add test outputs owned by aliases (#797)
Browse files Browse the repository at this point in the history
* ...

* add owned test outputs

* add test outputs owned by alias

* fix alias unlock

* randomized ownership dependency tree

* clean up

* fix algo

* fix format

* state controller uc

* generate actual addresses

* change mnemonic

Co-authored-by: Thoralf-M <[email protected]>

* min amount fix for foundries

* rm supply check

* align!

Co-authored-by: Thibault Martinez <[email protected]>

* review

* fix compile

* disable global snapshot verification for test data

* consistency

* bubble up

---------

Co-authored-by: DaughterOfMars <[email protected]>
Co-authored-by: Thoralf-M <[email protected]>
Co-authored-by: Thibault Martinez <[email protected]>
  • Loading branch information
4 people authored Jul 8, 2024
1 parent 05469dd commit 173c6f2
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 19 deletions.
8 changes: 4 additions & 4 deletions crates/iota-genesis-builder/examples/snapshot_test_outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use iota_genesis_builder::stardust::{
};
use iota_types::gas_coin::TOTAL_SUPPLY_IOTA;

fn parse_snapshot<P: AsRef<Path>, const VERIFY: bool>(path: P) -> anyhow::Result<()> {
fn parse_snapshot<const VERIFY: bool>(path: impl AsRef<Path>) -> anyhow::Result<()> {
let file = File::open(path)?;
let mut parser = HornetSnapshotParser::new::<VERIFY>(file)?;

Expand Down Expand Up @@ -43,11 +43,11 @@ async fn main() -> anyhow::Result<()> {
new_path.push_str(&current_path);
}

parse_snapshot::<_, true>(&current_path)?;
parse_snapshot::<false>(&current_path)?;

add_snapshot_test_outputs::<_, true>(&current_path, &new_path).await?;
add_snapshot_test_outputs::<false>(&current_path, &new_path).await?;

parse_snapshot::<_, true>(&new_path)?;
parse_snapshot::<false>(&new_path)?;

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Vec<(OutputHeader, Output)>> {
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<Address>,
) -> 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<Address>,
) -> 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<Address>,
) -> 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<AliasAddress>,
) -> 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,
)
}
22 changes: 8 additions & 14 deletions crates/iota-genesis-builder/src/stardust/test_outputs/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -65,23 +61,21 @@ 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<P: AsRef<Path> + core::fmt::Debug, const VERIFY: bool>(
current_path: P,
new_path: P,
pub async fn add_snapshot_test_outputs<const VERIFY: bool>(
current_path: impl AsRef<Path> + core::fmt::Debug,
new_path: impl AsRef<Path> + 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::<VERIFY>(current_file)?;
let output_to_decrease_amount_from = OutputId::from_str(OUTPUT_TO_DECREASE_AMOUNT_FROM)?;
let mut new_header = parser.header.clone();
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?,
Expand Down

0 comments on commit 173c6f2

Please sign in to comment.