From b55ec0c8aaa3e3fb6ecad0c94df1a17b9ced227f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 12 Dec 2023 10:43:23 +0100 Subject: [PATCH 01/51] Refactor MetadataFeature --- Cargo.lock | 6 +- bindings/core/Cargo.toml | 2 +- bindings/core/src/method/wallet.rs | 186 ++++---- bindings/core/src/method_handler/wallet.rs | 120 ++--- cli/src/wallet_cli/mod.rs | 225 +++++----- sdk/Cargo.toml | 10 +- .../client/output/build_account_output.rs | 6 +- .../client/output/build_basic_output.rs | 11 +- .../client/output/build_nft_output.rs | 6 +- sdk/examples/how_tos/outputs/features.rs | 21 +- sdk/src/types/block/error.rs | 17 +- .../types/block/output/feature/metadata.rs | 202 ++++++--- sdk/src/types/block/output/feature/mod.rs | 6 +- sdk/src/types/block/output/mod.rs | 2 +- sdk/src/types/block/rand/output/feature.rs | 42 +- sdk/src/types/fuzz/Cargo.toml | 2 +- sdk/src/wallet/operations/balance.rs | 14 +- .../wallet/operations/output_consolidation.rs | 18 +- .../wallet/operations/participation/mod.rs | 413 +++++++++--------- .../transaction/high_level/create_account.rs | 31 +- .../high_level/minting/create_native_token.rs | 8 +- .../high_level/minting/mint_nfts.rs | 14 +- .../operations/transaction/prepare_output.rs | 11 +- sdk/src/wallet/storage/constants.rs | 8 +- sdk/src/wallet/storage/mod.rs | 8 +- sdk/src/wallet/types/balance.rs | 19 +- .../client/input_selection/nft_outputs.rs | 119 ++--- sdk/tests/wallet/balance.rs | 86 ++-- sdk/tests/wallet/native_tokens.rs | 27 +- sdk/tests/wallet/output_preparation.rs | 20 +- 30 files changed, 899 insertions(+), 761 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0aa691bb5..c03c77e00a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2122,8 +2122,7 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "packable" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe35ea7a5959be5a87d24bcb31ed984580d9cd321c264c266818fff8cd47b3d" +source = "git+https://github.com/iotaledger/common-rs?rev=c72287fa24254b349f6e935b847485b0be141557#c72287fa24254b349f6e935b847485b0be141557" dependencies = [ "autocfg", "packable-derive", @@ -2134,8 +2133,7 @@ dependencies = [ [[package]] name = "packable-derive" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "858971e010057f7bcae183e545085b83d41280ca8abe0333613a7135fbb54430" +source = "git+https://github.com/iotaledger/common-rs?rev=c72287fa24254b349f6e935b847485b0be141557#c72287fa24254b349f6e935b847485b0be141557" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro-error", diff --git a/bindings/core/Cargo.toml b/bindings/core/Cargo.toml index 84b855e884..d72ce04445 100644 --- a/bindings/core/Cargo.toml +++ b/bindings/core/Cargo.toml @@ -23,7 +23,7 @@ iota-crypto = { version = "0.23.0", default-features = false, features = [ "bip44", ] } log = { version = "0.4.20", default-features = false } -packable = { version = "0.10.0", default-features = false } +packable = { git = "https://github.com/iotaledger/common-rs", rev = "c72287fa24254b349f6e935b847485b0be141557", default-features = false } prefix-hex = { version = "0.7.1", default-features = false } primitive-types = { version = "0.12.2", default-features = false } serde = { version = "1.0.195", default-features = false } diff --git a/bindings/core/src/method/wallet.rs b/bindings/core/src/method/wallet.rs index 0ed337c3f4..91802dec34 100644 --- a/bindings/core/src/method/wallet.rs +++ b/bindings/core/src/method/wallet.rs @@ -8,12 +8,12 @@ use crypto::keys::bip44::Bip44; use derivative::Derivative; #[cfg(feature = "events")] use iota_sdk::wallet::events::types::{WalletEvent, WalletEventType}; -#[cfg(feature = "participation")] -use iota_sdk::{ - client::node_manager::node::Node, - types::api::plugins::participation::types::{ParticipationEventId, ParticipationEventType}, - wallet::types::participation::ParticipationEventRegistrationOptions, -}; +// #[cfg(feature = "participation")] +// use iota_sdk::{ +// client::node_manager::node::Node, +// types::api::plugins::participation::types::{ParticipationEventId, ParticipationEventType}, +// wallet::types::participation::ParticipationEventRegistrationOptions, +// }; use iota_sdk::{ client::{ api::{input_selection::Burn, PreparedTransactionDataDto, SignedTransactionDataDto}, @@ -122,12 +122,12 @@ pub enum WalletMethod { /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) #[serde(rename_all = "camelCase")] ClaimOutputs { output_ids_to_claim: Vec }, - /// Removes a previously registered participation event from local storage. - /// Expected response: [`Ok`](crate::Response::Ok) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - DeregisterParticipationEvent { event_id: ParticipationEventId }, + // /// Removes a previously registered participation event from local storage. + // /// Expected response: [`Ok`](crate::Response::Ok) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // #[serde(rename_all = "camelCase")] + // DeregisterParticipationEvent { event_id: ParticipationEventId }, /// Get the wallet address. /// Expected response: [`Address`](crate::Response::Address) GetAddress, @@ -147,48 +147,48 @@ pub enum WalletMethod { /// Expected response: [`OutputData`](crate::Response::OutputData) #[serde(rename_all = "camelCase")] GetOutput { output_id: OutputId }, - /// Expected response: [`ParticipationEvent`](crate::Response::ParticipationEvent) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - GetParticipationEvent { event_id: ParticipationEventId }, - /// Expected response: [`ParticipationEventIds`](crate::Response::ParticipationEventIds) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - GetParticipationEventIds { - node: Node, - event_type: Option, - }, - /// Expected response: - /// [`ParticipationEventStatus`](crate::Response::ParticipationEventStatus) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - GetParticipationEventStatus { event_id: ParticipationEventId }, - /// Expected response: [`ParticipationEvents`](crate::Response::ParticipationEvents) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - GetParticipationEvents, - /// Calculates a participation overview for the wallet. If event_ids are provided, only return outputs and tracked - /// participations for them. - /// Expected response: [`ParticipationOverview`](crate::Response::ParticipationOverview) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - GetParticipationOverview { - event_ids: Option>, - }, + // /// Expected response: [`ParticipationEvent`](crate::Response::ParticipationEvent) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // #[serde(rename_all = "camelCase")] + // GetParticipationEvent { event_id: ParticipationEventId }, + // /// Expected response: [`ParticipationEventIds`](crate::Response::ParticipationEventIds) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // #[serde(rename_all = "camelCase")] + // GetParticipationEventIds { + // node: Node, + // event_type: Option, + // }, + // /// Expected response: + // /// [`ParticipationEventStatus`](crate::Response::ParticipationEventStatus) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // #[serde(rename_all = "camelCase")] + // GetParticipationEventStatus { event_id: ParticipationEventId }, + // /// Expected response: [`ParticipationEvents`](crate::Response::ParticipationEvents) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // GetParticipationEvents, + // /// Calculates a participation overview for the wallet. If event_ids are provided, only return outputs and + // tracked /// participations for them. + // /// Expected response: [`ParticipationOverview`](crate::Response::ParticipationOverview) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // #[serde(rename_all = "camelCase")] + // GetParticipationOverview { + // event_ids: Option>, + // }, /// Get the [`TransactionWithMetadata`](iota_sdk::wallet::types::TransactionWithMetadata) of a transaction stored /// in the wallet. /// Expected response: [`Transaction`](crate::Response::Transaction) #[serde(rename_all = "camelCase")] GetTransaction { transaction_id: TransactionId }, - /// Get the wallet's total voting power (voting or NOT voting). - /// Expected response: [`VotingPower`](crate::Response::VotingPower) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - GetVotingPower, + // /// Get the wallet's total voting power (voting or NOT voting). + // /// Expected response: [`VotingPower`](crate::Response::VotingPower) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // GetVotingPower, /// Returns the implicit account creation address of the wallet if it is Ed25519 based. /// Expected response: [`Bech32Address`](crate::Response::Bech32Address) ImplicitAccountCreationAddress, @@ -246,26 +246,26 @@ pub enum WalletMethod { params: CreateNativeTokenParams, options: Option, }, - /// Reduces a wallet's "voting power" by a given amount. - /// This will stop voting, but the voting data isn't lost and calling `Vote` without parameters will revote. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - PrepareDecreaseVotingPower { - #[serde(with = "iota_sdk::utils::serde::string")] - amount: u64, - }, - /// Designates a given amount of tokens towards a wallet's "voting power" by creating a - /// special output, which is really a basic one with some metadata. - /// This will stop voting in most cases (if there is a remainder output), but the voting data isn't lost and - /// calling `Vote` without parameters will revote. Expected response: - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - PrepareIncreaseVotingPower { - #[serde(with = "iota_sdk::utils::serde::string")] - amount: u64, - }, + // /// Reduces a wallet's "voting power" by a given amount. + // /// This will stop voting, but the voting data isn't lost and calling `Vote` without parameters will revote. + // /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // PrepareDecreaseVotingPower { + // #[serde(with = "iota_sdk::utils::serde::string")] + // amount: u64, + // }, + // /// Designates a given amount of tokens towards a wallet's "voting power" by creating a + // /// special output, which is really a basic one with some metadata. + // /// This will stop voting in most cases (if there is a remainder output), but the voting data isn't lost and + // /// calling `Vote` without parameters will revote. Expected response: + // /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // PrepareIncreaseVotingPower { + // #[serde(with = "iota_sdk::utils::serde::string")] + // amount: u64, + // }, /// Prepare to melt native tokens. This happens with the foundry output which minted them, by increasing it's /// `melted_tokens` field. /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) @@ -318,36 +318,36 @@ pub enum WalletMethod { params: Vec, options: Option, }, - /// Stop participating for an event. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - PrepareStopParticipating { event_id: ParticipationEventId }, + // /// Stop participating for an event. + // /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // #[serde(rename_all = "camelCase")] + // PrepareStopParticipating { event_id: ParticipationEventId }, /// Prepare transaction. /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) PrepareTransaction { outputs: Vec, options: Option, }, - /// Vote for a participation event. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - PrepareVote { - event_id: Option, - answers: Option>, - }, - /// Stores participation information locally and returns the event. - /// - /// This will NOT store the node url and auth inside the client options. - /// Expected response: [`ParticipationEvents`](crate::Response::ParticipationEvents) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - RegisterParticipationEvents { - options: ParticipationEventRegistrationOptions, - }, + // /// Vote for a participation event. + // /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // #[serde(rename_all = "camelCase")] + // PrepareVote { + // event_id: Option, + // answers: Option>, + // }, + // /// Stores participation information locally and returns the event. + // /// + // /// This will NOT store the node url and auth inside the client options. + // /// Expected response: [`ParticipationEvents`](crate::Response::ParticipationEvents) + // #[cfg(feature = "participation")] + // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + // RegisterParticipationEvents { + // options: ParticipationEventRegistrationOptions, + // }, /// Reissues a transaction sent from the wallet for a provided transaction id until it's /// included (referenced by a milestone). Returns the included block id. /// Expected response: [`BlockId`](crate::Response::BlockId) diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index 3b088222e0..a574bca7a1 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -138,11 +138,11 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let transaction = wallet.claim_outputs(output_ids_to_claim.to_vec()).await?; Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) } - #[cfg(feature = "participation")] - WalletMethod::DeregisterParticipationEvent { event_id } => { - wallet.deregister_participation_event(&event_id).await?; - Response::Ok - } + // #[cfg(feature = "participation")] + // WalletMethod::DeregisterParticipationEvent { event_id } => { + // wallet.deregister_participation_event(&event_id).await?; + // Response::Ok + // } WalletMethod::GetAddress => { let address = wallet.address().await; Response::Address(address) @@ -163,31 +163,31 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM WalletMethod::GetOutput { output_id } => { Response::OutputData(wallet.data().await.get_output(&output_id).cloned().map(Box::new)) } - #[cfg(feature = "participation")] - WalletMethod::GetParticipationEvent { event_id } => { - let event_and_nodes = wallet.get_participation_event(event_id).await?; - Response::ParticipationEvent(event_and_nodes) - } - #[cfg(feature = "participation")] - WalletMethod::GetParticipationEventIds { node, event_type } => { - let event_ids = wallet.get_participation_event_ids(&node, event_type).await?; - Response::ParticipationEventIds(event_ids) - } - #[cfg(feature = "participation")] - WalletMethod::GetParticipationEventStatus { event_id } => { - let event_status = wallet.get_participation_event_status(&event_id).await?; - Response::ParticipationEventStatus(event_status) - } - #[cfg(feature = "participation")] - WalletMethod::GetParticipationEvents => { - let events = wallet.get_participation_events().await?; - Response::ParticipationEvents(events) - } - #[cfg(feature = "participation")] - WalletMethod::GetParticipationOverview { event_ids } => { - let overview = wallet.get_participation_overview(event_ids).await?; - Response::ParticipationOverview(overview) - } + // #[cfg(feature = "participation")] + // WalletMethod::GetParticipationEvent { event_id } => { + // let event_and_nodes = wallet.get_participation_event(event_id).await?; + // Response::ParticipationEvent(event_and_nodes) + // } + // #[cfg(feature = "participation")] + // WalletMethod::GetParticipationEventIds { node, event_type } => { + // let event_ids = wallet.get_participation_event_ids(&node, event_type).await?; + // Response::ParticipationEventIds(event_ids) + // } + // #[cfg(feature = "participation")] + // WalletMethod::GetParticipationEventStatus { event_id } => { + // let event_status = wallet.get_participation_event_status(&event_id).await?; + // Response::ParticipationEventStatus(event_status) + // } + // #[cfg(feature = "participation")] + // WalletMethod::GetParticipationEvents => { + // let events = wallet.get_participation_events().await?; + // Response::ParticipationEvents(events) + // } + // #[cfg(feature = "participation")] + // WalletMethod::GetParticipationOverview { event_ids } => { + // let overview = wallet.get_participation_overview(event_ids).await?; + // Response::ParticipationOverview(overview) + // } WalletMethod::GetTransaction { transaction_id } => Response::Transaction( wallet .data() @@ -196,11 +196,11 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM .map(TransactionWithMetadataDto::from) .map(Box::new), ), - #[cfg(feature = "participation")] - WalletMethod::GetVotingPower => { - let voting_power = wallet.get_voting_power().await?; - Response::VotingPower(voting_power.to_string()) - } + // #[cfg(feature = "participation")] + // WalletMethod::GetVotingPower => { + // let voting_power = wallet.get_voting_power().await?; + // Response::VotingPower(voting_power.to_string()) + // } WalletMethod::ImplicitAccountCreationAddress => { let implicit_account_creation_address = wallet.implicit_account_creation_address().await?; Response::Bech32Address(implicit_account_creation_address) @@ -273,11 +273,11 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let data = wallet.prepare_melt_native_token(token_id, melt_amount, options).await?; Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) } - #[cfg(feature = "participation")] - WalletMethod::PrepareDecreaseVotingPower { amount } => { - let data = wallet.prepare_decrease_voting_power(amount).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } + // #[cfg(feature = "participation")] + // WalletMethod::PrepareDecreaseVotingPower { amount } => { + // let data = wallet.prepare_decrease_voting_power(amount).await?; + // Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + // } WalletMethod::PrepareMintNativeToken { token_id, mint_amount, @@ -286,11 +286,11 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let data = wallet.prepare_mint_native_token(token_id, mint_amount, options).await?; Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) } - #[cfg(feature = "participation")] - WalletMethod::PrepareIncreaseVotingPower { amount } => { - let data = wallet.prepare_increase_voting_power(amount).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } + // #[cfg(feature = "participation")] + // WalletMethod::PrepareIncreaseVotingPower { amount } => { + // let data = wallet.prepare_increase_voting_power(amount).await?; + // Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + // } WalletMethod::PrepareMintNfts { params, options } => { let data = wallet.prepare_mint_nfts(params, options).await?; Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) @@ -318,25 +318,25 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let data = wallet.prepare_send_nft(params.clone(), options).await?; Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) } - #[cfg(feature = "participation")] - WalletMethod::PrepareStopParticipating { event_id } => { - let data = wallet.prepare_stop_participating(event_id).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } + // #[cfg(feature = "participation")] + // WalletMethod::PrepareStopParticipating { event_id } => { + // let data = wallet.prepare_stop_participating(event_id).await?; + // Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + // } WalletMethod::PrepareTransaction { outputs, options } => { let data = wallet.prepare_transaction(outputs, options).await?; Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) } - #[cfg(feature = "participation")] - WalletMethod::PrepareVote { event_id, answers } => { - let data = wallet.prepare_vote(event_id, answers).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - #[cfg(feature = "participation")] - WalletMethod::RegisterParticipationEvents { options } => { - let events = wallet.register_participation_events(&options).await?; - Response::ParticipationEvents(events) - } + // #[cfg(feature = "participation")] + // WalletMethod::PrepareVote { event_id, answers } => { + // let data = wallet.prepare_vote(event_id, answers).await?; + // Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + // } + // #[cfg(feature = "participation")] + // WalletMethod::RegisterParticipationEvents { options } => { + // let events = wallet.register_participation_events(&options).await?; + // Response::ParticipationEvents(events) + // } WalletMethod::ReissueTransactionUntilIncluded { transaction_id, interval, diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 73fdfae5bc..cc43bc38a5 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -10,17 +10,14 @@ use colored::Colorize; use iota_sdk::{ client::request_funds_from_faucet, crypto::signatures::ed25519::PublicKey, - types::{ - api::plugins::participation::types::ParticipationEventId, - block::{ - address::{Bech32Address, ToBech32Ext}, - output::{ - unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, FoundryId, NativeToken, - NativeTokensBuilder, NftId, Output, OutputId, TokenId, - }, - payload::signed_transaction::TransactionId, - slot::SlotIndex, + types::block::{ + address::{Bech32Address, ToBech32Ext}, + output::{ + unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, FoundryId, NativeToken, + NativeTokensBuilder, NftId, Output, OutputId, TokenId, }, + payload::signed_transaction::TransactionId, + slot::SlotIndex, }, utils::ConvertTo, wallet::{ @@ -238,40 +235,40 @@ pub enum WalletCommand { }, /// List the unspent outputs. UnspentOutputs, - /// Cast votes for an event. - Vote { - /// Event ID for which to cast votes, e.g. 0xdc049a721dc65ec342f836c876ec15631ed915cd55213cee39e8d1c821c751f2. - event_id: ParticipationEventId, - /// Answers to the event questions. - answers: Vec, - }, - /// Stop participating to an event. - StopParticipating { - /// Event ID for which to stop participation, e.g. - /// 0xdc049a721dc65ec342f836c876ec15631ed915cd55213cee39e8d1c821c751f2. - event_id: ParticipationEventId, - }, - /// Get the participation overview of the wallet. - ParticipationOverview { - /// Event IDs for which to get the participation overview, e.g. - /// 0xdc049a721dc65ec342f836c876ec15631ed915cd55213cee39e8d1c821c751f2... - #[arg(short, long, num_args = 1.., value_delimiter = ' ')] - event_ids: Vec, - }, - /// Get the voting power of the wallet. - VotingPower, - /// Increase the voting power of the wallet. - IncreaseVotingPower { - /// Amount to increase the voting power by, e.g. 100. - amount: u64, - }, - /// Decrease the voting power of the wallet. - DecreaseVotingPower { - /// Amount to decrease the voting power by, e.g. 100. - amount: u64, - }, - /// Get the voting output of the wallet. - VotingOutput, + // /// Cast votes for an event. + // Vote { + // /// Event ID for which to cast votes, e.g. + // 0xdc049a721dc65ec342f836c876ec15631ed915cd55213cee39e8d1c821c751f2. event_id: ParticipationEventId, + // /// Answers to the event questions. + // answers: Vec, + // }, + // /// Stop participating to an event. + // StopParticipating { + // /// Event ID for which to stop participation, e.g. + // /// 0xdc049a721dc65ec342f836c876ec15631ed915cd55213cee39e8d1c821c751f2. + // event_id: ParticipationEventId, + // }, + // /// Get the participation overview of the wallet. + // ParticipationOverview { + // /// Event IDs for which to get the participation overview, e.g. + // /// 0xdc049a721dc65ec342f836c876ec15631ed915cd55213cee39e8d1c821c751f2... + // #[arg(short, long, num_args = 1.., value_delimiter = ' ')] + // event_ids: Vec, + // }, + // /// Get the voting power of the wallet. + // VotingPower, + // /// Increase the voting power of the wallet. + // IncreaseVotingPower { + // /// Amount to increase the voting power by, e.g. 100. + // amount: u64, + // }, + // /// Decrease the voting power of the wallet. + // DecreaseVotingPower { + // /// Amount to decrease the voting power by, e.g. 100. + // amount: u64, + // }, + // /// Get the voting output of the wallet. + // VotingOutput, } /// Select by transaction ID or list index @@ -920,80 +917,80 @@ pub async fn unspent_outputs_command(wallet: &Wallet) -> Result<(), Error> { ) } -pub async fn vote_command(wallet: &Wallet, event_id: ParticipationEventId, answers: Vec) -> Result<(), Error> { - let transaction = wallet.vote(Some(event_id), Some(answers)).await?; +// pub async fn vote_command(wallet: &Wallet, event_id: ParticipationEventId, answers: Vec) -> Result<(), Error> { +// let transaction = wallet.vote(Some(event_id), Some(answers)).await?; - println_log_info!( - "Voting transaction sent:\n{:?}\n{:?}", - transaction.transaction_id, - transaction.block_id - ); +// println_log_info!( +// "Voting transaction sent:\n{:?}\n{:?}", +// transaction.transaction_id, +// transaction.block_id +// ); - Ok(()) -} +// Ok(()) +// } -pub async fn stop_participating_command(wallet: &Wallet, event_id: ParticipationEventId) -> Result<(), Error> { - let transaction = wallet.stop_participating(event_id).await?; +// pub async fn stop_participating_command(wallet: &Wallet, event_id: ParticipationEventId) -> Result<(), Error> { +// let transaction = wallet.stop_participating(event_id).await?; - println_log_info!( - "Stop participating transaction sent:\n{:?}\n{:?}", - transaction.transaction_id, - transaction.block_id - ); +// println_log_info!( +// "Stop participating transaction sent:\n{:?}\n{:?}", +// transaction.transaction_id, +// transaction.block_id +// ); - Ok(()) -} +// Ok(()) +// } -pub async fn participation_overview_command( - wallet: &Wallet, - event_ids: Option>, -) -> Result<(), Error> { - let participation_overview = wallet.get_participation_overview(event_ids).await?; +// pub async fn participation_overview_command( +// wallet: &Wallet, +// event_ids: Option>, +// ) -> Result<(), Error> { +// let participation_overview = wallet.get_participation_overview(event_ids).await?; - println_log_info!("Participation overview: {participation_overview:?}"); +// println_log_info!("Participation overview: {participation_overview:?}"); - Ok(()) -} +// Ok(()) +// } -pub async fn voting_power_command(wallet: &Wallet) -> Result<(), Error> { - let voting_power = wallet.get_voting_power().await?; +// pub async fn voting_power_command(wallet: &Wallet) -> Result<(), Error> { +// let voting_power = wallet.get_voting_power().await?; - println_log_info!("Voting power: {voting_power}"); +// println_log_info!("Voting power: {voting_power}"); - Ok(()) -} +// Ok(()) +// } -pub async fn increase_voting_power_command(wallet: &Wallet, amount: u64) -> Result<(), Error> { - let transaction = wallet.increase_voting_power(amount).await?; +// pub async fn increase_voting_power_command(wallet: &Wallet, amount: u64) -> Result<(), Error> { +// let transaction = wallet.increase_voting_power(amount).await?; - println_log_info!( - "Increase voting power transaction sent:\n{:?}\n{:?}", - transaction.transaction_id, - transaction.block_id - ); +// println_log_info!( +// "Increase voting power transaction sent:\n{:?}\n{:?}", +// transaction.transaction_id, +// transaction.block_id +// ); - Ok(()) -} +// Ok(()) +// } -pub async fn decrease_voting_power_command(wallet: &Wallet, amount: u64) -> Result<(), Error> { - let transaction = wallet.decrease_voting_power(amount).await?; +// pub async fn decrease_voting_power_command(wallet: &Wallet, amount: u64) -> Result<(), Error> { +// let transaction = wallet.decrease_voting_power(amount).await?; - println_log_info!( - "Decrease voting power transaction sent:\n{:?}\n{:?}", - transaction.transaction_id, - transaction.block_id - ); +// println_log_info!( +// "Decrease voting power transaction sent:\n{:?}\n{:?}", +// transaction.transaction_id, +// transaction.block_id +// ); - Ok(()) -} +// Ok(()) +// } -pub async fn voting_output_command(wallet: &Wallet) -> Result<(), Error> { - let output = wallet.get_voting_output().await?; +// pub async fn voting_output_command(wallet: &Wallet) -> Result<(), Error> { +// let output = wallet.get_voting_output().await?; - println_log_info!("Voting output: {output:?}"); +// println_log_info!("Voting output: {output:?}"); - Ok(()) -} +// Ok(()) +// } async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> { let address = wallet.address().await; @@ -1241,22 +1238,22 @@ pub async fn prompt_internal( transactions_command(wallet, show_details).await } WalletCommand::UnspentOutputs => unspent_outputs_command(wallet).await, - WalletCommand::Vote { event_id, answers } => vote_command(wallet, event_id, answers).await, - WalletCommand::StopParticipating { event_id } => { - stop_participating_command(wallet, event_id).await - } - WalletCommand::ParticipationOverview { event_ids } => { - let event_ids = (!event_ids.is_empty()).then_some(event_ids); - participation_overview_command(wallet, event_ids).await - } - WalletCommand::VotingPower => voting_power_command(wallet).await, - WalletCommand::IncreaseVotingPower { amount } => { - increase_voting_power_command(wallet, amount).await - } - WalletCommand::DecreaseVotingPower { amount } => { - decrease_voting_power_command(wallet, amount).await - } - WalletCommand::VotingOutput => voting_output_command(wallet).await, + // WalletCommand::Vote { event_id, answers } => vote_command(wallet, event_id, answers).await, + // WalletCommand::StopParticipating { event_id } => { + // stop_participating_command(wallet, event_id).await + // } + // WalletCommand::ParticipationOverview { event_ids } => { + // let event_ids = (!event_ids.is_empty()).then_some(event_ids); + // participation_overview_command(wallet, event_ids).await + // } + // WalletCommand::VotingPower => voting_power_command(wallet).await, + // WalletCommand::IncreaseVotingPower { amount } => { + // increase_voting_power_command(wallet, amount).await + // } + // WalletCommand::DecreaseVotingPower { amount } => { + // decrease_voting_power_command(wallet, amount).await + // } + // WalletCommand::VotingOutput => voting_output_command(wallet).await, } .unwrap_or_else(|err| { println_log_error!("{err}"); diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index d36f4d4ccc..fa4066fb30 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -45,7 +45,7 @@ iota-crypto = { version = "0.23.0", default-features = false, features = [ "secp256k1", ] } iterator-sorted = { version = "0.1.0", default-features = false } -packable = { version = "0.10.0", default-features = false, features = [ +packable = { git = "https://github.com/iotaledger/common-rs", rev = "c72287fa24254b349f6e935b847485b0be141557", default-features = false, features = [ "primitive-types", ] } paste = { version = "1.0.14", default-features = false } @@ -639,10 +639,10 @@ name = "wallet_ledger_nano" path = "examples/wallet/ledger_nano.rs" required-features = ["wallet", "ledger_nano"] -[[example]] -name = "wallet_participation" -path = "examples/wallet/participation.rs" -required-features = ["wallet", "participation"] +# [[example]] +# name = "wallet_participation" +# path = "examples/wallet/participation.rs" +# required-features = ["wallet", "participation"] [[example]] name = "logger" diff --git a/sdk/examples/client/output/build_account_output.rs b/sdk/examples/client/output/build_account_output.rs index c575d37960..82f84888ea 100644 --- a/sdk/examples/client/output/build_account_output.rs +++ b/sdk/examples/client/output/build_account_output.rs @@ -49,9 +49,11 @@ async fn main() -> Result<()> { // Account id needs to be null the first time let account_output = AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .add_feature(SenderFeature::new(address.clone())) - .add_feature(MetadataFeature::new(metadata)?) + // TODO: enable again when MetadataFeature is cleared up + // .add_feature(MetadataFeature::new(metadata)?) .add_immutable_feature(IssuerFeature::new(address.clone())) - .add_immutable_feature(MetadataFeature::new(metadata)?) + // TODO: enable again when MetadataFeature is cleared up + // .add_immutable_feature(MetadataFeature::new(metadata)?) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output()?; diff --git a/sdk/examples/client/output/build_basic_output.rs b/sdk/examples/client/output/build_basic_output.rs index 37102b74c2..e5bc294d7d 100644 --- a/sdk/examples/client/output/build_basic_output.rs +++ b/sdk/examples/client/output/build_basic_output.rs @@ -41,11 +41,12 @@ async fn main() -> Result<()> { let outputs = [ // most simple output basic_output_builder.clone().finish_output()?, - // with metadata feature block - basic_output_builder - .clone() - .add_feature(MetadataFeature::new(METADATA)?) - .finish_output()?, + // TODO: enable again when MetadataFeature is cleared up + // // with metadata feature block + // basic_output_builder + // .clone() + // .add_feature(MetadataFeature::new(METADATA)?) + // .finish_output()?, // with storage deposit return basic_output_builder .clone() diff --git a/sdk/examples/client/output/build_nft_output.rs b/sdk/examples/client/output/build_nft_output.rs index 3fedaf5532..5328edd896 100644 --- a/sdk/examples/client/output/build_nft_output.rs +++ b/sdk/examples/client/output/build_nft_output.rs @@ -63,10 +63,12 @@ async fn main() -> Result<()> { let nft_output = NftOutputBuilder::new_with_minimum_amount(storage_score_params, NftId::null()) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) .add_feature(SenderFeature::new(address.clone())) - .add_feature(MetadataFeature::new(MUTABLE_METADATA)?) + // TODO: enable again when MetadataFeature is cleared up + // .add_feature(MetadataFeature::new(MUTABLE_METADATA)?) .add_feature(TagFeature::new(TAG)?) .add_immutable_feature(IssuerFeature::new(address)) - .add_immutable_feature(MetadataFeature::new(tip_27_immutable_metadata)?) + // TODO: enable again when MetadataFeature is cleared up + // .add_immutable_feature(MetadataFeature::new(tip_27_immutable_metadata)?) .finish_output()?; println!("{nft_output:#?}"); diff --git a/sdk/examples/how_tos/outputs/features.rs b/sdk/examples/how_tos/outputs/features.rs index 78d991872f..5001088824 100644 --- a/sdk/examples/how_tos/outputs/features.rs +++ b/sdk/examples/how_tos/outputs/features.rs @@ -53,16 +53,17 @@ async fn main() -> Result<()> { .clone() .add_immutable_feature(IssuerFeature::new(address)) .finish_output()?, - // with metadata feature block - nft_output_builder - .clone() - .add_feature(MetadataFeature::new("Hello, World!")?) - .finish_output()?, - // with immutable metadata feature block - nft_output_builder - .clone() - .add_immutable_feature(MetadataFeature::new("Hello, World!")?) - .finish_output()?, + // TODO: enable again when MetadataFeature is cleared up + // // with metadata feature block + // nft_output_builder + // .clone() + // .add_feature(MetadataFeature::new("Hello, World!")?) + // .finish_output()?, + // // with immutable metadata feature block + // nft_output_builder + // .clone() + // .add_immutable_feature(MetadataFeature::new("Hello, World!")?) + // .finish_output()?, // with tag feature nft_output_builder .add_feature(TagFeature::new("Hello, World!")?) diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index ee8d5b44c8..4a64923e73 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -18,7 +18,8 @@ use crate::types::block::{ output::{ feature::{BlockIssuerKeyCount, FeatureCount}, unlock_condition::UnlockConditionCount, - AccountId, AnchorId, ChainId, MetadataFeatureLength, NativeTokenCount, NftId, OutputIndex, TagFeatureLength, + AccountId, AnchorId, ChainId, MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength, + NativeTokenCount, NftId, OutputIndex, TagFeatureLength, }, payload::{ tagged_data::{TagLength, TaggedDataLength}, @@ -56,6 +57,7 @@ pub enum Error { InvalidAccountIndex(>::Error), InvalidAnchorIndex(>::Error), InvalidBlockBodyKind(u8), + NonAsciiMetadataKey(Vec), InvalidRewardInputIndex(>::Error), InvalidStorageDepositAmount(u64), /// Invalid transaction failure reason byte. @@ -104,7 +106,10 @@ pub enum Error { }, InvalidBlockLength(usize), InvalidManaValue(u64), + InvalidMetadataFeature(String), InvalidMetadataFeatureLength(>::Error), + InvalidMetadataFeatureKeyLength(>::Error), + InvalidMetadataFeatureValueLength(>::Error), InvalidNativeTokenCount(>::Error), InvalidNetworkName(FromUtf8Error), InvalidManaDecayFactors, @@ -249,6 +254,7 @@ impl fmt::Display for Error { write!(f, "invalid capability byte at index {index}: {byte:x}") } Self::InvalidBlockBodyKind(k) => write!(f, "invalid block body kind: {k}"), + Self::NonAsciiMetadataKey(b) => write!(f, "non ASCII key: {b:?}"), Self::InvalidRewardInputIndex(idx) => write!(f, "invalid reward input index: {idx}"), Self::InvalidStorageDepositAmount(amount) => { write!(f, "invalid storage deposit amount: {amount}") @@ -303,9 +309,18 @@ impl fmt::Display for Error { Self::InvalidInputOutputIndex(index) => write!(f, "invalid input or output index: {index}"), Self::InvalidBlockLength(length) => write!(f, "invalid block length {length}"), Self::InvalidManaValue(mana) => write!(f, "invalid mana value: {mana}"), + Self::InvalidMetadataFeature(e) => { + write!(f, "invalid metadata feature: {e}") + } Self::InvalidMetadataFeatureLength(length) => { write!(f, "invalid metadata feature length: {length}") } + Self::InvalidMetadataFeatureKeyLength(length) => { + write!(f, "invalid metadata feature key length: {length}") + } + Self::InvalidMetadataFeatureValueLength(length) => { + write!(f, "invalid metadata feature value length: {length}") + } Self::InvalidNativeTokenCount(count) => write!(f, "invalid native token count: {count}"), Self::InvalidNetworkName(err) => write!(f, "invalid network name: {err}"), Self::InvalidManaDecayFactors => write!(f, "invalid mana decay factors"), diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 090e5ebda7..058de7af80 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -1,24 +1,52 @@ // Copyright 2021-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{boxed::Box, string::String, vec::Vec}; -use core::{ops::RangeInclusive, str::FromStr}; +use alloc::{collections::BTreeMap, string::String, vec::Vec}; +use core::ops::{Deref, RangeInclusive}; -use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix}; +use packable::{ + bounded::{BoundedU16, BoundedU8}, + prefix::{BTreeMapPrefix, BoxedSlicePrefix}, +}; use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; pub(crate) type MetadataFeatureLength = BoundedU16<{ *MetadataFeature::LENGTH_RANGE.start() }, { *MetadataFeature::LENGTH_RANGE.end() }>; +pub(crate) type MetadataFeatureKeyLength = BoundedU8<1, { u8::MAX }>; +pub(crate) type MetadataFeatureValueLength = BoundedU16<0, { u16::MAX }>; + /// Defines metadata, arbitrary binary data, that will be stored in the output. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = |err| Error::InvalidMetadataFeatureLength(err.into_prefix_err().into()))] +#[packable(unpack_error = Error, with = |err| Error::InvalidMetadataFeature(err.to_string()))] pub struct MetadataFeature( // Binary data. - pub(crate) BoxedSlicePrefix, + #[packable(verify_with = verify_keys_packable)] + pub(crate) BTreeMapPrefix< + BoxedSlicePrefix, + BoxedSlicePrefix, + MetadataFeatureLength, + >, ); +fn verify_keys_packable( + map: &BTreeMapPrefix< + BoxedSlicePrefix, + BoxedSlicePrefix, + MetadataFeatureLength, + >, +) -> Result<(), Error> { + if VERIFY { + for key in map.keys() { + if !key.is_ascii() { + return Err(Error::NonAsciiMetadataKey(key.to_vec())); + } + } + } + Ok(()) +} + impl MetadataFeature { /// The [`Feature`](crate::types::block::output::Feature) kind of [`MetadataFeature`]. pub const KIND: u8 = 2; @@ -27,14 +55,22 @@ impl MetadataFeature { /// Creates a new [`MetadataFeature`]. #[inline(always)] - pub fn new(data: impl Into>) -> Result { - Self::try_from(data.into()) + pub fn new(data: BTreeMap, Vec>) -> Result { + for key in data.keys() { + if !key.is_ascii() { + return Err(Error::NonAsciiMetadataKey(key.to_vec())); + } + } + Self::try_from(data) } /// Returns the data. #[inline(always)] - pub fn data(&self) -> &[u8] { - &self.0 + pub fn data( + &self, + ) -> &BTreeMap, BoxedSlicePrefix> + { + self.0.deref() } } @@ -42,62 +78,53 @@ impl StorageScore for MetadataFeature {} impl WorkScore for MetadataFeature {} -macro_rules! impl_from_vec { - ($type:ty) => { - impl TryFrom<$type> for MetadataFeature { - type Error = Error; +impl TryFrom, Vec>> for MetadataFeature { + type Error = Error; - fn try_from(value: $type) -> Result { - Vec::::from(value).try_into() + fn try_from(data: BTreeMap, Vec>) -> Result { + let mut res = BTreeMap::< + BoxedSlicePrefix, + BoxedSlicePrefix, + >::new(); + for (k, v) in data { + if !k.is_ascii() { + return Err(Error::NonAsciiMetadataKey(k.to_vec())); } + res.insert( + k.into_boxed_slice() + .try_into() + .map_err(Error::InvalidMetadataFeatureKeyLength)?, + v.into_boxed_slice() + .try_into() + .map_err(Error::InvalidMetadataFeatureValueLength)?, + ); } - }; -} -impl_from_vec!(&str); -impl_from_vec!(String); -impl_from_vec!(&[u8]); - -impl TryFrom<[u8; N]> for MetadataFeature { - type Error = Error; - - fn try_from(value: [u8; N]) -> Result { - value.to_vec().try_into() - } -} - -impl TryFrom> for MetadataFeature { - type Error = Error; - - fn try_from(data: Vec) -> Result { - data.into_boxed_slice().try_into() - } -} - -impl TryFrom> for MetadataFeature { - type Error = Error; - - fn try_from(data: Box<[u8]>) -> Result { - data.try_into().map(Self).map_err(Error::InvalidMetadataFeatureLength) - } -} - -impl FromStr for MetadataFeature { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::new(prefix_hex::decode::>(s).map_err(Error::Hex)?) + Ok(Self(res.try_into().map_err(Error::InvalidMetadataFeatureLength)?)) } } impl core::fmt::Display for MetadataFeature { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", prefix_hex::encode(self.data())) + write!( + f, + "{:?}", + self.0 + .keys() + // Safe to unwrap, keys must be ascii + .map(|k| alloc::str::from_utf8(k).unwrap()) + .collect::>() + ) } } impl core::fmt::Debug for MetadataFeature { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "MetadataFeature({self})") + let dbg_map: BTreeMap<&str, String> = self + .0 + .iter() + .map(|(k, v)| (alloc::str::from_utf8(k).unwrap(), prefix_hex::encode(v.as_ref()))) + .collect(); + write!(f, "MetadataFeature({dbg_map:?})") } } @@ -434,35 +461,74 @@ pub(crate) mod irc_30 { #[cfg(feature = "serde")] pub(crate) mod dto { - use alloc::borrow::Cow; + use alloc::collections::BTreeMap; - use serde::{Deserialize, Serialize}; + use serde::{de, Deserialize, Deserializer, Serialize}; + use serde_json::Value; use super::*; - use crate::utils::serde::cow_boxed_slice_prefix_hex_bytes; - #[derive(Serialize, Deserialize)] - struct MetadataFeatureDto<'a> { + #[derive(Serialize)] + struct MetadataFeatureDto { #[serde(rename = "type")] kind: u8, - #[serde(with = "cow_boxed_slice_prefix_hex_bytes")] - data: Cow<'a, BoxedSlicePrefix>, + entries: BTreeMap, } - impl<'a> From<&'a MetadataFeature> for MetadataFeatureDto<'a> { - fn from(value: &'a MetadataFeature) -> Self { + impl<'de> Deserialize<'de> for MetadataFeature { + fn deserialize>(d: D) -> Result { + let value = Value::deserialize(d)?; + Ok( + match value + .get("type") + .and_then(Value::as_u64) + .ok_or_else(|| de::Error::custom("invalid metadata type"))? as u8 + { + Self::KIND => { + let map: BTreeMap = serde_json::from_value( + value + .get("entries") + .ok_or_else(|| de::Error::custom("missing metadata entries"))? + .clone(), + ) + .map_err(|e| de::Error::custom(format!("cannot deserialize metadata feature: {e}")))?; + + Self::try_from( + map.into_iter() + .map(|(key, value)| Ok((key.into_bytes(), prefix_hex::decode::>(value)?))) + .collect::, Vec>, prefix_hex::Error>>() + .map_err(de::Error::custom)?, + ) + .map_err(de::Error::custom)? + } + _ => return Err(de::Error::custom("invalid metadata feature")), + }, + ) + } + } + + impl From<&MetadataFeature> for MetadataFeatureDto { + fn from(value: &MetadataFeature) -> Self { + let mut entries = BTreeMap::new(); + for (k, v) in value.0.iter() { + entries.insert( + // Safe to unwrap, keys must be ascii + alloc::str::from_utf8(k.as_ref()).expect("invalid ascii").to_string(), + prefix_hex::encode(v.as_ref()), + ); + } Self { kind: MetadataFeature::KIND, - data: Cow::Borrowed(&value.0), + entries, } } } - - impl<'a> From> for MetadataFeature { - fn from(value: MetadataFeatureDto<'a>) -> Self { - Self(value.data.into_owned()) + impl Serialize for MetadataFeature { + fn serialize(&self, s: S) -> Result + where + S: serde::Serializer, + { + MetadataFeatureDto::from(self).serialize(s) } } - - crate::impl_serde_typed_dto!(MetadataFeature, MetadataFeatureDto<'_>, "metadata feature"); } diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index eed47ced38..d07c2f4618 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -20,7 +20,11 @@ use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; pub use self::metadata::irc_27::{Attribute, Irc27Metadata}; #[cfg(feature = "irc_30")] pub use self::metadata::irc_30::Irc30Metadata; -pub(crate) use self::{block_issuer::BlockIssuerKeyCount, metadata::MetadataFeatureLength, tag::TagFeatureLength}; +pub(crate) use self::{ + block_issuer::BlockIssuerKeyCount, + metadata::{MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength}, + tag::TagFeatureLength, +}; pub use self::{ block_issuer::{BlockIssuerFeature, BlockIssuerKey, BlockIssuerKeys, Ed25519BlockIssuerKey}, issuer::IssuerFeature, diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index e2ec4241a5..484be1ae99 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -47,7 +47,7 @@ pub use self::{ unlock_condition::{UnlockCondition, UnlockConditions}, }; pub(crate) use self::{ - feature::{MetadataFeatureLength, TagFeatureLength}, + feature::{MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength, TagFeatureLength}, native_token::NativeTokenCount, output_id::OutputIndex, unlock_condition::AddressUnlockCondition, diff --git a/sdk/src/types/block/rand/output/feature.rs b/sdk/src/types/block/rand/output/feature.rs index 57a8fdd609..e5c6615123 100644 --- a/sdk/src/types/block/rand/output/feature.rs +++ b/sdk/src/types/block/rand/output/feature.rs @@ -1,7 +1,13 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{collections::BTreeSet, vec::Vec}; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; +use core::ops::Range; + +use rand::distributions::{Alphanumeric, DistString}; use crate::types::block::{ output::feature::{ @@ -29,8 +35,38 @@ pub fn rand_issuer_feature() -> IssuerFeature { /// Generates a random [`MetadataFeature`]. pub fn rand_metadata_feature() -> MetadataFeature { - let bytes = rand_bytes(rand_number_range(MetadataFeature::LENGTH_RANGE) as usize); - MetadataFeature::new(bytes).unwrap() + let mut map = BTreeMap::new(); + let mut total_size = 0; + for _ in 0..10 { + if total_size >= *MetadataFeature::LENGTH_RANGE.end() as usize - u8::MAX as usize { + break; + } + // Key length + total_size += 1; + let key = Alphanumeric.sample_string( + &mut rand::thread_rng(), + rand_number_range(Range { + start: 1, + end: u8::MAX.into(), + }), + ); + total_size += key.as_bytes().len(); + + if total_size >= *MetadataFeature::LENGTH_RANGE.end() as usize - 2 { + break; + } + // Value length + total_size += 2; + let bytes = rand_bytes(rand_number_range(Range { + start: 0, + end: *MetadataFeature::LENGTH_RANGE.end() as usize - total_size, + }) as usize); + total_size += bytes.len(); + + map.insert(key.into(), bytes); + } + + MetadataFeature::new(map).unwrap() } /// Generates a random [`TagFeature`]. diff --git a/sdk/src/types/fuzz/Cargo.toml b/sdk/src/types/fuzz/Cargo.toml index be3e427c35..c4f9eee680 100644 --- a/sdk/src/types/fuzz/Cargo.toml +++ b/sdk/src/types/fuzz/Cargo.toml @@ -12,7 +12,7 @@ cargo-fuzz = true iota-types = { path = "..", default-features = false } libfuzzer-sys = { version = "0.4.7", default-features = false } -packable = { version = "0.10.0", default-features = false } +packable = { git = "https://github.com/iotaledger/common-rs", rev = "c72287fa24254b349f6e935b847485b0be141557", default-features = false } # Prevent this from interfering with workspaces [workspace] diff --git a/sdk/src/wallet/operations/balance.rs b/sdk/src/wallet/operations/balance.rs index 8723878780..08e1b56eb0 100644 --- a/sdk/src/wallet/operations/balance.rs +++ b/sdk/src/wallet/operations/balance.rs @@ -299,17 +299,19 @@ where } }); - let metadata = wallet_data - .native_token_foundries - .get(&FoundryId::from(*native_token.token_id())) - .and_then(|foundry| foundry.immutable_features().metadata()) - .cloned(); + // TODO: enable again when MetadataFeature is cleared up + // let metadata = wallet_data + // .native_token_foundries + // .get(&FoundryId::from(*native_token.token_id())) + // .and_then(|foundry| foundry.immutable_features().metadata()) + // .cloned(); balance.native_tokens.push(NativeTokensBalance { token_id: *native_token.token_id(), total: native_token.amount(), available: native_token.amount() - *locked_native_token_amount.unwrap_or(&U256::from(0u8)), - metadata, + // TODO: enable again when MetadataFeature is cleared up + // metadata, }) } diff --git a/sdk/src/wallet/operations/output_consolidation.rs b/sdk/src/wallet/operations/output_consolidation.rs index 7fae58242d..0046592c8f 100644 --- a/sdk/src/wallet/operations/output_consolidation.rs +++ b/sdk/src/wallet/operations/output_consolidation.rs @@ -130,8 +130,8 @@ where /// Prepares the transaction for [Wallet::consolidate_outputs()]. pub async fn prepare_consolidate_outputs(&self, params: ConsolidationParams) -> Result { log::debug!("[OUTPUT_CONSOLIDATION] prepare consolidating outputs if needed"); - #[cfg(feature = "participation")] - let voting_output = self.get_voting_output().await?; + // #[cfg(feature = "participation")] + // let voting_output = self.get_voting_output().await?; let slot_index = self.client().get_slot_index().await?; let mut outputs_to_consolidate = Vec::new(); let wallet_data = self.data().await; @@ -139,13 +139,13 @@ where let wallet_address = wallet_data.address.clone(); for (output_id, output_data) in &wallet_data.unspent_outputs { - #[cfg(feature = "participation")] - if let Some(ref voting_output) = voting_output { - // Remove voting output from inputs, because we want to keep its features and not consolidate it. - if output_data.output_id == voting_output.output_id { - continue; - } - } + // #[cfg(feature = "participation")] + // if let Some(ref voting_output) = voting_output { + // // Remove voting output from inputs, because we want to keep its features and not consolidate it. + // if output_data.output_id == voting_output.output_id { + // continue; + // } + // } let is_locked_output = wallet_data.locked_outputs.contains(output_id); let should_consolidate_output = self diff --git a/sdk/src/wallet/operations/participation/mod.rs b/sdk/src/wallet/operations/participation/mod.rs index 481881c214..152e6555e9 100644 --- a/sdk/src/wallet/operations/participation/mod.rs +++ b/sdk/src/wallet/operations/participation/mod.rs @@ -8,24 +8,24 @@ // address. // If the user has designated funds to vote with, the resulting output MUST NOT be used for input selection. -pub(crate) mod event; -pub(crate) mod voting; -pub(crate) mod voting_power; +// pub(crate) mod event; +// pub(crate) mod voting; +// pub(crate) mod voting_power; -use std::collections::{hash_map::Entry, HashMap, HashSet}; +use std::collections::HashMap; use serde::{Deserialize, Serialize}; use crate::{ - client::{node_manager::node::Node, secret::SecretManage, Client}, + client::{node_manager::node::Node, secret::SecretManage}, types::{ api::plugins::participation::{ responses::TrackedParticipation, - types::{ParticipationEventData, ParticipationEventId, Participations, PARTICIPATION_TAG}, + types::{ParticipationEventData, ParticipationEventId, PARTICIPATION_TAG}, }, block::output::{unlock_condition::UnlockCondition, Output, OutputId}, }, - wallet::{core::WalletData, task, types::OutputData, Result, Wallet}, + wallet::{core::WalletData, types::OutputData, Result, Wallet}, }; /// An object containing an account's entire participation overview. @@ -51,174 +51,175 @@ where crate::wallet::Error: From, crate::client::Error: From, { - /// Calculates the voting overview of a wallet. If event_ids are provided, only return outputs and tracked - /// participations for them. - pub async fn get_participation_overview( - &self, - event_ids: Option>, - ) -> Result { - log::debug!("[get_participation_overview]"); - // TODO: Could use the address endpoint in the future when https://github.com/iotaledger/inx-participation/issues/50 is done. + // /// Calculates the voting overview of a wallet. If event_ids are provided, only return outputs and tracked + // /// participations for them. + // pub async fn get_participation_overview( + // &self, + // event_ids: Option>, + // ) -> Result { + // log::debug!("[get_participation_overview]"); + // // TODO: Could use the address endpoint in the future when https://github.com/iotaledger/inx-participation/issues/50 is done. - let mut spent_cached_outputs = self - .storage_manager - .read() - .await - .get_cached_participation_output_status() - .await?; - let restored_spent_cached_outputs_len = spent_cached_outputs.len(); - log::debug!( - "[get_participation_overview] restored_spent_cached_outputs_len: {}", - restored_spent_cached_outputs_len - ); - let wallet_data = self.data().await; - let participation_outputs = wallet_data.outputs().values().filter(|output_data| { - is_valid_participation_output(&output_data.output) - // Check that the metadata exists, because otherwise we aren't participating for anything - && output_data.output.features().and_then(|f| f.metadata()).is_some() - // Don't add spent cached outputs, we have their data already and it can't change anymore - && !spent_cached_outputs.contains_key(&output_data.output_id) - }); + // let mut spent_cached_outputs = self + // .storage_manager + // .read() + // .await + // .get_cached_participation_output_status() + // .await?; + // let restored_spent_cached_outputs_len = spent_cached_outputs.len(); + // log::debug!( + // "[get_participation_overview] restored_spent_cached_outputs_len: {}", + // restored_spent_cached_outputs_len + // ); + // let wallet_data = self.data().await; + // let participation_outputs = wallet_data.outputs().values().filter(|output_data| { + // is_valid_participation_output(&output_data.output) + // // Check that the metadata exists, because otherwise we aren't participating for anything + // && output_data.output.features().and_then(|f| f.metadata()).is_some() + // // Don't add spent cached outputs, we have their data already and it can't change anymore + // && !spent_cached_outputs.contains_key(&output_data.output_id) + // }); - let mut events = HashMap::new(); - let mut spent_outputs = HashSet::new(); - for output_data in participation_outputs { - // PANIC: the filter already checks that the metadata exists. - let metadata = output_data.output.features().and_then(|f| f.metadata()).unwrap(); - if let Ok(participations) = Participations::from_bytes(&mut metadata.data()) { - for participation in participations.participations { - // Skip events that aren't in `event_ids` if not None - if let Some(event_ids) = event_ids.as_ref() { - if !event_ids.contains(&participation.event_id) { - continue; - } - } - match events.entry(participation.event_id) { - Entry::Vacant(entry) => { - entry.insert(vec![output_data.output_id]); - } - Entry::Occupied(mut entry) => { - entry.get_mut().push(output_data.output_id); - } - } - if output_data.is_spent { - spent_outputs.insert(output_data.output_id); - } - } - }; - } + // let mut events = HashMap::new(); + // let mut spent_outputs = HashSet::new(); + // for output_data in participation_outputs { + // // PANIC: the filter already checks that the metadata exists. + // let metadata = output_data.output.features().and_then(|f| f.metadata()).unwrap(); + // if let Ok(participations) = Participations::from_bytes(&mut metadata.data()) { + // for participation in participations.participations { + // // Skip events that aren't in `event_ids` if not None + // if let Some(event_ids) = event_ids.as_ref() { + // if !event_ids.contains(&participation.event_id) { + // continue; + // } + // } + // match events.entry(participation.event_id) { + // Entry::Vacant(entry) => { + // entry.insert(vec![output_data.output_id]); + // } + // Entry::Occupied(mut entry) => { + // entry.get_mut().push(output_data.output_id); + // } + // } + // if output_data.is_spent { + // spent_outputs.insert(output_data.output_id); + // } + // } + // }; + // } - let mut participations: HashMap> = HashMap::new(); + // let mut participations: HashMap> = + // HashMap::new(); - // Add cached data - for (output_id, output_status_response) in &spent_cached_outputs { - for (event_id, participation) in &output_status_response.participations { - // Skip events that aren't in `event_ids` if not None - if let Some(event_ids) = event_ids.as_ref() { - if !event_ids.contains(event_id) { - continue; - } - } - match participations.entry(*event_id) { - Entry::Vacant(entry) => { - entry.insert(HashMap::from([(*output_id, participation.clone())])); - } - Entry::Occupied(mut entry) => { - entry.get_mut().insert(*output_id, participation.clone()); - } - } - } - } + // // Add cached data + // for (output_id, output_status_response) in &spent_cached_outputs { + // for (event_id, participation) in &output_status_response.participations { + // // Skip events that aren't in `event_ids` if not None + // if let Some(event_ids) = event_ids.as_ref() { + // if !event_ids.contains(event_id) { + // continue; + // } + // } + // match participations.entry(*event_id) { + // Entry::Vacant(entry) => { + // entry.insert(HashMap::from([(*output_id, participation.clone())])); + // } + // Entry::Occupied(mut entry) => { + // entry.get_mut().insert(*output_id, participation.clone()); + // } + // } + // } + // } - for (event_id, mut output_ids) in events { - log::debug!( - "[get_participation_overview] requesting {} outputs for event {event_id}", - output_ids.len() - ); - let event_client = self.get_client_for_event(&event_id).await?; + // for (event_id, mut output_ids) in events { + // log::debug!( + // "[get_participation_overview] requesting {} outputs for event {event_id}", + // output_ids.len() + // ); + // let event_client = self.get_client_for_event(&event_id).await?; - output_ids.retain(|output_id| { - // Skip if participations already contains this output id with participation for this event - if let Some(p) = participations.get(&event_id) { - if p.contains_key(output_id) { - log::debug!( - "[get_participation_overview] skip requesting already known {output_id} for event {event_id}", - ); - return false; - } - } - true - }); + // output_ids.retain(|output_id| { + // // Skip if participations already contains this output id with participation for this event + // if let Some(p) = participations.get(&event_id) { + // if p.contains_key(output_id) { + // log::debug!( + // "[get_participation_overview] skip requesting already known {output_id} for event + // {event_id}", ); + // return false; + // } + // } + // true + // }); - let results = futures::future::try_join_all(output_ids.chunks(100).map(ToOwned::to_owned).map(|chunk| { - let event_client = event_client.clone(); - task::spawn(async move { - futures::future::join_all(chunk.iter().map(|output_id| async { - let output_id = *output_id; - (event_client.output_status(&output_id).await, output_id) - })) - .await - }) - })) - .await?; + // let results = futures::future::try_join_all(output_ids.chunks(100).map(ToOwned::to_owned).map(|chunk| { + // let event_client = event_client.clone(); + // task::spawn(async move { + // futures::future::join_all(chunk.iter().map(|output_id| async { + // let output_id = *output_id; + // (event_client.output_status(&output_id).await, output_id) + // })) + // .await + // }) + // })) + // .await?; - for (result, output_id) in results.into_iter().flatten() { - match result { - Ok(status) => { - // Cache data for spent outputs - if spent_outputs.contains(&output_id) { - match spent_cached_outputs.entry(output_id) { - Entry::Vacant(entry) => { - entry.insert(status.clone()); - } - Entry::Occupied(mut entry) => { - let output_status_response = entry.get_mut(); - for (event_id, participation) in &status.participations { - output_status_response - .participations - .insert(*event_id, participation.clone()); - } - } - } - } - for (event_id, participation) in status.participations { - // Skip events that aren't in `event_ids` if not None - if let Some(event_ids) = event_ids.as_ref() { - if !event_ids.contains(&event_id) { - continue; - } - } - match participations.entry(event_id) { - Entry::Vacant(entry) => { - entry.insert(HashMap::from([(output_id, participation)])); - } - Entry::Occupied(mut entry) => { - entry.get_mut().insert(output_id, participation); - } - } - } - } - Err(crate::client::Error::Node(crate::client::node_api::error::Error::NotFound(_))) => {} - Err(e) => return Err(crate::wallet::Error::Client(e.into())), - } - } - } + // for (result, output_id) in results.into_iter().flatten() { + // match result { + // Ok(status) => { + // // Cache data for spent outputs + // if spent_outputs.contains(&output_id) { + // match spent_cached_outputs.entry(output_id) { + // Entry::Vacant(entry) => { + // entry.insert(status.clone()); + // } + // Entry::Occupied(mut entry) => { + // let output_status_response = entry.get_mut(); + // for (event_id, participation) in &status.participations { + // output_status_response + // .participations + // .insert(*event_id, participation.clone()); + // } + // } + // } + // } + // for (event_id, participation) in status.participations { + // // Skip events that aren't in `event_ids` if not None + // if let Some(event_ids) = event_ids.as_ref() { + // if !event_ids.contains(&event_id) { + // continue; + // } + // } + // match participations.entry(event_id) { + // Entry::Vacant(entry) => { + // entry.insert(HashMap::from([(output_id, participation)])); + // } + // Entry::Occupied(mut entry) => { + // entry.get_mut().insert(output_id, participation); + // } + // } + // } + // } + // Err(crate::client::Error::Node(crate::client::node_api::error::Error::NotFound(_))) => {} + // Err(e) => return Err(crate::wallet::Error::Client(e.into())), + // } + // } + // } - log::debug!( - "[get_participation_overview] new spent_cached_outputs: {}", - spent_cached_outputs.len() - ); - // Only store updated data if new outputs got added - if spent_cached_outputs.len() > restored_spent_cached_outputs_len { - self.storage_manager - .read() - .await - .set_cached_participation_output_status(&spent_cached_outputs) - .await?; - } + // log::debug!( + // "[get_participation_overview] new spent_cached_outputs: {}", + // spent_cached_outputs.len() + // ); + // // Only store updated data if new outputs got added + // if spent_cached_outputs.len() > restored_spent_cached_outputs_len { + // self.storage_manager + // .read() + // .await + // .set_cached_participation_output_status(&spent_cached_outputs) + // .await?; + // } - Ok(ParticipationOverview { participations }) - } + // Ok(ParticipationOverview { participations }) + // } /// Returns the voting output ("PARTICIPATION" tag). /// @@ -227,54 +228,54 @@ where self.data().await.get_voting_output() } - /// Gets client for an event. - /// If event isn't found, the client from the account will be returned. - pub(crate) async fn get_client_for_event(&self, id: &ParticipationEventId) -> crate::wallet::Result { - log::debug!("[get_client_for_event]"); - let events = self.storage_manager.read().await.get_participation_events().await?; + // /// Gets client for an event. + // /// If event isn't found, the client from the account will be returned. + // pub(crate) async fn get_client_for_event(&self, id: &ParticipationEventId) -> crate::wallet::Result { + // log::debug!("[get_client_for_event]"); + // let events = self.storage_manager.read().await.get_participation_events().await?; - let event_with_nodes = match events.get(id) { - Some(event_with_nodes) => event_with_nodes, - None => return Ok(self.client().clone()), - }; + // let event_with_nodes = match events.get(id) { + // Some(event_with_nodes) => event_with_nodes, + // None => return Ok(self.client().clone()), + // }; - let mut client_builder = Client::builder().with_ignore_node_health(); - for node in &event_with_nodes.nodes { - client_builder = client_builder.with_node_auth(node.url.as_str(), node.auth.clone())?; - } + // let mut client_builder = Client::builder().with_ignore_node_health(); + // for node in &event_with_nodes.nodes { + // client_builder = client_builder.with_node_auth(node.url.as_str(), node.auth.clone())?; + // } - Ok(client_builder.finish().await?) - } + // Ok(client_builder.finish().await?) + // } - /// Checks if events in the participations ended and removes them. - pub(crate) async fn remove_ended_participation_events( - &self, - participations: &mut Participations, - ) -> crate::wallet::Result<()> { - log::debug!("[remove_ended_participation_events]"); - // TODO change to one of the new timestamps, which ones ? - let latest_milestone_index = 0; - // let latest_milestone_index = self.client().get_info().await?.node_info.status.latest_milestone.index; + // /// Checks if events in the participations ended and removes them. + // pub(crate) async fn remove_ended_participation_events( + // &self, + // participations: &mut Participations, + // ) -> crate::wallet::Result<()> { + // log::debug!("[remove_ended_participation_events]"); + // // TODO change to one of the new timestamps, which ones ? + // let latest_milestone_index = 0; + // // let latest_milestone_index = self.client().get_info().await?.node_info.status.latest_milestone.index; - let events = self.storage_manager.read().await.get_participation_events().await?; + // let events = self.storage_manager.read().await.get_participation_events().await?; - for participation in participations.participations.clone().iter() { - if let Some(event_with_nodes) = events.get(&participation.event_id) { - if event_with_nodes.data.milestone_index_end() < &latest_milestone_index { - participations.remove(&participation.event_id); - } - } else { - // If not found in local events, try to get the event status from the client. - if let Ok(event_status) = self.get_participation_event_status(&participation.event_id).await { - if event_status.status() == "ended" { - participations.remove(&participation.event_id); - } - } - } - } + // for participation in participations.participations.clone().iter() { + // if let Some(event_with_nodes) = events.get(&participation.event_id) { + // if event_with_nodes.data.milestone_index_end() < &latest_milestone_index { + // participations.remove(&participation.event_id); + // } + // } else { + // // If not found in local events, try to get the event status from the client. + // if let Ok(event_status) = self.get_participation_event_status(&participation.event_id).await { + // if event_status.status() == "ended" { + // participations.remove(&participation.event_id); + // } + // } + // } + // } - Ok(()) - } + // Ok(()) + // } } impl WalletData { diff --git a/sdk/src/wallet/operations/transaction/high_level/create_account.rs b/sdk/src/wallet/operations/transaction/high_level/create_account.rs index 9ae930a68f..fef642d842 100644 --- a/sdk/src/wallet/operations/transaction/high_level/create_account.rs +++ b/sdk/src/wallet/operations/transaction/high_level/create_account.rs @@ -83,24 +83,25 @@ where None => self.address().await.inner().clone(), }; - let mut account_output_builder = + let account_output_builder = AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .with_foundry_counter(0) .add_unlock_condition(AddressUnlockCondition::new(address.clone())); - if let Some(CreateAccountParams { - immutable_metadata, - metadata, - .. - }) = params - { - if let Some(immutable_metadata) = immutable_metadata { - account_output_builder = - account_output_builder.add_immutable_feature(MetadataFeature::new(immutable_metadata)?); - } - if let Some(metadata) = metadata { - account_output_builder = account_output_builder.add_feature(MetadataFeature::new(metadata)?); - } - } + // TODO: enable again when MetadataFeature is cleared up + // if let Some(CreateAccountParams { + // immutable_metadata, + // metadata, + // .. + // }) = params + // { + // if let Some(immutable_metadata) = immutable_metadata { + // account_output_builder = + // account_output_builder.add_immutable_feature(MetadataFeature::new(immutable_metadata)?); + // } + // if let Some(metadata) = metadata { + // account_output_builder = account_output_builder.add_feature(MetadataFeature::new(metadata)?); + // } + // } let outputs = [account_output_builder.finish_output()?]; diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs index e74dbc10f4..2e9f0776b6 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs @@ -169,9 +169,11 @@ where account_id, ))); - if let Some(foundry_metadata) = params.foundry_metadata { - foundry_builder = foundry_builder.add_immutable_feature(MetadataFeature::new(foundry_metadata)?) - } + // TODO: enable again when MetadataFeature is cleared up + // if let Some(foundry_metadata) = params.foundry_metadata { + // foundry_builder = + // foundry_builder.add_immutable_feature(MetadataFeature::new(foundry_metadata)?) + // } foundry_builder.finish_output()? }, // Native Tokens will be added automatically in the remainder output in try_select_inputs() diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs b/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs index 613fd48d19..ae278f4884 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs @@ -191,9 +191,10 @@ where nft_builder = nft_builder.add_feature(SenderFeature::new(sender)); } - if let Some(metadata) = metadata { - nft_builder = nft_builder.add_feature(MetadataFeature::new(metadata)?); - } + // TODO: enable again when MetadataFeature is cleared up + // if let Some(metadata) = metadata { + // nft_builder = nft_builder.add_feature(MetadataFeature::new(metadata)?); + // } if let Some(tag) = tag { nft_builder = nft_builder.add_feature(TagFeature::new(tag)?); @@ -203,9 +204,10 @@ where nft_builder = nft_builder.add_immutable_feature(IssuerFeature::new(issuer)); } - if let Some(immutable_metadata) = immutable_metadata { - nft_builder = nft_builder.add_immutable_feature(MetadataFeature::new(immutable_metadata)?); - } + // TODO: enable again when MetadataFeature is cleared up + // if let Some(immutable_metadata) = immutable_metadata { + // nft_builder = nft_builder.add_immutable_feature(MetadataFeature::new(immutable_metadata)?); + // } outputs.push(nft_builder.finish_output()?); } diff --git a/sdk/src/wallet/operations/transaction/prepare_output.rs b/sdk/src/wallet/operations/transaction/prepare_output.rs index 70d832d510..02f255408e 100644 --- a/sdk/src/wallet/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/operations/transaction/prepare_output.rs @@ -63,11 +63,12 @@ where )?); } - if let Some(metadata) = features.metadata { - first_output_builder = first_output_builder.add_feature(MetadataFeature::new( - prefix_hex::decode::>(metadata).map_err(|_| Error::InvalidField("metadata"))?, - )?); - } + // TODO: enable again when MetadataFeature is cleared up + // if let Some(metadata) = features.metadata { + // first_output_builder = first_output_builder.add_feature(MetadataFeature::new( + // prefix_hex::decode::>(metadata).map_err(|_| Error::InvalidField("metadata"))?, + // )?); + // } if let Some(sender) = features.sender { first_output_builder = first_output_builder.add_feature(SenderFeature::new(sender)) diff --git a/sdk/src/wallet/storage/constants.rs b/sdk/src/wallet/storage/constants.rs index e50e1a3f15..3891fecf14 100644 --- a/sdk/src/wallet/storage/constants.rs +++ b/sdk/src/wallet/storage/constants.rs @@ -24,7 +24,7 @@ pub(crate) const WALLET_SYNC_OPTIONS: &str = "wallet-sync-options"; pub(crate) const SECRET_MANAGER_KEY: &str = "secret-manager"; -#[cfg(feature = "participation")] -pub(crate) const PARTICIPATION_EVENTS: &str = "participation-events"; -#[cfg(feature = "participation")] -pub(crate) const PARTICIPATION_CACHED_OUTPUTS: &str = "participation-cached-outputs"; +// #[cfg(feature = "participation")] +// pub(crate) const PARTICIPATION_EVENTS: &str = "participation-events"; +// #[cfg(feature = "participation")] +// pub(crate) const PARTICIPATION_CACHED_OUTPUTS: &str = "participation-cached-outputs"; diff --git a/sdk/src/wallet/storage/mod.rs b/sdk/src/wallet/storage/mod.rs index 63149817f2..d467f8bb2e 100644 --- a/sdk/src/wallet/storage/mod.rs +++ b/sdk/src/wallet/storage/mod.rs @@ -11,10 +11,10 @@ mod kind; mod manager; /// Storage options. mod options; -/// Storage functions related to participation. -#[cfg(feature = "participation")] -#[cfg_attr(docsrs, doc(cfg(feature = "participation")))] -mod participation; +// /// Storage functions related to participation. +// #[cfg(feature = "participation")] +// #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] +// mod participation; use async_trait::async_trait; use crypto::ciphers::chacha; diff --git a/sdk/src/wallet/types/balance.rs b/sdk/src/wallet/types/balance.rs index 6b068788f2..3fb2249fe6 100644 --- a/sdk/src/wallet/types/balance.rs +++ b/sdk/src/wallet/types/balance.rs @@ -129,10 +129,11 @@ pub struct NativeTokensBalance { /// Balance that can currently be spent #[getset(get_copy = "pub")] pub(crate) available: U256, - /// Token foundry immutable metadata - #[getset(get = "pub")] - #[serde(with = "crate::utils::serde::option_string")] - pub(crate) metadata: Option, + // TODO: enable again when MetadataFeature is cleared up + // /// Token foundry immutable metadata + // #[getset(get = "pub")] + // #[serde(with = "crate::utils::serde::option_string")] + // pub(crate) metadata: Option, } impl Default for NativeTokensBalance { @@ -141,7 +142,8 @@ impl Default for NativeTokensBalance { token_id: TokenId::null(), total: U256::from(0u8), available: U256::from(0u8), - metadata: None, + // TODO: enable again when MetadataFeature is cleared up + // metadata: None, } } } @@ -150,9 +152,10 @@ impl std::ops::AddAssign for NativeTokensBalance { fn add_assign(&mut self, rhs: Self) { self.total += rhs.total; self.available += rhs.available; - if self.metadata.is_none() { - self.metadata = rhs.metadata; - } + // TODO: enable again when MetadataFeature is cleared up + // if self.metadata.is_none() { + // self.metadata = rhs.metadata; + // } } } diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index 33ddf7c960..50306791e1 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/input_selection/nft_outputs.rs @@ -1210,67 +1210,68 @@ fn transitioned_zero_nft_id_no_longer_is_zero() { }); } -#[test] -fn changed_immutable_metadata() { - let protocol_parameters = protocol_parameters(); - let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); +// TODO: enable again when MetadataFeature is cleared up +// #[test] +// fn changed_immutable_metadata() { +// let protocol_parameters = protocol_parameters(); +// let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); - #[cfg(feature = "irc_27")] - let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( - "image/jpeg", - "https://mywebsite.com/my-nft-files-1.jpeg".parse().unwrap(), - "file 1", - ) - .with_issuer_name("Alice"); - #[cfg(not(feature = "irc_27"))] - let metadata = [1, 2, 3]; - - let nft_output = - NftOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters(), nft_id_1) - .with_immutable_features(MetadataFeature::try_from(metadata)) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .finish_output() - .unwrap(); - - let inputs = [InputSigningData { - output: nft_output.clone(), - output_metadata: rand_output_metadata(), - chain: None, - }]; - - #[cfg(feature = "irc_27")] - let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( - "image/jpeg", - "https://mywebsite.com/my-nft-files-2.jpeg".parse().unwrap(), - "file 2", - ) - .with_issuer_name("Alice"); - #[cfg(not(feature = "irc_27"))] - let metadata = [4, 5, 6]; +// #[cfg(feature = "irc_27")] +// let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( +// "image/jpeg", +// "https://mywebsite.com/my-nft-files-1.jpeg".parse().unwrap(), +// "file 1", +// ) +// .with_issuer_name("Alice"); +// #[cfg(not(feature = "irc_27"))] +// let metadata = [1, 2, 3]; + +// let nft_output = +// NftOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters(), nft_id_1) +// .with_immutable_features(MetadataFeature::try_from(metadata)) +// .add_unlock_condition(AddressUnlockCondition::new( +// Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), +// )) +// .finish_output() +// .unwrap(); + +// let inputs = [InputSigningData { +// output: nft_output.clone(), +// output_metadata: rand_output_metadata(), +// chain: None, +// }]; + +// #[cfg(feature = "irc_27")] +// let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( +// "image/jpeg", +// "https://mywebsite.com/my-nft-files-2.jpeg".parse().unwrap(), +// "file 2", +// ) +// .with_issuer_name("Alice"); +// #[cfg(not(feature = "irc_27"))] +// let metadata = [4, 5, 6]; - // New nft output with changed immutable metadata feature - let updated_nft_output = NftOutputBuilder::from(nft_output.as_nft()) - .with_minimum_amount(protocol_parameters.storage_score_parameters()) - .with_immutable_features(MetadataFeature::try_from(metadata)) - .finish_output() - .unwrap(); +// // New nft output with changed immutable metadata feature +// let updated_nft_output = NftOutputBuilder::from(nft_output.as_nft()) +// .with_minimum_amount(protocol_parameters.storage_score_parameters()) +// .with_immutable_features(MetadataFeature::try_from(metadata)) +// .finish_output() +// .unwrap(); - let outputs = [updated_nft_output]; +// let outputs = [updated_nft_output]; - let selected = InputSelection::new( - inputs, - outputs, - [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], - protocol_parameters, - ) - .select(); +// let selected = InputSelection::new( +// inputs, +// outputs, +// [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], +// protocol_parameters, +// ) +// .select(); - assert!(matches!( - selected, - Err(Error::UnfulfillableRequirement(Requirement::Nft( - nft_id, - ))) if nft_id == nft_id_1 - )); -} +// assert!(matches!( +// selected, +// Err(Error::UnfulfillableRequirement(Requirement::Nft( +// nft_id, +// ))) if nft_id == nft_id_1 +// )); +// } diff --git a/sdk/tests/wallet/balance.rs b/sdk/tests/wallet/balance.rs index 6cedf7c8a1..0907ec82da 100644 --- a/sdk/tests/wallet/balance.rs +++ b/sdk/tests/wallet/balance.rs @@ -259,46 +259,46 @@ async fn balance_transfer() -> Result<()> { Ok(()) } -#[ignore] -#[tokio::test] -#[cfg(feature = "participation")] -async fn balance_voting_power() -> Result<()> { - let storage_path = "test-storage/balance_voting_power"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - request_funds(&wallet).await?; - - let faucet_amount = 100_000_000_000; - - let balance = wallet.balance().await?; - assert_eq!(balance.base_coin().total(), faucet_amount); - assert_eq!(balance.base_coin().available(), faucet_amount); - - let voting_power = 1_000_000; - // Only use a part as voting power - let tx = wallet.increase_voting_power(voting_power).await?; - wallet - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - let balance = wallet.sync(None).await?; - assert_eq!(balance.base_coin().total(), faucet_amount); - assert_eq!(balance.base_coin().available(), faucet_amount - voting_power); - let wallet_voting_power = wallet.get_voting_power().await?; - assert_eq!(wallet_voting_power, voting_power); - - // Increase voting power to total amount - let tx = wallet.increase_voting_power(faucet_amount - voting_power).await?; - wallet - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - let balance = wallet.sync(None).await?; - assert_eq!(balance.base_coin().total(), faucet_amount); - assert_eq!(balance.base_coin().available(), 0); - let wallet_voting_power = wallet.get_voting_power().await?; - assert_eq!(wallet_voting_power, faucet_amount); - - tear_down(storage_path)?; - Ok(()) -} +// #[ignore] +// #[tokio::test] +// #[cfg(feature = "participation")] +// async fn balance_voting_power() -> Result<()> { +// let storage_path = "test-storage/balance_voting_power"; +// setup(storage_path)?; + +// let wallet = make_wallet(storage_path, None, None).await?; + +// request_funds(&wallet).await?; + +// let faucet_amount = 100_000_000_000; + +// let balance = wallet.balance().await?; +// assert_eq!(balance.base_coin().total(), faucet_amount); +// assert_eq!(balance.base_coin().available(), faucet_amount); + +// let voting_power = 1_000_000; +// // Only use a part as voting power +// let tx = wallet.increase_voting_power(voting_power).await?; +// wallet +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await?; +// let balance = wallet.sync(None).await?; +// assert_eq!(balance.base_coin().total(), faucet_amount); +// assert_eq!(balance.base_coin().available(), faucet_amount - voting_power); +// let wallet_voting_power = wallet.get_voting_power().await?; +// assert_eq!(wallet_voting_power, voting_power); + +// // Increase voting power to total amount +// let tx = wallet.increase_voting_power(faucet_amount - voting_power).await?; +// wallet +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await?; +// let balance = wallet.sync(None).await?; +// assert_eq!(balance.base_coin().total(), faucet_amount); +// assert_eq!(balance.base_coin().available(), 0); +// let wallet_voting_power = wallet.get_voting_power().await?; +// assert_eq!(wallet_voting_power, faucet_amount); + +// tear_down(storage_path)?; +// Ok(()) +// } diff --git a/sdk/tests/wallet/native_tokens.rs b/sdk/tests/wallet/native_tokens.rs index a392e8fdd4..449d1a85b3 100644 --- a/sdk/tests/wallet/native_tokens.rs +++ b/sdk/tests/wallet/native_tokens.rs @@ -110,19 +110,20 @@ async fn native_token_foundry_metadata() -> Result<()> { })) .await?; assert_eq!(balance.native_tokens().len(), 1); - // Metadata should exist and be the same - assert_eq!( - balance - .native_tokens() - .iter() - .find(|t| t.token_id() == &create_tx.token_id) - .unwrap() - .metadata() - .as_ref() - .unwrap() - .data(), - &foundry_metadata - ); + // TODO: enable again when MetadataFeature is cleared up + // // Metadata should exist and be the same + // assert_eq!( + // balance + // .native_tokens() + // .iter() + // .find(|t| t.token_id() == &create_tx.token_id) + // .unwrap() + // .metadata() + // .as_ref() + // .unwrap() + // .data(), + // &foundry_metadata + // ); tear_down(storage_path) } diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index f6bc818ffc..0b7bd9b824 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -589,11 +589,12 @@ async fn prepare_nft_output_features_update() -> Result<()> { assert_eq!(nft.address(), wallet.address().await.inner()); assert!(nft.features().sender().is_none()); assert!(nft.features().tag().is_none()); - assert_eq!(nft.features().metadata().unwrap().data(), &[42]); - assert_eq!( - nft.immutable_features().metadata().unwrap().data(), - b"some immutable nft metadata" - ); + // TODO: enable again when MetadataFeature is cleared up + // assert_eq!(nft.features().metadata().unwrap().data(), &[42]); + // assert_eq!( + // nft.immutable_features().metadata().unwrap().data(), + // b"some immutable nft metadata" + // ); assert_eq!( nft.immutable_features().issuer().unwrap().address(), wallet.address().await.inner() @@ -865,10 +866,11 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { assert_eq!(nft.amount(), 52300); assert_eq!(nft.address(), wallet.address().await.inner()); assert!(nft.features().is_empty()); - assert_eq!( - nft.immutable_features().metadata().unwrap().data(), - b"some immutable nft metadata" - ); + // TODO: enable again when MetadataFeature is cleared up + // assert_eq!( + // nft.immutable_features().metadata().unwrap().data(), + // b"some immutable nft metadata" + // ); assert_eq!( nft.immutable_features().issuer().unwrap().address(), wallet.address().await.inner() From 197da4954384ceee2295eefd98b9bf5cd9d88fca Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 19 Dec 2023 13:12:23 +0100 Subject: [PATCH 02/51] Fix no_std --- sdk/src/types/block/error.rs | 5 ++++- sdk/src/types/block/output/feature/metadata.rs | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 4a64923e73..148127e932 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -1,7 +1,10 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::string::{FromUtf8Error, String}; +use alloc::{ + string::{FromUtf8Error, String}, + vec::Vec, +}; use core::{convert::Infallible, fmt}; use bech32::primitives::hrp::Error as Bech32HrpError; diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 058de7af80..e068ea440c 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -1,7 +1,11 @@ // Copyright 2021-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{collections::BTreeMap, string::String, vec::Vec}; +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + vec::Vec, +}; use core::ops::{Deref, RangeInclusive}; use packable::{ @@ -239,6 +243,7 @@ pub(crate) mod irc_27 { impl TryFrom for MetadataFeature { type Error = Error; + fn try_from(value: Irc27Metadata) -> Result { Self::new(value.to_bytes()) } @@ -413,6 +418,7 @@ pub(crate) mod irc_30 { impl TryFrom for MetadataFeature { type Error = Error; + fn try_from(value: Irc30Metadata) -> Result { Self::new(value.to_bytes()) } @@ -461,7 +467,7 @@ pub(crate) mod irc_30 { #[cfg(feature = "serde")] pub(crate) mod dto { - use alloc::collections::BTreeMap; + use alloc::{collections::BTreeMap, format}; use serde::{de, Deserialize, Deserializer, Serialize}; use serde_json::Value; From 71c227e12364e6018848a4b51374fc83bdc20906 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 19 Dec 2023 13:27:15 +0100 Subject: [PATCH 03/51] Fix IRC27/30 --- sdk/src/types/block/output/feature/metadata.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index e068ea440c..282e1f411a 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -59,7 +59,9 @@ impl MetadataFeature { /// Creates a new [`MetadataFeature`]. #[inline(always)] - pub fn new(data: BTreeMap, Vec>) -> Result { + pub fn new(data: impl Into, Vec>>) -> Result { + let data = data.into(); + for key in data.keys() { if !key.is_ascii() { return Err(Error::NonAsciiMetadataKey(key.to_vec())); @@ -236,7 +238,7 @@ pub(crate) mod irc_27 { } pub fn to_bytes(&self) -> Vec { - // Unwrap: Safe because this struct is known to be valid + // Unwrap: safe because this struct is known to be valid. serde_json::to_string(self).unwrap().into_bytes() } } @@ -245,7 +247,7 @@ pub(crate) mod irc_27 { type Error = Error; fn try_from(value: Irc27Metadata) -> Result { - Self::new(value.to_bytes()) + Self::new([("irc-27".as_bytes().to_vec(), value.to_bytes())]) } } @@ -411,7 +413,7 @@ pub(crate) mod irc_30 { } pub fn to_bytes(&self) -> Vec { - // Unwrap: Safe because this struct is known to be valid + // Unwrap: safe because this struct is known to be valid. serde_json::to_string(self).unwrap().into_bytes() } } @@ -420,7 +422,7 @@ pub(crate) mod irc_30 { type Error = Error; fn try_from(value: Irc30Metadata) -> Result { - Self::new(value.to_bytes()) + Self::new([("irc-30".as_bytes().to_vec(), value.to_bytes())]) } } From ace6f17fa61618d80e0f6e7c74fa232b64930caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 19 Dec 2023 13:34:32 +0100 Subject: [PATCH 04/51] Address some TODOs --- cli/src/wallet_cli/mod.rs | 20 ++++-- .../client/output/build_account_output.rs | 12 ++-- .../client/output/build_basic_output.rs | 19 +++--- .../client/output/build_nft_output.rs | 12 ++-- sdk/examples/how_tos/outputs/features.rs | 27 ++++---- sdk/src/wallet/operations/balance.rs | 14 ++-- .../transaction/high_level/create_account.rs | 38 +++++------ .../high_level/minting/create_native_token.rs | 13 ++-- .../high_level/minting/mint_nfts.rs | 24 +++---- .../operations/transaction/prepare_output.rs | 11 ++-- sdk/src/wallet/types/balance.rs | 18 ++--- sdk/tests/wallet/native_tokens.rs | 30 +++++---- sdk/tests/wallet/output_preparation.rs | 66 ++++++++++++++----- 13 files changed, 176 insertions(+), 128 deletions(-) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index cc43bc38a5..6e3a39b330 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -13,8 +13,8 @@ use iota_sdk::{ types::block::{ address::{Bech32Address, ToBech32Ext}, output::{ - unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, FoundryId, NativeToken, - NativeTokensBuilder, NftId, Output, OutputId, TokenId, + feature::MetadataFeature, unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, + FoundryId, NativeToken, NativeTokensBuilder, NftId, Output, OutputId, TokenId, }, payload::signed_transaction::TransactionId, slot::SlotIndex, @@ -702,13 +702,23 @@ pub async fn mint_nft_command( None }; - let nft_options = MintNftParams::new() + let mut nft_options = MintNftParams::new() .with_address(address) - .with_immutable_metadata(immutable_metadata) - .with_metadata(metadata) .with_tag(tag) .with_sender(sender) .with_issuer(issuer); + if let Some(metadata) = metadata { + nft_options = nft_options.with_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + metadata.clone(), + metadata, + )]))?); + } + if let Some(immutable_metadata) = immutable_metadata { + nft_options = nft_options.with_immutable_metadata(MetadataFeature::new( + std::collections::BTreeMap::from_iter(vec![(immutable_metadata.clone(), immutable_metadata)]), + )?); + } + let transaction = wallet.mint_nfts([nft_options], None).await?; println_log_info!( diff --git a/sdk/examples/client/output/build_account_output.rs b/sdk/examples/client/output/build_account_output.rs index 82f84888ea..b3ecfc051c 100644 --- a/sdk/examples/client/output/build_account_output.rs +++ b/sdk/examples/client/output/build_account_output.rs @@ -49,11 +49,15 @@ async fn main() -> Result<()> { // Account id needs to be null the first time let account_output = AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .add_feature(SenderFeature::new(address.clone())) - // TODO: enable again when MetadataFeature is cleared up - // .add_feature(MetadataFeature::new(metadata)?) + .add_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + metadata.to_vec(), + metadata.to_vec(), + )]))?) .add_immutable_feature(IssuerFeature::new(address.clone())) - // TODO: enable again when MetadataFeature is cleared up - // .add_immutable_feature(MetadataFeature::new(metadata)?) + .add_immutable_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + metadata.to_vec(), + metadata.to_vec(), + )]))?) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output()?; diff --git a/sdk/examples/client/output/build_basic_output.rs b/sdk/examples/client/output/build_basic_output.rs index e5bc294d7d..a1e6dc5a00 100644 --- a/sdk/examples/client/output/build_basic_output.rs +++ b/sdk/examples/client/output/build_basic_output.rs @@ -23,7 +23,8 @@ use iota_sdk::{ }, }; -const METADATA: &str = "Hello, World!"; +const KEY: &'static [u8; 5] = b"Hello"; +const METADATA: &'static [u8; 6] = b"World!"; #[tokio::main] async fn main() -> Result<()> { @@ -41,12 +42,14 @@ async fn main() -> Result<()> { let outputs = [ // most simple output basic_output_builder.clone().finish_output()?, - // TODO: enable again when MetadataFeature is cleared up - // // with metadata feature block - // basic_output_builder - // .clone() - // .add_feature(MetadataFeature::new(METADATA)?) - // .finish_output()?, + // with metadata feature block + basic_output_builder + .clone() + .add_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + KEY.to_vec(), + METADATA.to_vec(), + )]))?) + .finish_output()?, // with storage deposit return basic_output_builder .clone() @@ -65,7 +68,7 @@ async fn main() -> Result<()> { // with tag feature basic_output_builder .clone() - .add_feature(TagFeature::new(METADATA)?) + .add_feature(TagFeature::new(KEY)?) .finish_output()?, // with sender feature basic_output_builder diff --git a/sdk/examples/client/output/build_nft_output.rs b/sdk/examples/client/output/build_nft_output.rs index 5328edd896..40cb5b31bb 100644 --- a/sdk/examples/client/output/build_nft_output.rs +++ b/sdk/examples/client/output/build_nft_output.rs @@ -63,12 +63,16 @@ async fn main() -> Result<()> { let nft_output = NftOutputBuilder::new_with_minimum_amount(storage_score_params, NftId::null()) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) .add_feature(SenderFeature::new(address.clone())) - // TODO: enable again when MetadataFeature is cleared up - // .add_feature(MetadataFeature::new(MUTABLE_METADATA)?) + .add_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + "mutable".as_bytes().to_vec(), + MUTABLE_METADATA.as_bytes().to_vec(), + )]))?) .add_feature(TagFeature::new(TAG)?) .add_immutable_feature(IssuerFeature::new(address)) - // TODO: enable again when MetadataFeature is cleared up - // .add_immutable_feature(MetadataFeature::new(tip_27_immutable_metadata)?) + .add_immutable_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + "IRC27".as_bytes().to_vec(), + tip_27_immutable_metadata.as_bytes().to_vec(), + )]))?) .finish_output()?; println!("{nft_output:#?}"); diff --git a/sdk/examples/how_tos/outputs/features.rs b/sdk/examples/how_tos/outputs/features.rs index 5001088824..ca331a56e9 100644 --- a/sdk/examples/how_tos/outputs/features.rs +++ b/sdk/examples/how_tos/outputs/features.rs @@ -53,17 +53,22 @@ async fn main() -> Result<()> { .clone() .add_immutable_feature(IssuerFeature::new(address)) .finish_output()?, - // TODO: enable again when MetadataFeature is cleared up - // // with metadata feature block - // nft_output_builder - // .clone() - // .add_feature(MetadataFeature::new("Hello, World!")?) - // .finish_output()?, - // // with immutable metadata feature block - // nft_output_builder - // .clone() - // .add_immutable_feature(MetadataFeature::new("Hello, World!")?) - // .finish_output()?, + // with metadata feature block + nft_output_builder + .clone() + .add_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + b"Hello".to_vec(), + b"World!".to_vec(), + )]))?) + .finish_output()?, + // with immutable metadata feature block + nft_output_builder + .clone() + .add_immutable_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + b"Hello".to_vec(), + b"World!".to_vec(), + )]))?) + .finish_output()?, // with tag feature nft_output_builder .add_feature(TagFeature::new("Hello, World!")?) diff --git a/sdk/src/wallet/operations/balance.rs b/sdk/src/wallet/operations/balance.rs index 08e1b56eb0..8723878780 100644 --- a/sdk/src/wallet/operations/balance.rs +++ b/sdk/src/wallet/operations/balance.rs @@ -299,19 +299,17 @@ where } }); - // TODO: enable again when MetadataFeature is cleared up - // let metadata = wallet_data - // .native_token_foundries - // .get(&FoundryId::from(*native_token.token_id())) - // .and_then(|foundry| foundry.immutable_features().metadata()) - // .cloned(); + let metadata = wallet_data + .native_token_foundries + .get(&FoundryId::from(*native_token.token_id())) + .and_then(|foundry| foundry.immutable_features().metadata()) + .cloned(); balance.native_tokens.push(NativeTokensBalance { token_id: *native_token.token_id(), total: native_token.amount(), available: native_token.amount() - *locked_native_token_amount.unwrap_or(&U256::from(0u8)), - // TODO: enable again when MetadataFeature is cleared up - // metadata, + metadata, }) } diff --git a/sdk/src/wallet/operations/transaction/high_level/create_account.rs b/sdk/src/wallet/operations/transaction/high_level/create_account.rs index fef642d842..8b8e1fd2bb 100644 --- a/sdk/src/wallet/operations/transaction/high_level/create_account.rs +++ b/sdk/src/wallet/operations/transaction/high_level/create_account.rs @@ -11,7 +11,6 @@ use crate::{ feature::MetadataFeature, unlock_condition::AddressUnlockCondition, AccountId, AccountOutputBuilder, Output, }, }, - utils::serde::option_prefix_hex_bytes, wallet::{ operations::transaction::TransactionOptions, types::{OutputData, TransactionWithMetadata}, @@ -27,11 +26,9 @@ pub struct CreateAccountParams { /// ed25519 wallet address pub address: Option, /// Immutable account metadata - #[serde(default, with = "option_prefix_hex_bytes")] - pub immutable_metadata: Option>, + pub immutable_metadata: Option, /// Account metadata - #[serde(default, with = "option_prefix_hex_bytes")] - pub metadata: Option>, + pub metadata: Option, } impl Wallet @@ -83,25 +80,24 @@ where None => self.address().await.inner().clone(), }; - let account_output_builder = + let mut account_output_builder = AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .with_foundry_counter(0) .add_unlock_condition(AddressUnlockCondition::new(address.clone())); - // TODO: enable again when MetadataFeature is cleared up - // if let Some(CreateAccountParams { - // immutable_metadata, - // metadata, - // .. - // }) = params - // { - // if let Some(immutable_metadata) = immutable_metadata { - // account_output_builder = - // account_output_builder.add_immutable_feature(MetadataFeature::new(immutable_metadata)?); - // } - // if let Some(metadata) = metadata { - // account_output_builder = account_output_builder.add_feature(MetadataFeature::new(metadata)?); - // } - // } + + if let Some(CreateAccountParams { + immutable_metadata, + metadata, + .. + }) = params + { + if let Some(immutable_metadata) = immutable_metadata { + account_output_builder = account_output_builder.add_immutable_feature(immutable_metadata); + } + if let Some(metadata) = metadata { + account_output_builder = account_output_builder.add_feature(metadata); + } + } let outputs = [account_output_builder.finish_output()?]; diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs index 2e9f0776b6..35d26339b3 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs @@ -1,6 +1,8 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use alloc::collections::BTreeMap; + use primitive_types::U256; use serde::{Deserialize, Serialize}; @@ -169,11 +171,12 @@ where account_id, ))); - // TODO: enable again when MetadataFeature is cleared up - // if let Some(foundry_metadata) = params.foundry_metadata { - // foundry_builder = - // foundry_builder.add_immutable_feature(MetadataFeature::new(foundry_metadata)?) - // } + if let Some(foundry_metadata) = params.foundry_metadata { + foundry_builder = foundry_builder.add_immutable_feature(MetadataFeature::new( + // TODO: what hardcoded key or let user provide the full metadata? + BTreeMap::from_iter(vec![(b"foundry".to_vec(), foundry_metadata)]), + )?); + } foundry_builder.finish_output()? }, // Native Tokens will be added automatically in the remainder output in try_select_inputs() diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs b/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs index ae278f4884..959e254c66 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs @@ -34,8 +34,7 @@ pub struct MintNftParams { sender: Option, /// NFT metadata feature. #[getset(get = "pub")] - #[serde(default, with = "crate::utils::serde::option_prefix_hex_bytes")] - metadata: Option>, + metadata: Option, /// NFT tag feature. #[getset(get = "pub")] #[serde(default, with = "crate::utils::serde::option_prefix_hex_bytes")] @@ -45,8 +44,7 @@ pub struct MintNftParams { issuer: Option, /// NFT immutable metadata feature. #[getset(get = "pub")] - #[serde(default, with = "crate::utils::serde::option_prefix_hex_bytes")] - immutable_metadata: Option>, + immutable_metadata: Option, } impl MintNftParams { @@ -79,7 +77,7 @@ impl MintNftParams { } /// Set the metadata - pub fn with_metadata(mut self, metadata: impl Into>>) -> Self { + pub fn with_metadata(mut self, metadata: impl Into>) -> Self { self.metadata = metadata.into(); self } @@ -103,7 +101,7 @@ impl MintNftParams { } /// Set the immutable metadata - pub fn with_immutable_metadata(mut self, immutable_metadata: impl Into>>) -> Self { + pub fn with_immutable_metadata(mut self, immutable_metadata: impl Into>) -> Self { self.immutable_metadata = immutable_metadata.into(); self } @@ -191,10 +189,9 @@ where nft_builder = nft_builder.add_feature(SenderFeature::new(sender)); } - // TODO: enable again when MetadataFeature is cleared up - // if let Some(metadata) = metadata { - // nft_builder = nft_builder.add_feature(MetadataFeature::new(metadata)?); - // } + if let Some(metadata) = metadata { + nft_builder = nft_builder.add_feature(metadata); + } if let Some(tag) = tag { nft_builder = nft_builder.add_feature(TagFeature::new(tag)?); @@ -204,10 +201,9 @@ where nft_builder = nft_builder.add_immutable_feature(IssuerFeature::new(issuer)); } - // TODO: enable again when MetadataFeature is cleared up - // if let Some(immutable_metadata) = immutable_metadata { - // nft_builder = nft_builder.add_immutable_feature(MetadataFeature::new(immutable_metadata)?); - // } + if let Some(immutable_metadata) = immutable_metadata { + nft_builder = nft_builder.add_immutable_feature(immutable_metadata); + } outputs.push(nft_builder.finish_output()?); } diff --git a/sdk/src/wallet/operations/transaction/prepare_output.rs b/sdk/src/wallet/operations/transaction/prepare_output.rs index 02f255408e..75da29a3a7 100644 --- a/sdk/src/wallet/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/operations/transaction/prepare_output.rs @@ -63,12 +63,9 @@ where )?); } - // TODO: enable again when MetadataFeature is cleared up - // if let Some(metadata) = features.metadata { - // first_output_builder = first_output_builder.add_feature(MetadataFeature::new( - // prefix_hex::decode::>(metadata).map_err(|_| Error::InvalidField("metadata"))?, - // )?); - // } + if let Some(metadata) = features.metadata { + first_output_builder = first_output_builder.add_feature(metadata); + } if let Some(sender) = features.sender { first_output_builder = first_output_builder.add_feature(SenderFeature::new(sender)) @@ -317,7 +314,7 @@ pub struct Assets { #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Features { pub tag: Option, - pub metadata: Option, + pub metadata: Option, pub issuer: Option, pub sender: Option, pub native_token: Option, diff --git a/sdk/src/wallet/types/balance.rs b/sdk/src/wallet/types/balance.rs index 3fb2249fe6..6374a50944 100644 --- a/sdk/src/wallet/types/balance.rs +++ b/sdk/src/wallet/types/balance.rs @@ -129,11 +129,9 @@ pub struct NativeTokensBalance { /// Balance that can currently be spent #[getset(get_copy = "pub")] pub(crate) available: U256, - // TODO: enable again when MetadataFeature is cleared up - // /// Token foundry immutable metadata - // #[getset(get = "pub")] - // #[serde(with = "crate::utils::serde::option_string")] - // pub(crate) metadata: Option, + /// Token foundry immutable metadata + #[getset(get = "pub")] + pub(crate) metadata: Option, } impl Default for NativeTokensBalance { @@ -142,8 +140,7 @@ impl Default for NativeTokensBalance { token_id: TokenId::null(), total: U256::from(0u8), available: U256::from(0u8), - // TODO: enable again when MetadataFeature is cleared up - // metadata: None, + metadata: None, } } } @@ -152,10 +149,9 @@ impl std::ops::AddAssign for NativeTokensBalance { fn add_assign(&mut self, rhs: Self) { self.total += rhs.total; self.available += rhs.available; - // TODO: enable again when MetadataFeature is cleared up - // if self.metadata.is_none() { - // self.metadata = rhs.metadata; - // } + if self.metadata.is_none() { + self.metadata = rhs.metadata; + } } } diff --git a/sdk/tests/wallet/native_tokens.rs b/sdk/tests/wallet/native_tokens.rs index 449d1a85b3..9447716812 100644 --- a/sdk/tests/wallet/native_tokens.rs +++ b/sdk/tests/wallet/native_tokens.rs @@ -110,20 +110,22 @@ async fn native_token_foundry_metadata() -> Result<()> { })) .await?; assert_eq!(balance.native_tokens().len(), 1); - // TODO: enable again when MetadataFeature is cleared up - // // Metadata should exist and be the same - // assert_eq!( - // balance - // .native_tokens() - // .iter() - // .find(|t| t.token_id() == &create_tx.token_id) - // .unwrap() - // .metadata() - // .as_ref() - // .unwrap() - // .data(), - // &foundry_metadata - // ); + // Metadata should exist and be the same + assert_eq!( + balance + .native_tokens() + .iter() + .find(|t| t.token_id() == &create_tx.token_id) + .unwrap() + .metadata() + .as_ref() + .unwrap() + .data() + .get(&packable::prefix::BoxedSlicePrefix::try_from(b"foundry".to_vec().into_boxed_slice()).unwrap()) + .unwrap() + .to_vec(), + foundry_metadata.to_vec() + ); tear_down(storage_path) } diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index 0b7bd9b824..d343c7f759 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use iota_sdk::{ types::block::{ address::{Address, Bech32Address, ToBech32Ext}, - output::{BasicOutput, MinimumOutputAmount, NativeToken, NftId, TokenId}, + output::{feature::MetadataFeature, BasicOutput, MinimumOutputAmount, NativeToken, NftId, TokenId}, protocol::CommittableAgeRange, slot::SlotIndex, }, @@ -551,10 +551,16 @@ async fn prepare_nft_output_features_update() -> Result<()> { let nft_options = [MintNftParams::new() .with_address(wallet_address.clone()) .with_sender(wallet_address.clone()) - .with_metadata(b"some nft metadata".to_vec()) + .with_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + vec![42], + vec![42], + )]))?) .with_tag(b"some nft tag".to_vec()) .with_issuer(wallet_address.clone()) - .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; + .with_immutable_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + vec![42], + vec![42], + )]))?)]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet @@ -589,12 +595,28 @@ async fn prepare_nft_output_features_update() -> Result<()> { assert_eq!(nft.address(), wallet.address().await.inner()); assert!(nft.features().sender().is_none()); assert!(nft.features().tag().is_none()); - // TODO: enable again when MetadataFeature is cleared up - // assert_eq!(nft.features().metadata().unwrap().data(), &[42]); - // assert_eq!( - // nft.immutable_features().metadata().unwrap().data(), - // b"some immutable nft metadata" - // ); + assert_eq!( + nft.features() + .metadata() + .unwrap() + .data() + .first_key_value() + .unwrap() + .1 + .to_vec(), + [42] + ); + assert_eq!( + nft.immutable_features() + .metadata() + .unwrap() + .data() + .first_key_value() + .unwrap() + .1 + .to_vec(), + [42] + ); assert_eq!( nft.immutable_features().issuer().unwrap().address(), wallet.address().await.inner() @@ -828,10 +850,16 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { let nft_options = [MintNftParams::new() .with_address(address.clone()) .with_sender(address.clone()) - .with_metadata(b"some nft metadata".to_vec()) + .with_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + vec![42], + vec![42], + )]))?) .with_tag(b"some nft tag".to_vec()) .with_issuer(address.clone()) - .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; + .with_immutable_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + vec![43], + vec![43], + )]))?)]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet @@ -866,11 +894,17 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { assert_eq!(nft.amount(), 52300); assert_eq!(nft.address(), wallet.address().await.inner()); assert!(nft.features().is_empty()); - // TODO: enable again when MetadataFeature is cleared up - // assert_eq!( - // nft.immutable_features().metadata().unwrap().data(), - // b"some immutable nft metadata" - // ); + assert_eq!( + nft.immutable_features() + .metadata() + .unwrap() + .data() + .first_key_value() + .unwrap() + .1 + .to_vec(), + [43] + ); assert_eq!( nft.immutable_features().issuer().unwrap().address(), wallet.address().await.inner() From 501631871b529c58fe292f09b28f2dcf25166a82 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 19 Dec 2023 14:58:40 +0100 Subject: [PATCH 05/51] Some fixes --- .../how_tos/nft_collection/00_mint_issuer_nft.rs | 11 ++++++++--- .../nft_collection/01_mint_collection_nft.rs | 7 +++++-- sdk/examples/how_tos/nfts/mint_nft.rs | 6 +++--- .../transaction/high_level/create_account.rs | 6 ++++-- sdk/tests/wallet/burn_outputs.rs | 13 +++++++++---- sdk/tests/wallet/output_preparation.rs | 14 ++++++++------ 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs b/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs index 2a0e5bca56..fe3fac5275 100644 --- a/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs +++ b/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs @@ -16,7 +16,7 @@ use iota_sdk::{ types::block::{ - output::{NftId, Output, OutputId}, + output::{feature::MetadataFeature, NftId, Output, OutputId}, payload::signed_transaction::TransactionId, }, wallet::{MintNftParams, Result}, @@ -47,8 +47,13 @@ async fn main() -> Result<()> { // Issue the minting transaction and wait for its inclusion println!("Sending NFT minting transaction..."); - let nft_mint_params = [MintNftParams::new() - .with_immutable_metadata(b"This NFT will be the issuer from the awesome NFT collection".to_vec())]; + let nft_mint_params = [MintNftParams::new().with_immutable_metadata( + MetadataFeature::new([( + b"data".to_vec(), + b"This NFT will be the issuer from the awesome NFT collection".to_vec(), + )]) + .unwrap(), + )]; let transaction = dbg!(wallet.mint_nfts(nft_mint_params, None).await)?; wait_for_inclusion(&transaction.transaction_id, &wallet).await?; diff --git a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs index 914897800e..3be6ca990a 100644 --- a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs +++ b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs @@ -17,7 +17,10 @@ use iota_sdk::{ types::block::{ address::{Bech32Address, NftAddress}, - output::{feature::Irc27Metadata, NftId}, + output::{ + feature::{Irc27Metadata, MetadataFeature}, + NftId, + }, payload::signed_transaction::TransactionId, }, wallet::{MintNftParams, Result}, @@ -63,7 +66,7 @@ async fn main() -> Result<()> { let nft_mint_params = (0..NFT_COLLECTION_SIZE) .map(|index| { MintNftParams::new() - .with_immutable_metadata(get_immutable_metadata(index).to_bytes()) + .with_immutable_metadata(MetadataFeature::try_from(get_immutable_metadata(index)).unwrap()) // The NFT address from the NFT we minted in mint_issuer_nft example .with_issuer(issuer.clone()) }) diff --git a/sdk/examples/how_tos/nfts/mint_nft.rs b/sdk/examples/how_tos/nfts/mint_nft.rs index 10264f07c9..7c86bd37de 100644 --- a/sdk/examples/how_tos/nfts/mint_nft.rs +++ b/sdk/examples/how_tos/nfts/mint_nft.rs @@ -13,7 +13,7 @@ use iota_sdk::{ types::block::output::{ - feature::{Irc27Metadata, IssuerFeature, SenderFeature}, + feature::{Irc27Metadata, IssuerFeature, MetadataFeature, SenderFeature}, unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, }, @@ -68,10 +68,10 @@ async fn main() -> Result<()> { let nft_params = [MintNftParams::new() .try_with_address(NFT1_OWNER_ADDRESS)? .try_with_sender(sender_address.clone())? - .with_metadata(NFT1_METADATA.as_bytes().to_vec()) + .with_metadata(MetadataFeature::new([(b"data".to_vec(), NFT1_METADATA.as_bytes().to_vec())]).unwrap()) .with_tag(NFT1_TAG.as_bytes().to_vec()) .try_with_issuer(sender_address.clone())? - .with_immutable_metadata(metadata.to_bytes())]; + .with_immutable_metadata(MetadataFeature::try_from(metadata).unwrap())]; let transaction = wallet.mint_nfts(nft_params, None).await?; println!("Transaction sent: {}", transaction.transaction_id); diff --git a/sdk/src/wallet/operations/transaction/high_level/create_account.rs b/sdk/src/wallet/operations/transaction/high_level/create_account.rs index 8b8e1fd2bb..118e4279db 100644 --- a/sdk/src/wallet/operations/transaction/high_level/create_account.rs +++ b/sdk/src/wallet/operations/transaction/high_level/create_account.rs @@ -151,8 +151,10 @@ mod tests { let params_some_1 = CreateAccountParams { address: None, - immutable_metadata: Some(b"immutable_metadata".to_vec()), - metadata: Some(b"metadata".to_vec()), + immutable_metadata: Some( + MetadataFeature::new([(b"data".to_vec(), b"immutable_metadata".to_vec())]).unwrap(), + ), + metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"metadata".to_vec())]).unwrap()), }; let json_some = serde_json::to_string(¶ms_some_1).unwrap(); let params_some_2 = serde_json::from_str(&json_some).unwrap(); diff --git a/sdk/tests/wallet/burn_outputs.rs b/sdk/tests/wallet/burn_outputs.rs index 1d2d7330c3..9345092988 100644 --- a/sdk/tests/wallet/burn_outputs.rs +++ b/sdk/tests/wallet/burn_outputs.rs @@ -4,6 +4,7 @@ use iota_sdk::{ client::api::input_selection::Burn, types::block::output::{ + feature::MetadataFeature, unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, NativeToken, NftId, NftOutputBuilder, OutputId, UnlockCondition, }, @@ -25,8 +26,10 @@ async fn mint_and_burn_nft() -> Result<()> { let nft_options = [MintNftParams::new() .with_address(wallet.address().await) - .with_metadata(b"some nft metadata".to_vec()) - .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; + .with_metadata(MetadataFeature::new([(b"data".to_vec(), b"some nft metadata".to_vec())]).unwrap()) + .with_immutable_metadata( + MetadataFeature::new([(b"data".to_vec(), b"some immutable nft metadata".to_vec())]).unwrap(), + )]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet @@ -285,8 +288,10 @@ async fn mint_and_burn_nft_with_account() -> Result<()> { wallet.sync(None).await?; let nft_options = [MintNftParams::new() - .with_metadata(b"some nft metadata".to_vec()) - .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; + .with_metadata(MetadataFeature::new([(b"data".to_vec(), b"some nft metadata".to_vec())]).unwrap()) + .with_immutable_metadata( + MetadataFeature::new([(b"data".to_vec(), b"some immutable nft metadata".to_vec())]).unwrap(), + )]; let nft_tx = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet .reissue_transaction_until_included(&nft_tx.transaction_id, None, None) diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index d343c7f759..cad4de1870 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -101,7 +101,7 @@ async fn output_preparation() -> Result<()> { amount: 300000, assets: None, features: Some(Features { - metadata: Some(prefix_hex::encode(b"Hello world")), + metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"Hello world".to_vec())]).unwrap()), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -127,7 +127,7 @@ async fn output_preparation() -> Result<()> { amount: 1, assets: None, features: Some(Features { - metadata: Some(prefix_hex::encode(b"Hello world")), + metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"Hello world".to_vec())]).unwrap()), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -156,7 +156,7 @@ async fn output_preparation() -> Result<()> { amount: 12000, assets: None, features: Some(Features { - metadata: Some(prefix_hex::encode(b"Hello world")), + metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"Hello world".to_vec())]).unwrap()), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -182,7 +182,7 @@ async fn output_preparation() -> Result<()> { amount: 1, assets: None, features: Some(Features { - metadata: Some(prefix_hex::encode(b"Hello world")), + metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"Hello world".to_vec())]).unwrap()), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -398,7 +398,9 @@ async fn output_preparation() -> Result<()> { amount: 42600, assets: None, features: Some(Features { - metadata: Some(prefix_hex::encode(b"Large metadata".repeat(100))), + metadata: Some( + MetadataFeature::new([(b"data".to_vec(), b"Large metadata".repeat(100).to_vec())]).unwrap(), + ), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -576,7 +578,7 @@ async fn prepare_nft_output_features_update() -> Result<()> { amount: 1_000_000, assets: Some(Assets { nft_id: Some(nft_id) }), features: Some(Features { - metadata: Some("0x2a".to_string()), + metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"0x2a".to_vec())]).unwrap()), tag: None, issuer: None, sender: None, From d299f751f8f66e3d5df853d1a8567295ddcc3c2c Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Tue, 19 Dec 2023 16:45:41 +0100 Subject: [PATCH 06/51] Address comments --- .../types/block/output/feature/metadata.rs | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 282e1f411a..82824c722f 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -125,12 +125,14 @@ impl core::fmt::Display for MetadataFeature { impl core::fmt::Debug for MetadataFeature { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let dbg_map: BTreeMap<&str, String> = self - .0 - .iter() - .map(|(k, v)| (alloc::str::from_utf8(k).unwrap(), prefix_hex::encode(v.as_ref()))) - .collect(); - write!(f, "MetadataFeature({dbg_map:?})") + write!( + f, + "MetadataFeature({:?})", + self.0 + .iter() + .map(|(k, v)| (alloc::str::from_utf8(k).unwrap(), prefix_hex::encode(v.as_ref()))) + .collect::>() + ) } } @@ -517,14 +519,18 @@ pub(crate) mod dto { impl From<&MetadataFeature> for MetadataFeatureDto { fn from(value: &MetadataFeature) -> Self { - let mut entries = BTreeMap::new(); - for (k, v) in value.0.iter() { - entries.insert( - // Safe to unwrap, keys must be ascii - alloc::str::from_utf8(k.as_ref()).expect("invalid ascii").to_string(), - prefix_hex::encode(v.as_ref()), - ); - } + let entries = value + .0 + .iter() + .map(|(k, v)| { + ( + // Safe to unwrap, keys must be ascii + alloc::str::from_utf8(k.as_ref()).expect("invalid ascii").to_string(), + prefix_hex::encode(v.as_ref()), + ) + }) + .collect::>(); + Self { kind: MetadataFeature::KIND, entries, From dc55c04cea5e6763fe3905e616bbf4df55be93d3 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 21 Dec 2023 12:06:14 +0100 Subject: [PATCH 07/51] Use is_ascii_graphic --- sdk/src/types/block/output/feature/metadata.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 82824c722f..f65a8d32a0 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -43,7 +43,7 @@ fn verify_keys_packable( ) -> Result<(), Error> { if VERIFY { for key in map.keys() { - if !key.is_ascii() { + if !key.iter().all(|b| b.is_ascii_graphic()) { return Err(Error::NonAsciiMetadataKey(key.to_vec())); } } @@ -63,7 +63,7 @@ impl MetadataFeature { let data = data.into(); for key in data.keys() { - if !key.is_ascii() { + if !key.iter().all(|b| b.is_ascii_graphic()) { return Err(Error::NonAsciiMetadataKey(key.to_vec())); } } @@ -93,7 +93,7 @@ impl TryFrom, Vec>> for MetadataFeature { BoxedSlicePrefix, >::new(); for (k, v) in data { - if !k.is_ascii() { + if !k.iter().all(|b| b.is_ascii_graphic()) { return Err(Error::NonAsciiMetadataKey(k.to_vec())); } res.insert( From 9383d0987152d1fa5e5ab7b352e489d5a79b3131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 8 Jan 2024 11:18:01 +0100 Subject: [PATCH 08/51] Handle potential > u8 type --- sdk/src/types/block/output/feature/metadata.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index f65a8d32a0..bd68b7ec3a 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -489,10 +489,13 @@ pub(crate) mod dto { fn deserialize>(d: D) -> Result { let value = Value::deserialize(d)?; Ok( - match value - .get("type") - .and_then(Value::as_u64) - .ok_or_else(|| de::Error::custom("invalid metadata type"))? as u8 + match u8::try_from( + value + .get("type") + .and_then(Value::as_u64) + .ok_or_else(|| de::Error::custom("invalid metadata type"))?, + ) + .map_err(|_| de::Error::custom("invalid metadata type: {e}"))? { Self::KIND => { let map: BTreeMap = serde_json::from_value( From 772f019ed6726736a4fe5f38a70893cd40204e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 8 Jan 2024 13:54:20 +0100 Subject: [PATCH 09/51] Add explicit is_empty() check --- sdk/src/types/block/output/feature/metadata.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index bd68b7ec3a..f7825f29ea 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -42,6 +42,11 @@ fn verify_keys_packable( >, ) -> Result<(), Error> { if VERIFY { + if map.is_empty() { + return Err(Error::InvalidMetadataFeatureKeyLength( + MetadataFeatureKeyLength::try_from(0u8).unwrap_err().into(), + )); + } for key in map.keys() { if !key.iter().all(|b| b.is_ascii_graphic()) { return Err(Error::NonAsciiMetadataKey(key.to_vec())); From 5fea869112229cb7b2f100c71b9dde645a92cb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 9 Jan 2024 15:43:07 +0100 Subject: [PATCH 10/51] Update packable --- Cargo.lock | 9 ++++++--- bindings/core/Cargo.toml | 2 +- sdk/Cargo.toml | 2 +- sdk/src/types/fuzz/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c03c77e00a..2a6072b294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2121,10 +2121,12 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "packable" -version = "0.10.0" -source = "git+https://github.com/iotaledger/common-rs?rev=c72287fa24254b349f6e935b847485b0be141557#c72287fa24254b349f6e935b847485b0be141557" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ebbd9715a319d515dbc253604dd00b0e2c8618e4e5e4d3e0b9b4e46b90ef98e" dependencies = [ "autocfg", + "hashbrown", "packable-derive", "primitive-types", "serde", @@ -2133,7 +2135,8 @@ dependencies = [ [[package]] name = "packable-derive" version = "0.9.0" -source = "git+https://github.com/iotaledger/common-rs?rev=c72287fa24254b349f6e935b847485b0be141557#c72287fa24254b349f6e935b847485b0be141557" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "858971e010057f7bcae183e545085b83d41280ca8abe0333613a7135fbb54430" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro-error", diff --git a/bindings/core/Cargo.toml b/bindings/core/Cargo.toml index d72ce04445..60f4591aea 100644 --- a/bindings/core/Cargo.toml +++ b/bindings/core/Cargo.toml @@ -23,7 +23,7 @@ iota-crypto = { version = "0.23.0", default-features = false, features = [ "bip44", ] } log = { version = "0.4.20", default-features = false } -packable = { git = "https://github.com/iotaledger/common-rs", rev = "c72287fa24254b349f6e935b847485b0be141557", default-features = false } +packable = { version = "0.10.1", default-features = false } prefix-hex = { version = "0.7.1", default-features = false } primitive-types = { version = "0.12.2", default-features = false } serde = { version = "1.0.195", default-features = false } diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index fa4066fb30..8558a0b5bf 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -45,7 +45,7 @@ iota-crypto = { version = "0.23.0", default-features = false, features = [ "secp256k1", ] } iterator-sorted = { version = "0.1.0", default-features = false } -packable = { git = "https://github.com/iotaledger/common-rs", rev = "c72287fa24254b349f6e935b847485b0be141557", default-features = false, features = [ +packable = { version = "0.10.1", default-features = false, features = [ "primitive-types", ] } paste = { version = "1.0.14", default-features = false } diff --git a/sdk/src/types/fuzz/Cargo.toml b/sdk/src/types/fuzz/Cargo.toml index c4f9eee680..1d084a0ec6 100644 --- a/sdk/src/types/fuzz/Cargo.toml +++ b/sdk/src/types/fuzz/Cargo.toml @@ -12,7 +12,7 @@ cargo-fuzz = true iota-types = { path = "..", default-features = false } libfuzzer-sys = { version = "0.4.7", default-features = false } -packable = { git = "https://github.com/iotaledger/common-rs", rev = "c72287fa24254b349f6e935b847485b0be141557", default-features = false } +packable = { version = "0.10.1", default-features = false } # Prevent this from interfering with workspaces [workspace] From e314ebef5109a886c1dc4e83b4c305b66e8a3b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 09:49:47 +0100 Subject: [PATCH 11/51] Accept IntoIterator in MetadataFeature::new() --- sdk/examples/client/output/build_account_output.rs | 5 +---- sdk/examples/client/output/build_basic_output.rs | 5 +---- sdk/examples/client/output/build_nft_output.rs | 8 ++++---- sdk/examples/how_tos/outputs/features.rs | 10 ++-------- sdk/src/types/block/output/feature/metadata.rs | 4 ++-- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/sdk/examples/client/output/build_account_output.rs b/sdk/examples/client/output/build_account_output.rs index b3ecfc051c..5a340d9b8e 100644 --- a/sdk/examples/client/output/build_account_output.rs +++ b/sdk/examples/client/output/build_account_output.rs @@ -49,10 +49,7 @@ async fn main() -> Result<()> { // Account id needs to be null the first time let account_output = AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .add_feature(SenderFeature::new(address.clone())) - .add_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - metadata.to_vec(), - metadata.to_vec(), - )]))?) + .add_feature(MetadataFeature::new([(metadata.to_vec(), metadata.to_vec())])?) .add_immutable_feature(IssuerFeature::new(address.clone())) .add_immutable_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( metadata.to_vec(), diff --git a/sdk/examples/client/output/build_basic_output.rs b/sdk/examples/client/output/build_basic_output.rs index a1e6dc5a00..e333273efb 100644 --- a/sdk/examples/client/output/build_basic_output.rs +++ b/sdk/examples/client/output/build_basic_output.rs @@ -45,10 +45,7 @@ async fn main() -> Result<()> { // with metadata feature block basic_output_builder .clone() - .add_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - KEY.to_vec(), - METADATA.to_vec(), - )]))?) + .add_feature(MetadataFeature::new([(KEY.to_vec(), METADATA.to_vec())])?) .finish_output()?, // with storage deposit return basic_output_builder diff --git a/sdk/examples/client/output/build_nft_output.rs b/sdk/examples/client/output/build_nft_output.rs index 40cb5b31bb..dd3a4f9bc5 100644 --- a/sdk/examples/client/output/build_nft_output.rs +++ b/sdk/examples/client/output/build_nft_output.rs @@ -63,16 +63,16 @@ async fn main() -> Result<()> { let nft_output = NftOutputBuilder::new_with_minimum_amount(storage_score_params, NftId::null()) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) .add_feature(SenderFeature::new(address.clone())) - .add_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + .add_feature(MetadataFeature::new([( "mutable".as_bytes().to_vec(), MUTABLE_METADATA.as_bytes().to_vec(), - )]))?) + )])?) .add_feature(TagFeature::new(TAG)?) .add_immutable_feature(IssuerFeature::new(address)) - .add_immutable_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( + .add_immutable_feature(MetadataFeature::new([( "IRC27".as_bytes().to_vec(), tip_27_immutable_metadata.as_bytes().to_vec(), - )]))?) + )])?) .finish_output()?; println!("{nft_output:#?}"); diff --git a/sdk/examples/how_tos/outputs/features.rs b/sdk/examples/how_tos/outputs/features.rs index ca331a56e9..0d745b699c 100644 --- a/sdk/examples/how_tos/outputs/features.rs +++ b/sdk/examples/how_tos/outputs/features.rs @@ -56,18 +56,12 @@ async fn main() -> Result<()> { // with metadata feature block nft_output_builder .clone() - .add_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - b"Hello".to_vec(), - b"World!".to_vec(), - )]))?) + .add_feature(MetadataFeature::new([(b"Hello".to_vec(), b"World!".to_vec())])?) .finish_output()?, // with immutable metadata feature block nft_output_builder .clone() - .add_immutable_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - b"Hello".to_vec(), - b"World!".to_vec(), - )]))?) + .add_immutable_feature(MetadataFeature::new([(b"Hello".to_vec(), b"World!".to_vec())])?) .finish_output()?, // with tag feature nft_output_builder diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index f7825f29ea..a3de25dd5f 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -64,8 +64,8 @@ impl MetadataFeature { /// Creates a new [`MetadataFeature`]. #[inline(always)] - pub fn new(data: impl Into, Vec>>) -> Result { - let data = data.into(); + pub fn new(data: impl IntoIterator, Vec)>) -> Result { + let data: BTreeMap, Vec> = data.into_iter().collect(); for key in data.keys() { if !key.iter().all(|b| b.is_ascii_graphic()) { From 9d7e91d1de94cd1f114a26eec3ca08ceca6c9d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 10:00:04 +0100 Subject: [PATCH 12/51] Simply Metadata::new usage in more places --- cli/src/wallet_cli/mod.rs | 5 +---- .../client/output/build_account_output.rs | 5 +---- .../high_level/minting/create_native_token.rs | 2 +- sdk/tests/wallet/output_preparation.rs | 20 ++++--------------- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 344750e127..0122efb4cf 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -778,10 +778,7 @@ pub async fn mint_nft_command( .with_sender(sender) .with_issuer(issuer); if let Some(metadata) = metadata { - nft_options = nft_options.with_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - metadata.clone(), - metadata, - )]))?); + nft_options = nft_options.with_metadata(MetadataFeature::new([(metadata.clone(), metadata)])?); } if let Some(immutable_metadata) = immutable_metadata { nft_options = nft_options.with_immutable_metadata(MetadataFeature::new( diff --git a/sdk/examples/client/output/build_account_output.rs b/sdk/examples/client/output/build_account_output.rs index 5a340d9b8e..a86d95785d 100644 --- a/sdk/examples/client/output/build_account_output.rs +++ b/sdk/examples/client/output/build_account_output.rs @@ -51,10 +51,7 @@ async fn main() -> Result<()> { .add_feature(SenderFeature::new(address.clone())) .add_feature(MetadataFeature::new([(metadata.to_vec(), metadata.to_vec())])?) .add_immutable_feature(IssuerFeature::new(address.clone())) - .add_immutable_feature(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - metadata.to_vec(), - metadata.to_vec(), - )]))?) + .add_immutable_feature(MetadataFeature::new([(metadata.to_vec(), metadata.to_vec())])?) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output()?; diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs index 35d26339b3..0206c59bad 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs @@ -174,7 +174,7 @@ where if let Some(foundry_metadata) = params.foundry_metadata { foundry_builder = foundry_builder.add_immutable_feature(MetadataFeature::new( // TODO: what hardcoded key or let user provide the full metadata? - BTreeMap::from_iter(vec![(b"foundry".to_vec(), foundry_metadata)]), + [(b"foundry".to_vec(), foundry_metadata)], )?); } diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index cad4de1870..949ef3b057 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -553,16 +553,10 @@ async fn prepare_nft_output_features_update() -> Result<()> { let nft_options = [MintNftParams::new() .with_address(wallet_address.clone()) .with_sender(wallet_address.clone()) - .with_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - vec![42], - vec![42], - )]))?) + .with_metadata(MetadataFeature::new([(vec![42], vec![42])])?) .with_tag(b"some nft tag".to_vec()) .with_issuer(wallet_address.clone()) - .with_immutable_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - vec![42], - vec![42], - )]))?)]; + .with_immutable_metadata(MetadataFeature::new([(vec![42], vec![42])])?)]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet @@ -852,16 +846,10 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { let nft_options = [MintNftParams::new() .with_address(address.clone()) .with_sender(address.clone()) - .with_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - vec![42], - vec![42], - )]))?) + .with_metadata(MetadataFeature::new([(vec![42], vec![42])])?) .with_tag(b"some nft tag".to_vec()) .with_issuer(address.clone()) - .with_immutable_metadata(MetadataFeature::new(std::collections::BTreeMap::from_iter(vec![( - vec![43], - vec![43], - )]))?)]; + .with_immutable_metadata(MetadataFeature::new([(vec![43], vec![43])])?)]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet From 2ae16956a57f394c7a6af0d28b76c20ceea49b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 10:01:41 +0100 Subject: [PATCH 13/51] Update comment --- sdk/tests/client/input_selection/nft_outputs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index 50306791e1..ff23a74eb7 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/input_selection/nft_outputs.rs @@ -1210,7 +1210,7 @@ fn transitioned_zero_nft_id_no_longer_is_zero() { }); } -// TODO: enable again when MetadataFeature is cleared up +// TODO: enable again when Irc27Metadata is updated // #[test] // fn changed_immutable_metadata() { // let protocol_parameters = protocol_parameters(); From 03b52bbbf04215b86dc738d604bee6b11d307959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 10:09:13 +0100 Subject: [PATCH 14/51] Add TryFrom, Vec)>> for MetadataFeature, uncomment test --- .../types/block/output/feature/metadata.rs | 25 ++++ .../high_level/minting/create_native_token.rs | 2 - .../client/input_selection/nft_outputs.rs | 119 +++++++++--------- 3 files changed, 84 insertions(+), 62 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index a3de25dd5f..719f8bf6db 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -89,6 +89,31 @@ impl StorageScore for MetadataFeature {} impl WorkScore for MetadataFeature {} +impl TryFrom, Vec)>> for MetadataFeature { + type Error = Error; + + fn try_from(data: Vec<(Vec, Vec)>) -> Result { + let mut res = BTreeMap::< + BoxedSlicePrefix, + BoxedSlicePrefix, + >::new(); + for (k, v) in data { + if !k.iter().all(|b| b.is_ascii_graphic()) { + return Err(Error::NonAsciiMetadataKey(k.to_vec())); + } + res.insert( + k.into_boxed_slice() + .try_into() + .map_err(Error::InvalidMetadataFeatureKeyLength)?, + v.into_boxed_slice() + .try_into() + .map_err(Error::InvalidMetadataFeatureValueLength)?, + ); + } + Ok(Self(res.try_into().map_err(Error::InvalidMetadataFeatureLength)?)) + } +} + impl TryFrom, Vec>> for MetadataFeature { type Error = Error; diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs index 0206c59bad..1cb8695367 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs @@ -1,8 +1,6 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::collections::BTreeMap; - use primitive_types::U256; use serde::{Deserialize, Serialize}; diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index ff23a74eb7..c2318ad35c 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/input_selection/nft_outputs.rs @@ -1210,68 +1210,67 @@ fn transitioned_zero_nft_id_no_longer_is_zero() { }); } -// TODO: enable again when Irc27Metadata is updated -// #[test] -// fn changed_immutable_metadata() { -// let protocol_parameters = protocol_parameters(); -// let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); +#[test] +fn changed_immutable_metadata() { + let protocol_parameters = protocol_parameters(); + let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); -// #[cfg(feature = "irc_27")] -// let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( -// "image/jpeg", -// "https://mywebsite.com/my-nft-files-1.jpeg".parse().unwrap(), -// "file 1", -// ) -// .with_issuer_name("Alice"); -// #[cfg(not(feature = "irc_27"))] -// let metadata = [1, 2, 3]; - -// let nft_output = -// NftOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters(), nft_id_1) -// .with_immutable_features(MetadataFeature::try_from(metadata)) -// .add_unlock_condition(AddressUnlockCondition::new( -// Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), -// )) -// .finish_output() -// .unwrap(); - -// let inputs = [InputSigningData { -// output: nft_output.clone(), -// output_metadata: rand_output_metadata(), -// chain: None, -// }]; - -// #[cfg(feature = "irc_27")] -// let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( -// "image/jpeg", -// "https://mywebsite.com/my-nft-files-2.jpeg".parse().unwrap(), -// "file 2", -// ) -// .with_issuer_name("Alice"); -// #[cfg(not(feature = "irc_27"))] -// let metadata = [4, 5, 6]; + #[cfg(feature = "irc_27")] + let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( + "image/jpeg", + "https://mywebsite.com/my-nft-files-1.jpeg".parse().unwrap(), + "file 1", + ) + .with_issuer_name("Alice"); + #[cfg(not(feature = "irc_27"))] + let metadata = vec![(vec![42], vec![42])]; + + let nft_output = + NftOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters(), nft_id_1) + .with_immutable_features(MetadataFeature::try_from(metadata)) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(); + + let inputs = [InputSigningData { + output: nft_output.clone(), + output_metadata: rand_output_metadata(), + chain: None, + }]; + + #[cfg(feature = "irc_27")] + let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( + "image/jpeg", + "https://mywebsite.com/my-nft-files-2.jpeg".parse().unwrap(), + "file 2", + ) + .with_issuer_name("Alice"); + #[cfg(not(feature = "irc_27"))] + let metadata = vec![(vec![43], vec![43])]; -// // New nft output with changed immutable metadata feature -// let updated_nft_output = NftOutputBuilder::from(nft_output.as_nft()) -// .with_minimum_amount(protocol_parameters.storage_score_parameters()) -// .with_immutable_features(MetadataFeature::try_from(metadata)) -// .finish_output() -// .unwrap(); + // New nft output with changed immutable metadata feature + let updated_nft_output = NftOutputBuilder::from(nft_output.as_nft()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) + .with_immutable_features(MetadataFeature::try_from(metadata)) + .finish_output() + .unwrap(); -// let outputs = [updated_nft_output]; + let outputs = [updated_nft_output]; -// let selected = InputSelection::new( -// inputs, -// outputs, -// [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], -// protocol_parameters, -// ) -// .select(); + let selected = InputSelection::new( + inputs, + outputs, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + protocol_parameters, + ) + .select(); -// assert!(matches!( -// selected, -// Err(Error::UnfulfillableRequirement(Requirement::Nft( -// nft_id, -// ))) if nft_id == nft_id_1 -// )); -// } + assert!(matches!( + selected, + Err(Error::UnfulfillableRequirement(Requirement::Nft( + nft_id, + ))) if nft_id == nft_id_1 + )); +} From 991155a7356feaa46ab6692c9b631c74281e0ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 10:13:10 +0100 Subject: [PATCH 15/51] One more place --- cli/src/wallet_cli/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 0122efb4cf..09148c5901 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -781,9 +781,10 @@ pub async fn mint_nft_command( nft_options = nft_options.with_metadata(MetadataFeature::new([(metadata.clone(), metadata)])?); } if let Some(immutable_metadata) = immutable_metadata { - nft_options = nft_options.with_immutable_metadata(MetadataFeature::new( - std::collections::BTreeMap::from_iter(vec![(immutable_metadata.clone(), immutable_metadata)]), - )?); + nft_options = nft_options.with_immutable_metadata(MetadataFeature::new([( + immutable_metadata.clone(), + immutable_metadata, + )])?); } let transaction = wallet.mint_nfts([nft_options], None).await?; From 2f15ad6f1b73d54c96fe184296e237068561b2a9 Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:06:06 +0100 Subject: [PATCH 16/51] Update sdk/src/types/block/rand/output/feature.rs Co-authored-by: Thibault Martinez --- sdk/src/types/block/rand/output/feature.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/types/block/rand/output/feature.rs b/sdk/src/types/block/rand/output/feature.rs index e5c6615123..4b30ba63a7 100644 --- a/sdk/src/types/block/rand/output/feature.rs +++ b/sdk/src/types/block/rand/output/feature.rs @@ -37,6 +37,7 @@ pub fn rand_issuer_feature() -> IssuerFeature { pub fn rand_metadata_feature() -> MetadataFeature { let mut map = BTreeMap::new(); let mut total_size = 0; + for _ in 0..10 { if total_size >= *MetadataFeature::LENGTH_RANGE.end() as usize - u8::MAX as usize { break; From 5f36dc4f65ef326d70eb5213b3b383265c590473 Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:06:33 +0100 Subject: [PATCH 17/51] Update cli/src/wallet_cli/mod.rs Co-authored-by: Thibault Martinez --- cli/src/wallet_cli/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 09148c5901..67623d41e0 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -777,6 +777,7 @@ pub async fn mint_nft_command( .with_tag(tag) .with_sender(sender) .with_issuer(issuer); + if let Some(metadata) = metadata { nft_options = nft_options.with_metadata(MetadataFeature::new([(metadata.clone(), metadata)])?); } From 64a69560785c4f5fad892e57a1b5e2d3b7f4c1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 11:07:09 +0100 Subject: [PATCH 18/51] Update error name --- sdk/src/types/block/error.rs | 4 ++-- sdk/src/types/block/output/feature/metadata.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 33b2acd572..165da47dce 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -60,7 +60,7 @@ pub enum Error { InvalidAccountIndex(>::Error), InvalidAnchorIndex(>::Error), InvalidBlockBodyKind(u8), - NonAsciiMetadataKey(Vec), + NonGraphicAsciiMetadataKey(Vec), InvalidRewardInputIndex(>::Error), InvalidStorageDepositAmount(u64), /// Invalid transaction failure reason byte. @@ -258,7 +258,7 @@ impl fmt::Display for Error { write!(f, "invalid capability byte at index {index}: {byte:x}") } Self::InvalidBlockBodyKind(k) => write!(f, "invalid block body kind: {k}"), - Self::NonAsciiMetadataKey(b) => write!(f, "non ASCII key: {b:?}"), + Self::NonGraphicAsciiMetadataKey(b) => write!(f, "non graphic ASCII key: {b:?}"), Self::InvalidRewardInputIndex(idx) => write!(f, "invalid reward input index: {idx}"), Self::InvalidStorageDepositAmount(amount) => { write!(f, "invalid storage deposit amount: {amount}") diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 719f8bf6db..b1d57b4a68 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -49,7 +49,7 @@ fn verify_keys_packable( } for key in map.keys() { if !key.iter().all(|b| b.is_ascii_graphic()) { - return Err(Error::NonAsciiMetadataKey(key.to_vec())); + return Err(Error::NonGraphicAsciiMetadataKey(key.to_vec())); } } } @@ -69,7 +69,7 @@ impl MetadataFeature { for key in data.keys() { if !key.iter().all(|b| b.is_ascii_graphic()) { - return Err(Error::NonAsciiMetadataKey(key.to_vec())); + return Err(Error::NonGraphicAsciiMetadataKey(key.to_vec())); } } Self::try_from(data) @@ -99,7 +99,7 @@ impl TryFrom, Vec)>> for MetadataFeature { >::new(); for (k, v) in data { if !k.iter().all(|b| b.is_ascii_graphic()) { - return Err(Error::NonAsciiMetadataKey(k.to_vec())); + return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); } res.insert( k.into_boxed_slice() @@ -124,7 +124,7 @@ impl TryFrom, Vec>> for MetadataFeature { >::new(); for (k, v) in data { if !k.iter().all(|b| b.is_ascii_graphic()) { - return Err(Error::NonAsciiMetadataKey(k.to_vec())); + return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); } res.insert( k.into_boxed_slice() From c27bb49650c1def6a5bb5b5684a9054db1c64d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 11:27:41 +0100 Subject: [PATCH 19/51] Change foundry_metadata, add TODOs --- cli/src/wallet_cli/mod.rs | 9 +++++++-- sdk/src/types/block/output/feature/metadata.rs | 2 ++ .../high_level/minting/create_native_token.rs | 9 +++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 67623d41e0..deceef7a8e 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -576,7 +576,7 @@ pub async fn create_native_token_command( wallet: &Wallet, circulating_supply: String, maximum_supply: String, - foundry_metadata: Option>, + foundry_metadata: Option, ) -> Result<(), Error> { // If no account output exists, create one first if wallet.balance().await?.accounts().is_empty() { @@ -779,10 +779,12 @@ pub async fn mint_nft_command( .with_issuer(issuer); if let Some(metadata) = metadata { + // TODO: Let user specify key or the full metadata nft_options = nft_options.with_metadata(MetadataFeature::new([(metadata.clone(), metadata)])?); } if let Some(immutable_metadata) = immutable_metadata { nft_options = nft_options.with_immutable_metadata(MetadataFeature::new([( + // TODO: Let user specify key or the full metadata immutable_metadata.clone(), immutable_metadata, )])?); @@ -1243,7 +1245,10 @@ pub async fn prompt_internal( wallet, circulating_supply, maximum_supply, - bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file).await?, + bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file) + .await? + // TODO: Let user specify key or the full metadata + .map(|d| MetadataFeature::new([(b"data".to_vec(), d)]).unwrap()), ) .await } diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index b1d57b4a68..61cadf259d 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -279,6 +279,7 @@ pub(crate) mod irc_27 { type Error = Error; fn try_from(value: Irc27Metadata) -> Result { + // TODO: is this hardcoded key correct or should users provide it? Self::new([("irc-27".as_bytes().to_vec(), value.to_bytes())]) } } @@ -454,6 +455,7 @@ pub(crate) mod irc_30 { type Error = Error; fn try_from(value: Irc30Metadata) -> Result { + // TODO: is this hardcoded key correct or should users provide it? Self::new([("irc-30".as_bytes().to_vec(), value.to_bytes())]) } } diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs index 1cb8695367..c0bf62719e 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs @@ -34,8 +34,8 @@ pub struct CreateNativeTokenParams { /// Maximum supply pub maximum_supply: U256, /// Foundry metadata - #[serde(default, with = "crate::utils::serde::option_prefix_hex_bytes")] - pub foundry_metadata: Option>, + #[serde(default)] + pub foundry_metadata: Option, } /// The result of a transaction to create a native token @@ -170,10 +170,7 @@ where ))); if let Some(foundry_metadata) = params.foundry_metadata { - foundry_builder = foundry_builder.add_immutable_feature(MetadataFeature::new( - // TODO: what hardcoded key or let user provide the full metadata? - [(b"foundry".to_vec(), foundry_metadata)], - )?); + foundry_builder = foundry_builder.add_immutable_feature(foundry_metadata); } foundry_builder.finish_output()? From 95709a702516cd3192c86b9bb1bbac1386dca4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 11:35:31 +0100 Subject: [PATCH 20/51] Update test --- sdk/tests/wallet/native_tokens.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sdk/tests/wallet/native_tokens.rs b/sdk/tests/wallet/native_tokens.rs index 9447716812..80fe56a55b 100644 --- a/sdk/tests/wallet/native_tokens.rs +++ b/sdk/tests/wallet/native_tokens.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::{ + types::block::output::feature::MetadataFeature, wallet::{CreateNativeTokenParams, Result, SyncOptions}, U256, }; @@ -86,7 +87,7 @@ async fn native_token_foundry_metadata() -> Result<()> { .await?; wallet.sync(None).await?; - let foundry_metadata = [1, 3, 3, 7]; + let foundry_metadata = MetadataFeature::new([(vec![1, 3], vec![3, 7])])?; let create_tx = wallet .create_native_token( @@ -94,7 +95,7 @@ async fn native_token_foundry_metadata() -> Result<()> { account_id: None, circulating_supply: U256::from(50), maximum_supply: U256::from(100), - foundry_metadata: Some(foundry_metadata.to_vec()), + foundry_metadata: Some(foundry_metadata.clone()), }, None, ) @@ -119,12 +120,8 @@ async fn native_token_foundry_metadata() -> Result<()> { .unwrap() .metadata() .as_ref() - .unwrap() - .data() - .get(&packable::prefix::BoxedSlicePrefix::try_from(b"foundry".to_vec().into_boxed_slice()).unwrap()) - .unwrap() - .to_vec(), - foundry_metadata.to_vec() + .unwrap(), + &foundry_metadata ); tear_down(storage_path) From 98547c042063df1b4e9f6f8471540ed0fb8b2ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Thu, 11 Jan 2024 11:44:34 +0100 Subject: [PATCH 21/51] Update example --- sdk/examples/how_tos/native_tokens/create.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/examples/how_tos/native_tokens/create.rs b/sdk/examples/how_tos/native_tokens/create.rs index 467162c741..4989d8b3bf 100644 --- a/sdk/examples/how_tos/native_tokens/create.rs +++ b/sdk/examples/how_tos/native_tokens/create.rs @@ -73,7 +73,7 @@ async fn main() -> Result<()> { account_id: None, circulating_supply: U256::from(CIRCULATING_SUPPLY), maximum_supply: U256::from(MAXIMUM_SUPPLY), - foundry_metadata: Some(metadata.to_bytes()), + foundry_metadata: Some(metadata.try_into()?), }; let transaction = wallet.create_native_token(params, None).await?; From 8ea060de44846806e97e016af501b849dd2aaa32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 12 Jan 2024 09:17:20 +0100 Subject: [PATCH 22/51] Update example --- bindings/core/src/method/secret_manager.rs | 2 +- sdk/examples/client/output/build_account_output.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/core/src/method/secret_manager.rs b/bindings/core/src/method/secret_manager.rs index a4298f2b1e..2a13acbca0 100644 --- a/bindings/core/src/method/secret_manager.rs +++ b/bindings/core/src/method/secret_manager.rs @@ -99,7 +99,7 @@ pub enum SecretManagerMethod { /// Expected response: [`Ok`](crate::Response::Ok) #[cfg(feature = "stronghold")] #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] - ClearStrongholdPassword + ClearStrongholdPassword, } #[cfg(test)] diff --git a/sdk/examples/client/output/build_account_output.rs b/sdk/examples/client/output/build_account_output.rs index a86d95785d..eb5f5f8661 100644 --- a/sdk/examples/client/output/build_account_output.rs +++ b/sdk/examples/client/output/build_account_output.rs @@ -49,9 +49,9 @@ async fn main() -> Result<()> { // Account id needs to be null the first time let account_output = AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .add_feature(SenderFeature::new(address.clone())) - .add_feature(MetadataFeature::new([(metadata.to_vec(), metadata.to_vec())])?) + .add_feature(MetadataFeature::new([(b"data".to_vec(), metadata.to_vec())])?) .add_immutable_feature(IssuerFeature::new(address.clone())) - .add_immutable_feature(MetadataFeature::new([(metadata.to_vec(), metadata.to_vec())])?) + .add_immutable_feature(MetadataFeature::new([(b"data".to_vec(), metadata.to_vec())])?) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output()?; From a9cdb68997235206750e14247d74d34afb5f915b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 12 Jan 2024 10:27:11 +0100 Subject: [PATCH 23/51] Return error for duplicated keys --- .../types/block/output/feature/metadata.rs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 61cadf259d..cb61c3c464 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -101,14 +101,19 @@ impl TryFrom, Vec)>> for MetadataFeature { if !k.iter().all(|b| b.is_ascii_graphic()) { return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); } - res.insert( - k.into_boxed_slice() - .try_into() - .map_err(Error::InvalidMetadataFeatureKeyLength)?, - v.into_boxed_slice() - .try_into() - .map_err(Error::InvalidMetadataFeatureValueLength)?, - ); + if res + .insert( + k.into_boxed_slice() + .try_into() + .map_err(Error::InvalidMetadataFeatureKeyLength)?, + v.into_boxed_slice() + .try_into() + .map_err(Error::InvalidMetadataFeatureValueLength)?, + ) + .is_some() + { + return Err(Error::InvalidMetadataFeature("Duplicated metadata key".to_string())); + }; } Ok(Self(res.try_into().map_err(Error::InvalidMetadataFeatureLength)?)) } From b8d1b63b9739f44fcc6d177ffda8b4bfd661ef37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 12 Jan 2024 12:24:12 +0100 Subject: [PATCH 24/51] Remove duplicated validation --- sdk/src/types/block/output/feature/metadata.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index cb61c3c464..467886cca0 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -42,11 +42,6 @@ fn verify_keys_packable( >, ) -> Result<(), Error> { if VERIFY { - if map.is_empty() { - return Err(Error::InvalidMetadataFeatureKeyLength( - MetadataFeatureKeyLength::try_from(0u8).unwrap_err().into(), - )); - } for key in map.keys() { if !key.iter().all(|b| b.is_ascii_graphic()) { return Err(Error::NonGraphicAsciiMetadataKey(key.to_vec())); From 43dbb057f20608eb896ca57d5449ac55e5ac9849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 12 Jan 2024 12:46:34 +0100 Subject: [PATCH 25/51] Update cli commands --- cli/src/cli.rs | 2 +- cli/src/wallet_cli/mod.rs | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index f074dd77eb..44e1cbacad 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -64,7 +64,7 @@ pub struct InitParameters { #[arg(short, long, value_name = "URL", env = "NODE_URL", default_value = DEFAULT_NODE_URL)] pub node_url: String, /// Set the BIP path, `4219/0/0/0` if not provided. - #[arg(short, long, value_parser = parse_bip_path)] + #[arg(short, long, value_parser = parse_bip_path, default_value = "4219/0/0/0")] pub bip_path: Option, /// Set the Bech32-encoded wallet address. #[arg(short, long)] diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index deceef7a8e..8d18913188 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -142,12 +142,18 @@ pub enum WalletCommand { MintNft { /// Address to send the NFT to, e.g. rms1qztwng6cty8cfm42nzvq099ev7udhrnk0rw8jt8vttf9kpqnxhpsx869vr3. address: Option, + /// Immutable metadata key, e.g. --immutable-metadata-key data. + #[arg(long, group = "immutable_metadata", default_value = "data")] + immutable_metadata_key: String, #[arg(long, group = "immutable_metadata")] /// Immutable metadata to attach to the NFT, e.g. --immutable-metadata-hex 0xdeadbeef. immutable_metadata_hex: Option, /// Immutable metadata to attach to the NFT, e.g. --immutable-metadata-file ./nft-immutable-metadata.json. #[arg(long, group = "immutable_metadata")] immutable_metadata_file: Option, + /// Metadata key, e.g. --metadata-key data. + #[arg(long, group = "metadata", default_value = "data")] + metadata_key: String, /// Metadata to attach to the NFT, e.g. --metadata-hex 0xdeadbeef. #[arg(long, group = "metadata")] metadata_hex: Option, @@ -760,7 +766,9 @@ pub async fn mint_native_token_command(wallet: &Wallet, token_id: String, amount pub async fn mint_nft_command( wallet: &Wallet, address: Option, + immutable_metadata_key: String, immutable_metadata: Option>, + metadata_key: String, metadata: Option>, tag: Option, sender: Option, @@ -779,13 +787,11 @@ pub async fn mint_nft_command( .with_issuer(issuer); if let Some(metadata) = metadata { - // TODO: Let user specify key or the full metadata - nft_options = nft_options.with_metadata(MetadataFeature::new([(metadata.clone(), metadata)])?); + nft_options = nft_options.with_metadata(MetadataFeature::new([(metadata_key.into_bytes(), metadata)])?); } if let Some(immutable_metadata) = immutable_metadata { nft_options = nft_options.with_immutable_metadata(MetadataFeature::new([( - // TODO: Let user specify key or the full metadata - immutable_metadata.clone(), + immutable_metadata_key.into_bytes(), immutable_metadata, )])?); } @@ -1277,8 +1283,10 @@ pub async fn prompt_internal( } WalletCommand::MintNft { address, + immutable_metadata_key, immutable_metadata_hex, immutable_metadata_file, + metadata_key, metadata_hex, metadata_file, tag, @@ -1288,7 +1296,9 @@ pub async fn prompt_internal( mint_nft_command( wallet, address, + immutable_metadata_key, bytes_from_hex_or_file(immutable_metadata_hex, immutable_metadata_file).await?, + metadata_key, bytes_from_hex_or_file(metadata_hex, metadata_file).await?, tag, sender, From aab512920ddc4291a0771923b1fe19647f1c48c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 12 Jan 2024 12:52:43 +0100 Subject: [PATCH 26/51] Temporarily allow broken doc links --- bindings/core/src/response.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/core/src/response.rs b/bindings/core/src/response.rs index 89cac737a3..cf6116cb65 100644 --- a/bindings/core/src/response.rs +++ b/bindings/core/src/response.rs @@ -52,6 +52,9 @@ use { use crate::{error::Error, OmittedDebug}; +// TODO: disallow when https://github.com/iotaledger/iota-sdk/issues/1822 is done +#[allow(rustdoc::broken_intra_doc_links)] + /// The response message. #[derive(Serialize, Derivative)] #[derivative(Debug)] From cfa871c1791a20fd90048415bb8e64ee5793ea05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 12 Jan 2024 15:13:39 +0100 Subject: [PATCH 27/51] Address review comments --- cli/src/wallet_cli/mod.rs | 4 ++-- sdk/src/types/block/output/feature/metadata.rs | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 0527a16a86..36863720af 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -143,7 +143,7 @@ pub enum WalletCommand { /// Address to send the NFT to, e.g. rms1qztwng6cty8cfm42nzvq099ev7udhrnk0rw8jt8vttf9kpqnxhpsx869vr3. address: Option, /// Immutable metadata key, e.g. --immutable-metadata-key data. - #[arg(long, group = "immutable_metadata", default_value = "data")] + #[arg(long, default_value = "data")] immutable_metadata_key: String, #[arg(long, group = "immutable_metadata")] /// Immutable metadata to attach to the NFT, e.g. --immutable-metadata-hex 0xdeadbeef. @@ -152,7 +152,7 @@ pub enum WalletCommand { #[arg(long, group = "immutable_metadata")] immutable_metadata_file: Option, /// Metadata key, e.g. --metadata-key data. - #[arg(long, group = "metadata", default_value = "data")] + #[arg(long, default_value = "data")] metadata_key: String, /// Metadata to attach to the NFT, e.g. --metadata-hex 0xdeadbeef. #[arg(long, group = "metadata")] diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 467886cca0..3bf4f1e05e 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -62,12 +62,9 @@ impl MetadataFeature { pub fn new(data: impl IntoIterator, Vec)>) -> Result { let data: BTreeMap, Vec> = data.into_iter().collect(); - for key in data.keys() { - if !key.iter().all(|b| b.is_ascii_graphic()) { - return Err(Error::NonGraphicAsciiMetadataKey(key.to_vec())); - } - } - Self::try_from(data) + let metadata = Self::try_from(data)?; + verify_keys_packable::(&metadata.0)?; + Ok(metadata) } /// Returns the data. From 47838d1bbfc93d954934d8014d9993e64b0a6409 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Fri, 12 Jan 2024 15:46:41 +0100 Subject: [PATCH 28/51] Apply suggestions from code review --- sdk/src/types/block/output/feature/metadata.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 3bf4f1e05e..4e5f122654 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -61,10 +61,9 @@ impl MetadataFeature { #[inline(always)] pub fn new(data: impl IntoIterator, Vec)>) -> Result { let data: BTreeMap, Vec> = data.into_iter().collect(); - let metadata = Self::try_from(data)?; verify_keys_packable::(&metadata.0)?; - Ok(metadata) + Ok(metadata) } /// Returns the data. From fa412f8cf1549b179a5bacd80078b84f21512b6b Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Fri, 12 Jan 2024 15:48:17 +0100 Subject: [PATCH 29/51] sorry --- sdk/src/types/block/output/feature/metadata.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 4e5f122654..0ead53bb86 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -62,8 +62,10 @@ impl MetadataFeature { pub fn new(data: impl IntoIterator, Vec)>) -> Result { let data: BTreeMap, Vec> = data.into_iter().collect(); let metadata = Self::try_from(data)?; + verify_keys_packable::(&metadata.0)?; - Ok(metadata) + + Ok(metadata) } /// Returns the data. From c93904abd17618726c23f11046034c5c8fd89d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 12 Jan 2024 16:10:23 +0100 Subject: [PATCH 30/51] Refactor try from impls --- .../types/block/output/feature/metadata.rs | 59 ++++++++----------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 0ead53bb86..60f6ba84a0 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -86,29 +86,7 @@ impl TryFrom, Vec)>> for MetadataFeature { type Error = Error; fn try_from(data: Vec<(Vec, Vec)>) -> Result { - let mut res = BTreeMap::< - BoxedSlicePrefix, - BoxedSlicePrefix, - >::new(); - for (k, v) in data { - if !k.iter().all(|b| b.is_ascii_graphic()) { - return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); - } - if res - .insert( - k.into_boxed_slice() - .try_into() - .map_err(Error::InvalidMetadataFeatureKeyLength)?, - v.into_boxed_slice() - .try_into() - .map_err(Error::InvalidMetadataFeatureValueLength)?, - ) - .is_some() - { - return Err(Error::InvalidMetadataFeature("Duplicated metadata key".to_string())); - }; - } - Ok(Self(res.try_into().map_err(Error::InvalidMetadataFeatureLength)?)) + metadata_feature_from_iter(data) } } @@ -116,25 +94,36 @@ impl TryFrom, Vec>> for MetadataFeature { type Error = Error; fn try_from(data: BTreeMap, Vec>) -> Result { - let mut res = BTreeMap::< - BoxedSlicePrefix, - BoxedSlicePrefix, - >::new(); - for (k, v) in data { - if !k.iter().all(|b| b.is_ascii_graphic()) { - return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); - } - res.insert( + metadata_feature_from_iter(data) + } +} + +fn metadata_feature_from_iter(data: impl IntoIterator, Vec)>) -> Result { + let mut res = BTreeMap::< + BoxedSlicePrefix, + BoxedSlicePrefix, + >::new(); + for (k, v) in data { + if !k.iter().all(|b| b.is_ascii_graphic()) { + return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); + } + if res + .insert( k.into_boxed_slice() .try_into() .map_err(Error::InvalidMetadataFeatureKeyLength)?, v.into_boxed_slice() .try_into() .map_err(Error::InvalidMetadataFeatureValueLength)?, - ); - } - Ok(Self(res.try_into().map_err(Error::InvalidMetadataFeatureLength)?)) + ) + .is_some() + { + return Err(Error::InvalidMetadataFeature("Duplicated metadata key".to_string())); + }; } + Ok(MetadataFeature( + res.try_into().map_err(Error::InvalidMetadataFeatureLength)?, + )) } impl core::fmt::Display for MetadataFeature { From f6929b733dc5da8a504da7f6064448d5f2946e12 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Fri, 12 Jan 2024 16:12:11 +0100 Subject: [PATCH 31/51] Update sdk/src/types/block/output/feature/metadata.rs --- sdk/src/types/block/output/feature/metadata.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 60f6ba84a0..8a75636b56 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -103,6 +103,7 @@ fn metadata_feature_from_iter(data: impl IntoIterator, Vec)> BoxedSlicePrefix, BoxedSlicePrefix, >::new(); + for (k, v) in data { if !k.iter().all(|b| b.is_ascii_graphic()) { return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); From cd0bd1f9a9b28b5a51578d78a97488d079a942ac Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Fri, 12 Jan 2024 16:12:39 +0100 Subject: [PATCH 32/51] Update sdk/src/types/block/output/feature/metadata.rs --- sdk/src/types/block/output/feature/metadata.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 8a75636b56..ea20347a79 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -122,6 +122,7 @@ fn metadata_feature_from_iter(data: impl IntoIterator, Vec)> return Err(Error::InvalidMetadataFeature("Duplicated metadata key".to_string())); }; } + Ok(MetadataFeature( res.try_into().map_err(Error::InvalidMetadataFeatureLength)?, )) From 1e996414d01cd68d197e59971f535907fdbbc904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 12 Jan 2024 20:56:24 +0100 Subject: [PATCH 33/51] Add MetadataFeature tests --- sdk/tests/types/output/feature/metadata.rs | 56 ++++++++++++++++++++++ sdk/tests/types/output/feature/mod.rs | 4 ++ sdk/tests/types/output/mod.rs | 2 + 3 files changed, 62 insertions(+) create mode 100644 sdk/tests/types/output/feature/metadata.rs create mode 100644 sdk/tests/types/output/feature/mod.rs diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs new file mode 100644 index 0000000000..b401d526af --- /dev/null +++ b/sdk/tests/types/output/feature/metadata.rs @@ -0,0 +1,56 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use iota_sdk::types::block::{output::feature::MetadataFeature, Error}; +use packable::{error::UnpackError, PackableExt}; + +#[test] +fn invalid() { + // Invalid key + assert!( + serde_json::from_str::( + r#"{"type": 2, "entries": { "space is a non graphical ASCII value": "0x42" } }"# + ) + .is_err() + ); + + // Invalid value + assert!(serde_json::from_str::(r#"{"type": 2, "entries": { "nothing": "" } }"#).is_err()); +} + +#[test] +fn serde_roundtrip() { + // Single entry + let metadata_feature: MetadataFeature = + serde_json::from_str(r#"{"type": 2, "entries": { "some_key": "0x42" } }"#).unwrap(); + let metadata_feature_ser = serde_json::to_string(&metadata_feature).unwrap(); + + assert_eq!( + serde_json::from_str::(&metadata_feature_ser).unwrap(), + metadata_feature + ); + + // Multiple entries, order doesn't matter + let metadata_feature: MetadataFeature = + serde_json::from_str(r#"{"type": 2, "entries": { "b": "0x42", "a": "0x1337", "c": "0x" } }"#).unwrap(); + let metadata_feature_ser = serde_json::to_string(&metadata_feature).unwrap(); + + assert_eq!( + serde_json::from_str::(&metadata_feature_ser).unwrap(), + metadata_feature + ); +} + +#[test] +fn unpack_invalid_order() { + assert!(matches!( + MetadataFeature::unpack_verified([3, 0, 1, 99, 0, 0, 1, 98, 0, 0, 1, 97, 0, 0], &()), + Err(UnpackError::Packable(Error::InvalidMetadataFeature(error_msg))) if &error_msg == "unordered map" + )); +} + +#[test] +fn unpack_invalid_length() { + // TODO: this should fail + MetadataFeature::unpack_verified([vec![1, 0, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()).unwrap(); +} diff --git a/sdk/tests/types/output/feature/mod.rs b/sdk/tests/types/output/feature/mod.rs new file mode 100644 index 0000000000..76cddae45b --- /dev/null +++ b/sdk/tests/types/output/feature/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod metadata; diff --git a/sdk/tests/types/output/mod.rs b/sdk/tests/types/output/mod.rs index 7c0be69755..986986ff7b 100644 --- a/sdk/tests/types/output/mod.rs +++ b/sdk/tests/types/output/mod.rs @@ -5,3 +5,5 @@ mod account; mod basic; mod foundry; mod nft; + +mod feature; From b776e784444a9a898a6bd9d5fd81943387333af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 15 Jan 2024 09:05:28 +0100 Subject: [PATCH 34/51] Allow setting foundry metadata key --- cli/src/wallet_cli/mod.rs | 7 +++++-- sdk/src/types/block/output/feature/metadata.rs | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 36863720af..fc66263459 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -93,6 +93,9 @@ pub enum WalletCommand { circulating_supply: String, /// Maximum supply of the native token to be minted, e.g. 500. maximum_supply: String, + /// Metadata key, e.g. --foundry-metadata-key data. + #[arg(long, default_value = "data")] + foundry_metadata_key: String, /// Metadata to attach to the associated foundry, e.g. --foundry-metadata-hex 0xdeadbeef. #[arg(long, group = "foundry_metadata")] foundry_metadata_hex: Option, @@ -1250,6 +1253,7 @@ pub async fn prompt_internal( WalletCommand::CreateNativeToken { circulating_supply, maximum_supply, + foundry_metadata_key, foundry_metadata_hex, foundry_metadata_file, } => { @@ -1259,8 +1263,7 @@ pub async fn prompt_internal( maximum_supply, bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file) .await? - // TODO: Let user specify key or the full metadata - .map(|d| MetadataFeature::new([(b"data".to_vec(), d)]).unwrap()), + .map(|d| MetadataFeature::new([(foundry_metadata_key.into_bytes(), d)]).unwrap()), ) .await } diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index ea20347a79..6db6db311c 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -103,7 +103,7 @@ fn metadata_feature_from_iter(data: impl IntoIterator, Vec)> BoxedSlicePrefix, BoxedSlicePrefix, >::new(); - + for (k, v) in data { if !k.iter().all(|b| b.is_ascii_graphic()) { return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); @@ -122,7 +122,7 @@ fn metadata_feature_from_iter(data: impl IntoIterator, Vec)> return Err(Error::InvalidMetadataFeature("Duplicated metadata key".to_string())); }; } - + Ok(MetadataFeature( res.try_into().map_err(Error::InvalidMetadataFeatureLength)?, )) From a4a3dc7a42c6f6ca8df7714b512d1a83274ad037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 15 Jan 2024 11:17:51 +0100 Subject: [PATCH 35/51] Manual packable impl for MetadataFeature for length verification --- sdk/Cargo.toml | 1 + .../types/block/output/feature/metadata.rs | 97 ++++++++++++++++++- sdk/tests/types/output/feature/metadata.rs | 8 +- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index fce93a496e..f72ad700df 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -135,6 +135,7 @@ fern-logger = { version = "0.5.0", default-features = false } num_cpus = { version = "1.16.0", default-features = false } once_cell = { version = "1.19.0", default-features = false } regex = { version = "1.10.2", default-features = false } +time = { version = "0.3.31", default-features = false } tokio = { version = "1.35.1", default-features = false, features = [ "macros", "rt", diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 6db6db311c..0374e7f590 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -10,7 +10,11 @@ use core::ops::{Deref, RangeInclusive}; use packable::{ bounded::{BoundedU16, BoundedU8}, + error::{UnpackError, UnpackErrorExt}, + packer::Packer, prefix::{BTreeMapPrefix, BoxedSlicePrefix}, + unpacker::Unpacker, + Packable, PackableExt, }; use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; @@ -22,11 +26,9 @@ pub(crate) type MetadataFeatureKeyLength = BoundedU8<1, { u8::MAX }>; pub(crate) type MetadataFeatureValueLength = BoundedU16<0, { u16::MAX }>; /// Defines metadata, arbitrary binary data, that will be stored in the output. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = |err| Error::InvalidMetadataFeature(err.to_string()))] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct MetadataFeature( // Binary data. - #[packable(verify_with = verify_keys_packable)] pub(crate) BTreeMapPrefix< BoxedSlicePrefix, BoxedSlicePrefix, @@ -51,6 +53,24 @@ fn verify_keys_packable( Ok(()) } +fn verify_length_packable( + map: &BTreeMapPrefix< + BoxedSlicePrefix, + BoxedSlicePrefix, + MetadataFeatureLength, + >, +) -> Result<(), Error> { + if VERIFY { + let len = map.packed_len(); + if !MetadataFeature::LENGTH_RANGE + .contains(&u16::try_from(len).map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?) + { + return Err(Error::InvalidMetadataFeature(len.to_string())); + } + } + Ok(()) +} + impl MetadataFeature { /// The [`Feature`](crate::types::block::output::Feature) kind of [`MetadataFeature`]. pub const KIND: u8 = 2; @@ -64,6 +84,7 @@ impl MetadataFeature { let metadata = Self::try_from(data)?; verify_keys_packable::(&metadata.0)?; + verify_length_packable::(&metadata.0)?; Ok(metadata) } @@ -82,6 +103,76 @@ impl StorageScore for MetadataFeature {} impl WorkScore for MetadataFeature {} +impl Packable for MetadataFeature { + type UnpackError = Error; + type UnpackVisitor = (); + + fn pack(&self, packer: &mut P) -> Result<(), P::Error> { + (self.0.len() as u16).pack(packer)?; + + for (k, v) in self.0.iter() { + k.pack(packer)?; + v.pack(packer)?; + } + + Ok(()) + } + + fn unpack( + unpacker: &mut U, + visitor: &Self::UnpackVisitor, + ) -> Result> { + let len = u16::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + + let mut map = BTreeMap::< + BoxedSlicePrefix, + BoxedSlicePrefix, + >::new(); + + for _ in 0..len { + let key = BoxedSlicePrefix::::unpack::<_, VERIFY>(unpacker, visitor) + .map_err(|_| Error::InvalidMetadataFeature("invalid key".to_string())) + .map_err(UnpackError::Packable)?; + + if let Some((last, _)) = map.last_key_value() { + match last.cmp(&key) { + core::cmp::Ordering::Equal => { + return Err(UnpackError::Packable(Error::InvalidMetadataFeature( + "duplicated key".to_string(), + ))); + } + core::cmp::Ordering::Greater => { + return Err(UnpackError::Packable(Error::InvalidMetadataFeature( + "unordered map".to_string(), + ))); + } + core::cmp::Ordering::Less => (), + } + } + + let value = BoxedSlicePrefix::::unpack::<_, VERIFY>(unpacker, visitor) + .map_err(|_| Error::InvalidMetadataFeature("invalid value".to_string())) + .map_err(UnpackError::Packable)?; + + map.insert(key, value); + } + + let r: BTreeMapPrefix< + BoxedSlicePrefix, + BoxedSlicePrefix, + MetadataFeatureLength, + > = map + .try_into() + .map_err(|_| Error::InvalidMetadataFeature("invalid metadata feature".to_string())) + .map_err(UnpackError::Packable)?; + + verify_keys_packable::(&r).map_err(UnpackError::Packable)?; + verify_length_packable::(&r).map_err(UnpackError::Packable)?; + + Ok(Self(r)) + } +} + impl TryFrom, Vec)>> for MetadataFeature { type Error = Error; diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs index b401d526af..9d72432f9e 100644 --- a/sdk/tests/types/output/feature/metadata.rs +++ b/sdk/tests/types/output/feature/metadata.rs @@ -39,6 +39,8 @@ fn serde_roundtrip() { serde_json::from_str::(&metadata_feature_ser).unwrap(), metadata_feature ); + // Unordered keys are not removed + assert_eq!(metadata_feature.data().keys().count(), 3); } #[test] @@ -51,6 +53,8 @@ fn unpack_invalid_order() { #[test] fn unpack_invalid_length() { - // TODO: this should fail - MetadataFeature::unpack_verified([vec![1, 0, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()).unwrap(); + assert!(matches!( + MetadataFeature::unpack_verified([vec![1, 0, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()), + Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "8198" + )); } From 141c05aea2eae7d1b8ad3200a2173b0bcf3fd3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 15 Jan 2024 17:19:48 +0100 Subject: [PATCH 36/51] Add MetadataBTreeMap types --- .../types/block/output/feature/metadata.rs | 52 ++++++------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 0374e7f590..08caf39002 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -25,24 +25,23 @@ pub(crate) type MetadataFeatureLength = pub(crate) type MetadataFeatureKeyLength = BoundedU8<1, { u8::MAX }>; pub(crate) type MetadataFeatureValueLength = BoundedU16<0, { u16::MAX }>; +type MetadataBTreeMapPrefix = BTreeMapPrefix< + BoxedSlicePrefix, + BoxedSlicePrefix, + MetadataFeatureLength, +>; + +type MetadataBTreeMap = + BTreeMap, BoxedSlicePrefix>; + /// Defines metadata, arbitrary binary data, that will be stored in the output. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct MetadataFeature( // Binary data. - pub(crate) BTreeMapPrefix< - BoxedSlicePrefix, - BoxedSlicePrefix, - MetadataFeatureLength, - >, + pub(crate) MetadataBTreeMapPrefix, ); -fn verify_keys_packable( - map: &BTreeMapPrefix< - BoxedSlicePrefix, - BoxedSlicePrefix, - MetadataFeatureLength, - >, -) -> Result<(), Error> { +fn verify_keys_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { if VERIFY { for key in map.keys() { if !key.iter().all(|b| b.is_ascii_graphic()) { @@ -53,13 +52,7 @@ fn verify_keys_packable( Ok(()) } -fn verify_length_packable( - map: &BTreeMapPrefix< - BoxedSlicePrefix, - BoxedSlicePrefix, - MetadataFeatureLength, - >, -) -> Result<(), Error> { +fn verify_length_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { if VERIFY { let len = map.packed_len(); if !MetadataFeature::LENGTH_RANGE @@ -91,10 +84,7 @@ impl MetadataFeature { /// Returns the data. #[inline(always)] - pub fn data( - &self, - ) -> &BTreeMap, BoxedSlicePrefix> - { + pub fn data(&self) -> &MetadataBTreeMap { self.0.deref() } } @@ -124,10 +114,7 @@ impl Packable for MetadataFeature { ) -> Result> { let len = u16::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let mut map = BTreeMap::< - BoxedSlicePrefix, - BoxedSlicePrefix, - >::new(); + let mut map = MetadataBTreeMap::new(); for _ in 0..len { let key = BoxedSlicePrefix::::unpack::<_, VERIFY>(unpacker, visitor) @@ -157,11 +144,7 @@ impl Packable for MetadataFeature { map.insert(key, value); } - let r: BTreeMapPrefix< - BoxedSlicePrefix, - BoxedSlicePrefix, - MetadataFeatureLength, - > = map + let r: MetadataBTreeMapPrefix = map .try_into() .map_err(|_| Error::InvalidMetadataFeature("invalid metadata feature".to_string())) .map_err(UnpackError::Packable)?; @@ -190,10 +173,7 @@ impl TryFrom, Vec>> for MetadataFeature { } fn metadata_feature_from_iter(data: impl IntoIterator, Vec)>) -> Result { - let mut res = BTreeMap::< - BoxedSlicePrefix, - BoxedSlicePrefix, - >::new(); + let mut res = MetadataBTreeMap::new(); for (k, v) in data { if !k.iter().all(|b| b.is_ascii_graphic()) { From 29ad64fbd1505c3e2f025669bee062ed67590aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 15 Jan 2024 20:14:10 +0100 Subject: [PATCH 37/51] Derive packable again --- .../types/block/output/feature/metadata.rs | 79 +++---------------- 1 file changed, 10 insertions(+), 69 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 08caf39002..0e40a738dd 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -10,11 +10,8 @@ use core::ops::{Deref, RangeInclusive}; use packable::{ bounded::{BoundedU16, BoundedU8}, - error::{UnpackError, UnpackErrorExt}, - packer::Packer, prefix::{BTreeMapPrefix, BoxedSlicePrefix}, - unpacker::Unpacker, - Packable, PackableExt, + PackableExt, }; use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; @@ -35,12 +32,19 @@ type MetadataBTreeMap = BTreeMap, BoxedSlicePrefix>; /// Defines metadata, arbitrary binary data, that will be stored in the output. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] +#[packable(unpack_error = Error, with = |err| Error::InvalidMetadataFeature(err.to_string()))] pub struct MetadataFeature( // Binary data. - pub(crate) MetadataBTreeMapPrefix, + #[packable(verify_with = verify_packable)] pub(crate) MetadataBTreeMapPrefix, ); +fn verify_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { + verify_keys_packable::(map)?; + verify_length_packable::(map)?; + Ok(()) +} + fn verify_keys_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { if VERIFY { for key in map.keys() { @@ -93,69 +97,6 @@ impl StorageScore for MetadataFeature {} impl WorkScore for MetadataFeature {} -impl Packable for MetadataFeature { - type UnpackError = Error; - type UnpackVisitor = (); - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - (self.0.len() as u16).pack(packer)?; - - for (k, v) in self.0.iter() { - k.pack(packer)?; - v.pack(packer)?; - } - - Ok(()) - } - - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - let len = u16::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - let mut map = MetadataBTreeMap::new(); - - for _ in 0..len { - let key = BoxedSlicePrefix::::unpack::<_, VERIFY>(unpacker, visitor) - .map_err(|_| Error::InvalidMetadataFeature("invalid key".to_string())) - .map_err(UnpackError::Packable)?; - - if let Some((last, _)) = map.last_key_value() { - match last.cmp(&key) { - core::cmp::Ordering::Equal => { - return Err(UnpackError::Packable(Error::InvalidMetadataFeature( - "duplicated key".to_string(), - ))); - } - core::cmp::Ordering::Greater => { - return Err(UnpackError::Packable(Error::InvalidMetadataFeature( - "unordered map".to_string(), - ))); - } - core::cmp::Ordering::Less => (), - } - } - - let value = BoxedSlicePrefix::::unpack::<_, VERIFY>(unpacker, visitor) - .map_err(|_| Error::InvalidMetadataFeature("invalid value".to_string())) - .map_err(UnpackError::Packable)?; - - map.insert(key, value); - } - - let r: MetadataBTreeMapPrefix = map - .try_into() - .map_err(|_| Error::InvalidMetadataFeature("invalid metadata feature".to_string())) - .map_err(UnpackError::Packable)?; - - verify_keys_packable::(&r).map_err(UnpackError::Packable)?; - verify_length_packable::(&r).map_err(UnpackError::Packable)?; - - Ok(Self(r)) - } -} - impl TryFrom, Vec)>> for MetadataFeature { type Error = Error; From 237d73804522e8f0c367d4a2dac003e67cbe8d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 15 Jan 2024 20:16:42 +0100 Subject: [PATCH 38/51] Add +1 for type byte --- sdk/src/types/block/output/feature/metadata.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 0e40a738dd..1d4b6003e0 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -58,7 +58,8 @@ fn verify_keys_packable(map: &MetadataBTreeMapPrefix) -> Res fn verify_length_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { if VERIFY { - let len = map.packed_len(); + // +1 for the feature type + let len = map.packed_len() + 1; if !MetadataFeature::LENGTH_RANGE .contains(&u16::try_from(len).map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?) { From d2128ec33fe8f15e16812c224a45338d543337db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 15 Jan 2024 20:25:17 +0100 Subject: [PATCH 39/51] Update tests --- sdk/src/types/block/rand/output/feature.rs | 2 +- sdk/tests/types/output/feature/metadata.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/sdk/src/types/block/rand/output/feature.rs b/sdk/src/types/block/rand/output/feature.rs index 4b30ba63a7..54a3c46dfe 100644 --- a/sdk/src/types/block/rand/output/feature.rs +++ b/sdk/src/types/block/rand/output/feature.rs @@ -39,7 +39,7 @@ pub fn rand_metadata_feature() -> MetadataFeature { let mut total_size = 0; for _ in 0..10 { - if total_size >= *MetadataFeature::LENGTH_RANGE.end() as usize - u8::MAX as usize { + if total_size >= (*MetadataFeature::LENGTH_RANGE.end() - 1) as usize - u8::MAX as usize { break; } // Key length diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs index 9d72432f9e..bdfad03447 100644 --- a/sdk/tests/types/output/feature/metadata.rs +++ b/sdk/tests/types/output/feature/metadata.rs @@ -7,12 +7,10 @@ use packable::{error::UnpackError, PackableExt}; #[test] fn invalid() { // Invalid key - assert!( - serde_json::from_str::( - r#"{"type": 2, "entries": { "space is a non graphical ASCII value": "0x42" } }"# - ) - .is_err() - ); + assert!(serde_json::from_str::( + r#"{"type": 2, "entries": { "space is a non graphical ASCII value": "0x42" } }"# + ) + .is_err()); // Invalid value assert!(serde_json::from_str::(r#"{"type": 2, "entries": { "nothing": "" } }"#).is_err()); @@ -55,6 +53,6 @@ fn unpack_invalid_order() { fn unpack_invalid_length() { assert!(matches!( MetadataFeature::unpack_verified([vec![1, 0, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()), - Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "8198" + Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "8199" )); } From 3964d1c8c2f178b2d86f3f37e50afd7ffe9d4241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 15 Jan 2024 20:26:50 +0100 Subject: [PATCH 40/51] fmt --- sdk/tests/types/output/feature/metadata.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs index bdfad03447..c28c958eee 100644 --- a/sdk/tests/types/output/feature/metadata.rs +++ b/sdk/tests/types/output/feature/metadata.rs @@ -7,10 +7,12 @@ use packable::{error::UnpackError, PackableExt}; #[test] fn invalid() { // Invalid key - assert!(serde_json::from_str::( - r#"{"type": 2, "entries": { "space is a non graphical ASCII value": "0x42" } }"# - ) - .is_err()); + assert!( + serde_json::from_str::( + r#"{"type": 2, "entries": { "space is a non graphical ASCII value": "0x42" } }"# + ) + .is_err() + ); // Invalid value assert!(serde_json::from_str::(r#"{"type": 2, "entries": { "nothing": "" } }"#).is_err()); From 1a5221340b20c2c3a575bb865edcb181369ee36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 16 Jan 2024 12:40:57 +0100 Subject: [PATCH 41/51] Improve error message --- sdk/src/types/block/output/feature/metadata.rs | 4 +++- sdk/tests/types/output/feature/metadata.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 1d4b6003e0..bc8a3e7a35 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -63,7 +63,9 @@ fn verify_length_packable(map: &MetadataBTreeMapPrefix) -> R if !MetadataFeature::LENGTH_RANGE .contains(&u16::try_from(len).map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?) { - return Err(Error::InvalidMetadataFeature(len.to_string())); + return Err(Error::InvalidMetadataFeature(format!( + "Out of bounds byte length: {len}" + ))); } } Ok(()) diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs index c28c958eee..b554ca16ae 100644 --- a/sdk/tests/types/output/feature/metadata.rs +++ b/sdk/tests/types/output/feature/metadata.rs @@ -55,6 +55,6 @@ fn unpack_invalid_order() { fn unpack_invalid_length() { assert!(matches!( MetadataFeature::unpack_verified([vec![1, 0, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()), - Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "8199" + Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "Out of bounds byte length: 8199" )); } From 9d80a70d167ee0be926aa1697ec2aaab036008bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 16 Jan 2024 13:54:24 +0100 Subject: [PATCH 42/51] Add import --- sdk/src/types/block/output/feature/metadata.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index bc8a3e7a35..e1280dfe22 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -3,6 +3,7 @@ use alloc::{ collections::BTreeMap, + format, string::{String, ToString}, vec::Vec, }; From fc5931de714d8094a39fcd1a06ac274a7af867d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 16 Jan 2024 19:16:23 +0100 Subject: [PATCH 43/51] Use CounterUnpacker, fix rand_metadata_feature(), fix entry count type, fix rand_mana_allotment() --- sdk/src/types/block/error.rs | 8 +-- .../types/block/output/feature/metadata.rs | 65 +++++++++++++++---- sdk/src/types/block/output/feature/mod.rs | 2 +- sdk/src/types/block/output/mod.rs | 2 +- sdk/src/types/block/rand/mana.rs | 2 +- sdk/src/types/block/rand/output/feature.rs | 51 ++++++++++----- sdk/tests/types/output/feature/metadata.rs | 6 +- 7 files changed, 97 insertions(+), 39 deletions(-) diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index aa6bcba79d..751270f18a 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -21,7 +21,7 @@ use crate::types::block::{ output::{ feature::{BlockIssuerKeyCount, FeatureCount}, unlock_condition::UnlockConditionCount, - AccountId, AnchorId, ChainId, MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength, + AccountId, AnchorId, ChainId, MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength, NativeTokenCount, NftId, OutputIndex, TagFeatureLength, }, payload::{ @@ -110,7 +110,7 @@ pub enum Error { InvalidBlockLength(usize), InvalidManaValue(u64), InvalidMetadataFeature(String), - InvalidMetadataFeatureLength(>::Error), + InvalidMetadataFeatureEntryCount(>::Error), InvalidMetadataFeatureKeyLength(>::Error), InvalidMetadataFeatureValueLength(>::Error), InvalidNativeTokenCount(>::Error), @@ -316,8 +316,8 @@ impl fmt::Display for Error { Self::InvalidMetadataFeature(e) => { write!(f, "invalid metadata feature: {e}") } - Self::InvalidMetadataFeatureLength(length) => { - write!(f, "invalid metadata feature length: {length}") + Self::InvalidMetadataFeatureEntryCount(count) => { + write!(f, "invalid metadata feature entry count: {count}") } Self::InvalidMetadataFeatureKeyLength(length) => { write!(f, "invalid metadata feature key length: {length}") diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index e1280dfe22..f58c56c012 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -11,39 +11,78 @@ use core::ops::{Deref, RangeInclusive}; use packable::{ bounded::{BoundedU16, BoundedU8}, + error::{UnpackError, UnpackErrorExt}, + packer::Packer, prefix::{BTreeMapPrefix, BoxedSlicePrefix}, - PackableExt, + unpacker::{CounterUnpacker, Unpacker}, + Packable, PackableExt, }; use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; -pub(crate) type MetadataFeatureLength = - BoundedU16<{ *MetadataFeature::LENGTH_RANGE.start() }, { *MetadataFeature::LENGTH_RANGE.end() }>; - +pub(crate) type MetadataFeatureEntryCount = BoundedU8<1, { u8::MAX }>; pub(crate) type MetadataFeatureKeyLength = BoundedU8<1, { u8::MAX }>; pub(crate) type MetadataFeatureValueLength = BoundedU16<0, { u16::MAX }>; type MetadataBTreeMapPrefix = BTreeMapPrefix< BoxedSlicePrefix, BoxedSlicePrefix, - MetadataFeatureLength, + MetadataFeatureEntryCount, >; type MetadataBTreeMap = BTreeMap, BoxedSlicePrefix>; /// Defines metadata, arbitrary binary data, that will be stored in the output. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = |err| Error::InvalidMetadataFeature(err.to_string()))] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct MetadataFeature( // Binary data. - #[packable(verify_with = verify_packable)] pub(crate) MetadataBTreeMapPrefix, + pub(crate) MetadataBTreeMapPrefix, ); -fn verify_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { - verify_keys_packable::(map)?; - verify_length_packable::(map)?; - Ok(()) +impl Packable for MetadataFeature { + type UnpackError = Error; + type UnpackVisitor = (); + + fn pack(&self, packer: &mut P) -> Result<(), P::Error> { + self.0.pack(packer)?; + + Ok(()) + } + + fn unpack( + unpacker: &mut U, + visitor: &Self::UnpackVisitor, + ) -> Result> { + let mut unpacker = CounterUnpacker::new(unpacker); + let start_opt = unpacker.read_bytes(); + + let map = MetadataBTreeMapPrefix::unpack::<_, VERIFY>(&mut unpacker, visitor) + .map_packable_err(|e| Error::InvalidMetadataFeature(e.to_string()))?; + + verify_keys_packable::(&map).map_err(UnpackError::Packable)?; + + let packed_len = if let (Some(start), Some(end)) = (start_opt, unpacker.read_bytes()) { + end - start + } else { + map.packed_len() + }; + + let packed_len = u16::try_from(packed_len).map_err(|_| { + UnpackError::Packable(Error::InvalidMetadataFeature(format!( + "Out of bounds byte length: {}", + packed_len + 1 + ))) + })? + 1; // +1 for the type byte + + if !Self::LENGTH_RANGE.contains(&(packed_len)) { + return Err(UnpackError::Packable(Error::InvalidMetadataFeature(format!( + "Out of bounds byte length: {packed_len}" + )))); + } + + Ok(Self(map)) + } } fn verify_keys_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { @@ -140,7 +179,7 @@ fn metadata_feature_from_iter(data: impl IntoIterator, Vec)> } Ok(MetadataFeature( - res.try_into().map_err(Error::InvalidMetadataFeatureLength)?, + res.try_into().map_err(Error::InvalidMetadataFeatureEntryCount)?, )) } diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index d07c2f4618..19faa7af65 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -22,7 +22,7 @@ pub use self::metadata::irc_27::{Attribute, Irc27Metadata}; pub use self::metadata::irc_30::Irc30Metadata; pub(crate) use self::{ block_issuer::BlockIssuerKeyCount, - metadata::{MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength}, + metadata::{MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength}, tag::TagFeatureLength, }; pub use self::{ diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 9a5315d02f..42e7fb9276 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -47,7 +47,7 @@ pub use self::{ unlock_condition::{UnlockCondition, UnlockConditions}, }; pub(crate) use self::{ - feature::{MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength, TagFeatureLength}, + feature::{MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength, TagFeatureLength}, native_token::NativeTokenCount, output_id::OutputIndex, unlock_condition::AddressUnlockCondition, diff --git a/sdk/src/types/block/rand/mana.rs b/sdk/src/types/block/rand/mana.rs index 2476a75cdf..5e2332a1b6 100644 --- a/sdk/src/types/block/rand/mana.rs +++ b/sdk/src/types/block/rand/mana.rs @@ -11,7 +11,7 @@ use crate::types::block::{ pub fn rand_mana_allotment(params: &ProtocolParameters) -> ManaAllotment { ManaAllotment::new( rand_account_id(), - rand_number_range(0..params.mana_parameters().max_mana()), + rand_number_range(1..params.mana_parameters().max_mana()), ) .unwrap() } diff --git a/sdk/src/types/block/rand/output/feature.rs b/sdk/src/types/block/rand/output/feature.rs index 54a3c46dfe..4bc4a836ea 100644 --- a/sdk/src/types/block/rand/output/feature.rs +++ b/sdk/src/types/block/rand/output/feature.rs @@ -36,32 +36,51 @@ pub fn rand_issuer_feature() -> IssuerFeature { /// Generates a random [`MetadataFeature`]. pub fn rand_metadata_feature() -> MetadataFeature { let mut map = BTreeMap::new(); - let mut total_size = 0; + // Starting at 2 for type + entries count bytes + let mut total_size = 2; + let max_size = *MetadataFeature::LENGTH_RANGE.end() as usize; + let key_prefix_length = 1; + let value_prefix_length = 2; for _ in 0..10 { - if total_size >= (*MetadataFeature::LENGTH_RANGE.end() - 1) as usize - u8::MAX as usize { + // +1 since min key size is 1 + if total_size > (max_size - (key_prefix_length + value_prefix_length + 1)) as usize { break; } + // Key length - total_size += 1; - let key = Alphanumeric.sample_string( - &mut rand::thread_rng(), - rand_number_range(Range { - start: 1, - end: u8::MAX.into(), - }), - ); + total_size += key_prefix_length; + let max_val = if max_size - total_size - value_prefix_length < u8::MAX as usize { + max_size - total_size - value_prefix_length + } else { + u8::MAX.into() + }; + + let key = if max_val == 1 { + "a".to_string() + } else { + Alphanumeric.sample_string( + &mut rand::thread_rng(), + rand_number_range(Range { start: 1, end: max_val }), + ) + }; total_size += key.as_bytes().len(); - if total_size >= *MetadataFeature::LENGTH_RANGE.end() as usize - 2 { + if total_size > max_size - value_prefix_length { + // println!("breaking before adding more"); break; } + // Value length - total_size += 2; - let bytes = rand_bytes(rand_number_range(Range { - start: 0, - end: *MetadataFeature::LENGTH_RANGE.end() as usize - total_size, - }) as usize); + total_size += value_prefix_length; + let bytes = if max_size - total_size == 0 { + vec![] + } else { + rand_bytes(rand_number_range(Range { + start: 0, + end: max_size - total_size, + })) + }; total_size += bytes.len(); map.insert(key.into(), bytes); diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs index b554ca16ae..75fde01ce6 100644 --- a/sdk/tests/types/output/feature/metadata.rs +++ b/sdk/tests/types/output/feature/metadata.rs @@ -46,7 +46,7 @@ fn serde_roundtrip() { #[test] fn unpack_invalid_order() { assert!(matches!( - MetadataFeature::unpack_verified([3, 0, 1, 99, 0, 0, 1, 98, 0, 0, 1, 97, 0, 0], &()), + MetadataFeature::unpack_verified([3, 1, 99, 0, 0, 1, 98, 0, 0, 1, 97, 0, 0], &()), Err(UnpackError::Packable(Error::InvalidMetadataFeature(error_msg))) if &error_msg == "unordered map" )); } @@ -54,7 +54,7 @@ fn unpack_invalid_order() { #[test] fn unpack_invalid_length() { assert!(matches!( - MetadataFeature::unpack_verified([vec![1, 0, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()), - Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "Out of bounds byte length: 8199" + MetadataFeature::unpack_verified([vec![1, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()), + Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "Out of bounds byte length: 8198" )); } From 8be46b772b0b32a58e868ae6706a35c3cbafea28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 16 Jan 2024 19:30:01 +0100 Subject: [PATCH 44/51] Align key --- sdk/examples/client/output/build_nft_output.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/examples/client/output/build_nft_output.rs b/sdk/examples/client/output/build_nft_output.rs index dd3a4f9bc5..81d36739f6 100644 --- a/sdk/examples/client/output/build_nft_output.rs +++ b/sdk/examples/client/output/build_nft_output.rs @@ -70,7 +70,7 @@ async fn main() -> Result<()> { .add_feature(TagFeature::new(TAG)?) .add_immutable_feature(IssuerFeature::new(address)) .add_immutable_feature(MetadataFeature::new([( - "IRC27".as_bytes().to_vec(), + "irc-27".as_bytes().to_vec(), tip_27_immutable_metadata.as_bytes().to_vec(), )])?) .finish_output()?; From b736da1cff3c8933bdafaf784b58f9c5150cfab8 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Tue, 16 Jan 2024 14:06:03 -0500 Subject: [PATCH 45/51] improvements --- cli/src/wallet_cli/mod.rs | 10 +- .../client/output/build_account_output.rs | 4 +- .../client/output/build_basic_output.rs | 4 +- .../client/output/build_nft_output.rs | 18 +- .../nft_collection/00_mint_issuer_nft.rs | 2 +- sdk/examples/how_tos/nfts/mint_nft.rs | 7 +- sdk/examples/how_tos/outputs/features.rs | 4 +- sdk/src/types/block/error.rs | 8 +- .../types/block/output/feature/metadata.rs | 221 ++++++++++++------ sdk/src/types/block/output/feature/mod.rs | 2 +- sdk/src/types/block/output/mod.rs | 2 +- sdk/src/types/block/rand/output/feature.rs | 6 +- .../transaction/high_level/create_account.rs | 4 +- sdk/tests/types/output/feature/metadata.rs | 2 +- sdk/tests/wallet/burn_outputs.rs | 8 +- sdk/tests/wallet/native_tokens.rs | 2 +- sdk/tests/wallet/output_preparation.rs | 31 +-- 17 files changed, 208 insertions(+), 127 deletions(-) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 3ec28c12a8..1bb9c3317a 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -796,13 +796,11 @@ pub async fn mint_nft_command( .with_issuer(issuer); if let Some(metadata) = metadata { - nft_options = nft_options.with_metadata(MetadataFeature::new([(metadata_key.into_bytes(), metadata)])?); + nft_options = nft_options.with_metadata(MetadataFeature::new([(metadata_key, metadata)])?); } if let Some(immutable_metadata) = immutable_metadata { - nft_options = nft_options.with_immutable_metadata(MetadataFeature::new([( - immutable_metadata_key.into_bytes(), - immutable_metadata, - )])?); + nft_options = + nft_options.with_immutable_metadata(MetadataFeature::new([(immutable_metadata_key, immutable_metadata)])?); } let transaction = wallet.mint_nfts([nft_options], None).await?; @@ -1267,7 +1265,7 @@ pub async fn prompt_internal( maximum_supply, bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file) .await? - .map(|d| MetadataFeature::new([(foundry_metadata_key.into_bytes(), d)]).unwrap()), + .map(|d| MetadataFeature::new([(foundry_metadata_key, d)]).unwrap()), ) .await } diff --git a/sdk/examples/client/output/build_account_output.rs b/sdk/examples/client/output/build_account_output.rs index eb5f5f8661..21e48242d2 100644 --- a/sdk/examples/client/output/build_account_output.rs +++ b/sdk/examples/client/output/build_account_output.rs @@ -49,9 +49,9 @@ async fn main() -> Result<()> { // Account id needs to be null the first time let account_output = AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .add_feature(SenderFeature::new(address.clone())) - .add_feature(MetadataFeature::new([(b"data".to_vec(), metadata.to_vec())])?) + .add_feature(MetadataFeature::build().with_key_value("data", metadata).finish()?) .add_immutable_feature(IssuerFeature::new(address.clone())) - .add_immutable_feature(MetadataFeature::new([(b"data".to_vec(), metadata.to_vec())])?) + .add_immutable_feature(MetadataFeature::build().with_key_value("data", metadata).finish()?) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output()?; diff --git a/sdk/examples/client/output/build_basic_output.rs b/sdk/examples/client/output/build_basic_output.rs index e333273efb..fa4f9f13a0 100644 --- a/sdk/examples/client/output/build_basic_output.rs +++ b/sdk/examples/client/output/build_basic_output.rs @@ -23,7 +23,7 @@ use iota_sdk::{ }, }; -const KEY: &'static [u8; 5] = b"Hello"; +const KEY: &'static str = "Hello"; const METADATA: &'static [u8; 6] = b"World!"; #[tokio::main] @@ -45,7 +45,7 @@ async fn main() -> Result<()> { // with metadata feature block basic_output_builder .clone() - .add_feature(MetadataFeature::new([(KEY.to_vec(), METADATA.to_vec())])?) + .add_feature(MetadataFeature::new([(KEY.to_owned(), METADATA.to_vec())])?) .finish_output()?, // with storage deposit return basic_output_builder diff --git a/sdk/examples/client/output/build_nft_output.rs b/sdk/examples/client/output/build_nft_output.rs index dd3a4f9bc5..7e53b17a80 100644 --- a/sdk/examples/client/output/build_nft_output.rs +++ b/sdk/examples/client/output/build_nft_output.rs @@ -63,16 +63,18 @@ async fn main() -> Result<()> { let nft_output = NftOutputBuilder::new_with_minimum_amount(storage_score_params, NftId::null()) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) .add_feature(SenderFeature::new(address.clone())) - .add_feature(MetadataFeature::new([( - "mutable".as_bytes().to_vec(), - MUTABLE_METADATA.as_bytes().to_vec(), - )])?) + .add_feature( + MetadataFeature::build() + .with_key_value("mutable", MUTABLE_METADATA.as_bytes()) + .finish()?, + ) .add_feature(TagFeature::new(TAG)?) .add_immutable_feature(IssuerFeature::new(address)) - .add_immutable_feature(MetadataFeature::new([( - "IRC27".as_bytes().to_vec(), - tip_27_immutable_metadata.as_bytes().to_vec(), - )])?) + .add_immutable_feature( + MetadataFeature::build() + .with_key_value("IRC27", tip_27_immutable_metadata.as_bytes()) + .finish()?, + ) .finish_output()?; println!("{nft_output:#?}"); diff --git a/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs b/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs index fe3fac5275..0fd929bf93 100644 --- a/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs +++ b/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs @@ -49,7 +49,7 @@ async fn main() -> Result<()> { println!("Sending NFT minting transaction..."); let nft_mint_params = [MintNftParams::new().with_immutable_metadata( MetadataFeature::new([( - b"data".to_vec(), + "data".to_owned(), b"This NFT will be the issuer from the awesome NFT collection".to_vec(), )]) .unwrap(), diff --git a/sdk/examples/how_tos/nfts/mint_nft.rs b/sdk/examples/how_tos/nfts/mint_nft.rs index 7c86bd37de..7af6673613 100644 --- a/sdk/examples/how_tos/nfts/mint_nft.rs +++ b/sdk/examples/how_tos/nfts/mint_nft.rs @@ -68,7 +68,12 @@ async fn main() -> Result<()> { let nft_params = [MintNftParams::new() .try_with_address(NFT1_OWNER_ADDRESS)? .try_with_sender(sender_address.clone())? - .with_metadata(MetadataFeature::new([(b"data".to_vec(), NFT1_METADATA.as_bytes().to_vec())]).unwrap()) + .with_metadata( + MetadataFeature::build() + .with_key_value("data", NFT1_METADATA.as_bytes()) + .finish() + .unwrap(), + ) .with_tag(NFT1_TAG.as_bytes().to_vec()) .try_with_issuer(sender_address.clone())? .with_immutable_metadata(MetadataFeature::try_from(metadata).unwrap())]; diff --git a/sdk/examples/how_tos/outputs/features.rs b/sdk/examples/how_tos/outputs/features.rs index 0d745b699c..8289b4cfc0 100644 --- a/sdk/examples/how_tos/outputs/features.rs +++ b/sdk/examples/how_tos/outputs/features.rs @@ -56,12 +56,12 @@ async fn main() -> Result<()> { // with metadata feature block nft_output_builder .clone() - .add_feature(MetadataFeature::new([(b"Hello".to_vec(), b"World!".to_vec())])?) + .add_feature(MetadataFeature::new([("Hello".to_owned(), b"World!".to_vec())])?) .finish_output()?, // with immutable metadata feature block nft_output_builder .clone() - .add_immutable_feature(MetadataFeature::new([(b"Hello".to_vec(), b"World!".to_vec())])?) + .add_immutable_feature(MetadataFeature::new([("Hello".to_owned(), b"World!".to_vec())])?) .finish_output()?, // with tag feature nft_output_builder diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index aa6bcba79d..0b31259dcb 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -5,7 +5,7 @@ use alloc::{ string::{FromUtf8Error, String}, vec::Vec, }; -use core::{convert::Infallible, fmt}; +use core::{convert::Infallible, fmt, num::TryFromIntError}; use bech32::primitives::hrp::Error as Bech32HrpError; use crypto::Error as CryptoError; @@ -21,8 +21,8 @@ use crate::types::block::{ output::{ feature::{BlockIssuerKeyCount, FeatureCount}, unlock_condition::UnlockConditionCount, - AccountId, AnchorId, ChainId, MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength, - NativeTokenCount, NftId, OutputIndex, TagFeatureLength, + AccountId, AnchorId, ChainId, MetadataFeatureKeyLength, MetadataFeatureValueLength, NativeTokenCount, NftId, + OutputIndex, TagFeatureLength, }, payload::{ tagged_data::{TagLength, TaggedDataLength}, @@ -110,7 +110,7 @@ pub enum Error { InvalidBlockLength(usize), InvalidManaValue(u64), InvalidMetadataFeature(String), - InvalidMetadataFeatureLength(>::Error), + InvalidMetadataFeatureLength(TryFromIntError), InvalidMetadataFeatureKeyLength(>::Error), InvalidMetadataFeatureValueLength(>::Error), InvalidNativeTokenCount(>::Error), diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index e1280dfe22..17a5ca4778 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -7,49 +7,45 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use core::ops::{Deref, RangeInclusive}; +use core::ops::RangeInclusive; use packable::{ bounded::{BoundedU16, BoundedU8}, + error::{UnpackError, UnpackErrorExt}, + packer::Packer, prefix::{BTreeMapPrefix, BoxedSlicePrefix}, - PackableExt, + unpacker::{CounterUnpacker, Unpacker}, + Packable, PackableExt, }; use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; -pub(crate) type MetadataFeatureLength = - BoundedU16<{ *MetadataFeature::LENGTH_RANGE.start() }, { *MetadataFeature::LENGTH_RANGE.end() }>; - pub(crate) type MetadataFeatureKeyLength = BoundedU8<1, { u8::MAX }>; pub(crate) type MetadataFeatureValueLength = BoundedU16<0, { u16::MAX }>; type MetadataBTreeMapPrefix = BTreeMapPrefix< BoxedSlicePrefix, BoxedSlicePrefix, - MetadataFeatureLength, + u16, >; type MetadataBTreeMap = BTreeMap, BoxedSlicePrefix>; /// Defines metadata, arbitrary binary data, that will be stored in the output. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = |err| Error::InvalidMetadataFeature(err.to_string()))] -pub struct MetadataFeature( - // Binary data. - #[packable(verify_with = verify_packable)] pub(crate) MetadataBTreeMapPrefix, -); - -fn verify_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { - verify_keys_packable::(map)?; - verify_length_packable::(map)?; +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct MetadataFeature(MetadataBTreeMapPrefix); + +fn verify_packable(feature: &MetadataFeature) -> Result<(), Error> { + verify_keys_packable::(feature)?; + verify_byte_length_packable::(feature.packed_len())?; Ok(()) } -fn verify_keys_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { +fn verify_keys_packable(feature: &MetadataFeature) -> Result<(), Error> { if VERIFY { - for key in map.keys() { - if !key.iter().all(|b| b.is_ascii_graphic()) { + for key in feature.0.keys() { + if !key.iter().all(|c| c.is_ascii_graphic()) { return Err(Error::NonGraphicAsciiMetadataKey(key.to_vec())); } } @@ -57,11 +53,10 @@ fn verify_keys_packable(map: &MetadataBTreeMapPrefix) -> Res Ok(()) } -fn verify_length_packable(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { +fn verify_byte_length_packable(len: usize) -> Result<(), Error> { if VERIFY { // +1 for the feature type - let len = map.packed_len() + 1; - if !MetadataFeature::LENGTH_RANGE + if !MetadataFeature::BYTE_LENGTH_RANGE .contains(&u16::try_from(len).map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?) { return Err(Error::InvalidMetadataFeature(format!( @@ -75,25 +70,109 @@ fn verify_length_packable(map: &MetadataBTreeMapPrefix) -> R impl MetadataFeature { /// The [`Feature`](crate::types::block::output::Feature) kind of [`MetadataFeature`]. pub const KIND: u8 = 2; - /// Valid lengths for a [`MetadataFeature`]. - pub const LENGTH_RANGE: RangeInclusive = 1..=8192; + /// Valid byte lengths for a [`MetadataFeature`]. + pub const BYTE_LENGTH_RANGE: RangeInclusive = 1..=8192; /// Creates a new [`MetadataFeature`]. #[inline(always)] - pub fn new(data: impl IntoIterator, Vec)>) -> Result { - let data: BTreeMap, Vec> = data.into_iter().collect(); - let metadata = Self::try_from(data)?; + pub fn new(data: impl IntoIterator)>) -> Result { + let mut builder = Self::build(); + for (k, v) in data { + builder.insert(k, v); + } + builder.finish() + } - verify_keys_packable::(&metadata.0)?; - verify_length_packable::(&metadata.0)?; + pub fn build() -> MetadataFeatureMap { + Default::default() + } - Ok(metadata) + pub fn to_map(&self) -> MetadataFeatureMap { + // Unsafe: acceptable because of type level checking, i.e. keys must be ASCII + unsafe { + MetadataFeatureMap( + self.0 + .iter() + .map(|(k, v)| { + ( + String::from_utf8_unchecked(k.as_ref().to_owned()), + v.as_ref().to_owned(), + ) + }) + .collect(), + ) + } } - /// Returns the data. + /// Returns the data for a given key. #[inline(always)] - pub fn data(&self) -> &MetadataBTreeMap { - self.0.deref() + pub fn get(&self, key: &str) -> Option<&[u8]> { + BoxedSlicePrefix::::try_from(key.as_bytes().to_vec().into_boxed_slice()) + .ok() + .and_then(|key| self.0.get(&key)) + .map(|v| v.as_ref()) + } +} + +impl core::ops::Deref for MetadataFeature { + type Target = MetadataBTreeMap; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +/// A map of metadata feature keys to values. This type is not guaranteed to be valid. +#[derive(Clone, Debug, Default)] +pub struct MetadataFeatureMap(BTreeMap>); + +impl MetadataFeatureMap { + /// Creates a new [`MetadataFeatureBuilder`]. + #[inline(always)] + pub fn new() -> Self { + Self(Default::default()) + } + + pub fn with_key_value(mut self, key: &str, value: impl Into>) -> Self { + self.insert(key.to_owned(), value.into()); + self + } + + pub fn finish(self) -> Result { + let res = MetadataFeature( + MetadataBTreeMapPrefix::try_from( + self.0 + .iter() + .map(|(k, v)| { + Ok(( + BoxedSlicePrefix::::try_from( + k.as_bytes().to_vec().into_boxed_slice(), + ) + .map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + BoxedSlicePrefix::::try_from(v.clone().into_boxed_slice()) + .map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + )) + }) + .collect::>()?, + ) + .map_err(Error::InvalidMetadataFeatureLength)?, + ); + verify_packable::(&res)?; + Ok(res) + } +} + +impl core::ops::Deref for MetadataFeatureMap { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for MetadataFeatureMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } @@ -101,47 +180,53 @@ impl StorageScore for MetadataFeature {} impl WorkScore for MetadataFeature {} -impl TryFrom, Vec)>> for MetadataFeature { - type Error = Error; +impl Packable for MetadataFeature { + type UnpackError = Error; + type UnpackVisitor = (); + + fn pack(&self, packer: &mut P) -> Result<(), P::Error> { + self.0.pack(packer) + } + + fn unpack( + unpacker: &mut U, + visitor: &Self::UnpackVisitor, + ) -> Result> { + let mut unpacker = CounterUnpacker::new(unpacker); + let res = MetadataFeature( + MetadataBTreeMapPrefix::unpack::<_, VERIFY>(&mut unpacker, visitor) + .map_packable_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + ); + + verify_keys_packable::(&res).map_err(UnpackError::Packable)?; + verify_byte_length_packable::(unpacker.counter()).map_err(UnpackError::Packable)?; - fn try_from(data: Vec<(Vec, Vec)>) -> Result { - metadata_feature_from_iter(data) + Ok(res) } } -impl TryFrom, Vec>> for MetadataFeature { +impl TryFrom)>> for MetadataFeature { type Error = Error; - fn try_from(data: BTreeMap, Vec>) -> Result { - metadata_feature_from_iter(data) + fn try_from(data: Vec<(String, Vec)>) -> Result { + let mut builder = Self::build(); + for (k, v) in data { + builder.insert(k, v); + } + builder.finish() } } -fn metadata_feature_from_iter(data: impl IntoIterator, Vec)>) -> Result { - let mut res = MetadataBTreeMap::new(); - - for (k, v) in data { - if !k.iter().all(|b| b.is_ascii_graphic()) { - return Err(Error::NonGraphicAsciiMetadataKey(k.to_vec())); - } - if res - .insert( - k.into_boxed_slice() - .try_into() - .map_err(Error::InvalidMetadataFeatureKeyLength)?, - v.into_boxed_slice() - .try_into() - .map_err(Error::InvalidMetadataFeatureValueLength)?, - ) - .is_some() - { - return Err(Error::InvalidMetadataFeature("Duplicated metadata key".to_string())); - }; - } +impl TryFrom>> for MetadataFeature { + type Error = Error; - Ok(MetadataFeature( - res.try_into().map_err(Error::InvalidMetadataFeatureLength)?, - )) + fn try_from(data: BTreeMap>) -> Result { + let mut builder = Self::build(); + for (k, v) in data { + builder.insert(k, v); + } + builder.finish() + } } impl core::fmt::Display for MetadataFeature { @@ -285,7 +370,7 @@ pub(crate) mod irc_27 { fn try_from(value: Irc27Metadata) -> Result { // TODO: is this hardcoded key correct or should users provide it? - Self::new([("irc-27".as_bytes().to_vec(), value.to_bytes())]) + Self::build().with_key_value("irc-27", value).finish() } } @@ -461,7 +546,7 @@ pub(crate) mod irc_30 { fn try_from(value: Irc30Metadata) -> Result { // TODO: is this hardcoded key correct or should users provide it? - Self::new([("irc-30".as_bytes().to_vec(), value.to_bytes())]) + Self::build().with_key_value("irc-30", value).finish() } } @@ -545,8 +630,8 @@ pub(crate) mod dto { Self::try_from( map.into_iter() - .map(|(key, value)| Ok((key.into_bytes(), prefix_hex::decode::>(value)?))) - .collect::, Vec>, prefix_hex::Error>>() + .map(|(key, value)| Ok((key, prefix_hex::decode::>(value)?))) + .collect::>, prefix_hex::Error>>() .map_err(de::Error::custom)?, ) .map_err(de::Error::custom)? diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index d07c2f4618..5f16c77a33 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -22,7 +22,7 @@ pub use self::metadata::irc_27::{Attribute, Irc27Metadata}; pub use self::metadata::irc_30::Irc30Metadata; pub(crate) use self::{ block_issuer::BlockIssuerKeyCount, - metadata::{MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength}, + metadata::{MetadataFeatureKeyLength, MetadataFeatureValueLength}, tag::TagFeatureLength, }; pub use self::{ diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 9a5315d02f..0d2f16cec3 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -47,7 +47,7 @@ pub use self::{ unlock_condition::{UnlockCondition, UnlockConditions}, }; pub(crate) use self::{ - feature::{MetadataFeatureKeyLength, MetadataFeatureLength, MetadataFeatureValueLength, TagFeatureLength}, + feature::{MetadataFeatureKeyLength, MetadataFeatureValueLength, TagFeatureLength}, native_token::NativeTokenCount, output_id::OutputIndex, unlock_condition::AddressUnlockCondition, diff --git a/sdk/src/types/block/rand/output/feature.rs b/sdk/src/types/block/rand/output/feature.rs index 54a3c46dfe..4451a26844 100644 --- a/sdk/src/types/block/rand/output/feature.rs +++ b/sdk/src/types/block/rand/output/feature.rs @@ -39,7 +39,7 @@ pub fn rand_metadata_feature() -> MetadataFeature { let mut total_size = 0; for _ in 0..10 { - if total_size >= (*MetadataFeature::LENGTH_RANGE.end() - 1) as usize - u8::MAX as usize { + if total_size >= (*MetadataFeature::BYTE_LENGTH_RANGE.end() - 1) as usize - u8::MAX as usize { break; } // Key length @@ -53,14 +53,14 @@ pub fn rand_metadata_feature() -> MetadataFeature { ); total_size += key.as_bytes().len(); - if total_size >= *MetadataFeature::LENGTH_RANGE.end() as usize - 2 { + if total_size >= *MetadataFeature::BYTE_LENGTH_RANGE.end() as usize - 2 { break; } // Value length total_size += 2; let bytes = rand_bytes(rand_number_range(Range { start: 0, - end: *MetadataFeature::LENGTH_RANGE.end() as usize - total_size, + end: *MetadataFeature::BYTE_LENGTH_RANGE.end() as usize - total_size, }) as usize); total_size += bytes.len(); diff --git a/sdk/src/wallet/operations/transaction/high_level/create_account.rs b/sdk/src/wallet/operations/transaction/high_level/create_account.rs index 118e4279db..a06297bd60 100644 --- a/sdk/src/wallet/operations/transaction/high_level/create_account.rs +++ b/sdk/src/wallet/operations/transaction/high_level/create_account.rs @@ -152,9 +152,9 @@ mod tests { let params_some_1 = CreateAccountParams { address: None, immutable_metadata: Some( - MetadataFeature::new([(b"data".to_vec(), b"immutable_metadata".to_vec())]).unwrap(), + MetadataFeature::new([("data".to_owned(), b"immutable_metadata".to_vec())]).unwrap(), ), - metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"metadata".to_vec())]).unwrap()), + metadata: Some(MetadataFeature::new([("data".to_owned(), b"metadata".to_vec())]).unwrap()), }; let json_some = serde_json::to_string(¶ms_some_1).unwrap(); let params_some_2 = serde_json::from_str(&json_some).unwrap(); diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs index b554ca16ae..ea3625cb11 100644 --- a/sdk/tests/types/output/feature/metadata.rs +++ b/sdk/tests/types/output/feature/metadata.rs @@ -40,7 +40,7 @@ fn serde_roundtrip() { metadata_feature ); // Unordered keys are not removed - assert_eq!(metadata_feature.data().keys().count(), 3); + assert_eq!(metadata_feature.len(), 3); } #[test] diff --git a/sdk/tests/wallet/burn_outputs.rs b/sdk/tests/wallet/burn_outputs.rs index 9345092988..09c01e3b45 100644 --- a/sdk/tests/wallet/burn_outputs.rs +++ b/sdk/tests/wallet/burn_outputs.rs @@ -26,9 +26,9 @@ async fn mint_and_burn_nft() -> Result<()> { let nft_options = [MintNftParams::new() .with_address(wallet.address().await) - .with_metadata(MetadataFeature::new([(b"data".to_vec(), b"some nft metadata".to_vec())]).unwrap()) + .with_metadata(MetadataFeature::new([("data".to_owned(), b"some nft metadata".to_vec())]).unwrap()) .with_immutable_metadata( - MetadataFeature::new([(b"data".to_vec(), b"some immutable nft metadata".to_vec())]).unwrap(), + MetadataFeature::new([("data".to_owned(), b"some immutable nft metadata".to_vec())]).unwrap(), )]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); @@ -288,9 +288,9 @@ async fn mint_and_burn_nft_with_account() -> Result<()> { wallet.sync(None).await?; let nft_options = [MintNftParams::new() - .with_metadata(MetadataFeature::new([(b"data".to_vec(), b"some nft metadata".to_vec())]).unwrap()) + .with_metadata(MetadataFeature::new([("data".to_owned(), b"some nft metadata".to_vec())]).unwrap()) .with_immutable_metadata( - MetadataFeature::new([(b"data".to_vec(), b"some immutable nft metadata".to_vec())]).unwrap(), + MetadataFeature::new([("data".to_owned(), b"some immutable nft metadata".to_vec())]).unwrap(), )]; let nft_tx = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet diff --git a/sdk/tests/wallet/native_tokens.rs b/sdk/tests/wallet/native_tokens.rs index 80fe56a55b..dbc2d3d8f7 100644 --- a/sdk/tests/wallet/native_tokens.rs +++ b/sdk/tests/wallet/native_tokens.rs @@ -87,7 +87,7 @@ async fn native_token_foundry_metadata() -> Result<()> { .await?; wallet.sync(None).await?; - let foundry_metadata = MetadataFeature::new([(vec![1, 3], vec![3, 7])])?; + let foundry_metadata = MetadataFeature::new([("13".to_owned(), vec![3, 7])])?; let create_tx = wallet .create_native_token( diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index 949ef3b057..c966f3a434 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -101,7 +101,7 @@ async fn output_preparation() -> Result<()> { amount: 300000, assets: None, features: Some(Features { - metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"Hello world".to_vec())]).unwrap()), + metadata: Some(MetadataFeature::new([("data".to_owned(), b"Hello world".to_vec())]).unwrap()), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -127,7 +127,7 @@ async fn output_preparation() -> Result<()> { amount: 1, assets: None, features: Some(Features { - metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"Hello world".to_vec())]).unwrap()), + metadata: Some(MetadataFeature::new([("data".to_owned(), b"Hello world".to_vec())]).unwrap()), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -156,7 +156,7 @@ async fn output_preparation() -> Result<()> { amount: 12000, assets: None, features: Some(Features { - metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"Hello world".to_vec())]).unwrap()), + metadata: Some(MetadataFeature::new([("data".to_owned(), b"Hello world".to_vec())]).unwrap()), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -182,7 +182,7 @@ async fn output_preparation() -> Result<()> { amount: 1, assets: None, features: Some(Features { - metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"Hello world".to_vec())]).unwrap()), + metadata: Some(MetadataFeature::new([("data".to_owned(), b"Hello world".to_vec())]).unwrap()), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -399,7 +399,7 @@ async fn output_preparation() -> Result<()> { assets: None, features: Some(Features { metadata: Some( - MetadataFeature::new([(b"data".to_vec(), b"Large metadata".repeat(100).to_vec())]).unwrap(), + MetadataFeature::new([("data".to_owned(), b"Large metadata".repeat(100).to_vec())]).unwrap(), ), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, @@ -553,10 +553,10 @@ async fn prepare_nft_output_features_update() -> Result<()> { let nft_options = [MintNftParams::new() .with_address(wallet_address.clone()) .with_sender(wallet_address.clone()) - .with_metadata(MetadataFeature::new([(vec![42], vec![42])])?) + .with_metadata(MetadataFeature::new([("42".to_owned(), vec![42])])?) .with_tag(b"some nft tag".to_vec()) .with_issuer(wallet_address.clone()) - .with_immutable_metadata(MetadataFeature::new([(vec![42], vec![42])])?)]; + .with_immutable_metadata(MetadataFeature::new([("42".to_owned(), vec![42])])?)]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet @@ -572,7 +572,7 @@ async fn prepare_nft_output_features_update() -> Result<()> { amount: 1_000_000, assets: Some(Assets { nft_id: Some(nft_id) }), features: Some(Features { - metadata: Some(MetadataFeature::new([(b"data".to_vec(), b"0x2a".to_vec())]).unwrap()), + metadata: Some(MetadataFeature::new([("data".to_owned(), b"0x2a".to_vec())]).unwrap()), tag: None, issuer: None, sender: None, @@ -592,21 +592,13 @@ async fn prepare_nft_output_features_update() -> Result<()> { assert!(nft.features().sender().is_none()); assert!(nft.features().tag().is_none()); assert_eq!( - nft.features() - .metadata() - .unwrap() - .data() - .first_key_value() - .unwrap() - .1 - .to_vec(), + nft.features().metadata().unwrap().first_key_value().unwrap().1.to_vec(), [42] ); assert_eq!( nft.immutable_features() .metadata() .unwrap() - .data() .first_key_value() .unwrap() .1 @@ -846,10 +838,10 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { let nft_options = [MintNftParams::new() .with_address(address.clone()) .with_sender(address.clone()) - .with_metadata(MetadataFeature::new([(vec![42], vec![42])])?) + .with_metadata(MetadataFeature::new([("42".to_owned(), vec![42])])?) .with_tag(b"some nft tag".to_vec()) .with_issuer(address.clone()) - .with_immutable_metadata(MetadataFeature::new([(vec![43], vec![43])])?)]; + .with_immutable_metadata(MetadataFeature::new([("43".to_owned(), vec![43])])?)]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet @@ -888,7 +880,6 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { nft.immutable_features() .metadata() .unwrap() - .data() .first_key_value() .unwrap() .1 From 15d3127907b89b9e8150ed87167820e88401cd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Wed, 17 Jan 2024 09:40:35 +0100 Subject: [PATCH 46/51] Small fixes, improvements, clippy --- .../input_selection/remainder.rs | 4 +-- sdk/src/types/block/error.rs | 2 +- .../types/block/output/feature/metadata.rs | 30 +++++++------------ sdk/src/types/block/output/mod.rs | 4 +-- sdk/src/types/block/rand/output/feature.rs | 4 +-- .../client/input_selection/nft_outputs.rs | 4 +-- sdk/tests/types/output/feature/metadata.rs | 2 +- 7 files changed, 20 insertions(+), 30 deletions(-) diff --git a/sdk/src/client/api/block_builder/input_selection/remainder.rs b/sdk/src/client/api/block_builder/input_selection/remainder.rs index e572fc1ad8..52d9a038b4 100644 --- a/sdk/src/client/api/block_builder/input_selection/remainder.rs +++ b/sdk/src/client/api/block_builder/input_selection/remainder.rs @@ -167,10 +167,10 @@ impl InputSelection { if let Some(index) = index { self.outputs[index] = match &self.outputs[index] { - Output::Account(output) => AccountOutputBuilder::from(&*output) + Output::Account(output) => AccountOutputBuilder::from(output) .with_mana(output.mana() + mana_diff) .finish_output()?, - Output::Nft(output) => NftOutputBuilder::from(&*output) + Output::Nft(output) => NftOutputBuilder::from(output) .with_mana(output.mana() + mana_diff) .finish_output()?, _ => panic!("only account, nft can be automatically created and can hold mana"), diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index a3ee4def0e..751270f18a 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -5,7 +5,7 @@ use alloc::{ string::{FromUtf8Error, String}, vec::Vec, }; -use core::{convert::Infallible, fmt, num::TryFromIntError}; +use core::{convert::Infallible, fmt}; use bech32::primitives::hrp::Error as Bech32HrpError; use crypto::Error as CryptoError; diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index fa9a311970..8c79f80593 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -55,15 +55,13 @@ fn verify_keys_packable(feature: &MetadataFeature) -> Result } fn verify_byte_length_packable(len: usize) -> Result<(), Error> { - if VERIFY { - // +1 for the feature type - if !MetadataFeature::BYTE_LENGTH_RANGE + if VERIFY + && !MetadataFeature::BYTE_LENGTH_RANGE .contains(&u16::try_from(len).map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?) - { - return Err(Error::InvalidMetadataFeature(format!( - "Out of bounds byte length: {len}" - ))); - } + { + return Err(Error::InvalidMetadataFeature(format!( + "Out of bounds byte length: {len}" + ))); } Ok(()) } @@ -119,7 +117,7 @@ impl core::ops::Deref for MetadataFeature { type Target = MetadataBTreeMap; fn deref(&self) -> &Self::Target { - &*self.0 + &self.0 } } @@ -194,7 +192,7 @@ impl Packable for MetadataFeature { visitor: &Self::UnpackVisitor, ) -> Result> { let mut unpacker = CounterUnpacker::new(unpacker); - let res = MetadataFeature( + let res = Self( MetadataBTreeMapPrefix::unpack::<_, VERIFY>(&mut unpacker, visitor) .map_packable_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, ); @@ -210,11 +208,7 @@ impl TryFrom)>> for MetadataFeature { type Error = Error; fn try_from(data: Vec<(String, Vec)>) -> Result { - let mut builder = Self::build(); - for (k, v) in data { - builder.insert(k, v); - } - builder.finish() + Self::new(data) } } @@ -222,11 +216,7 @@ impl TryFrom>> for MetadataFeature { type Error = Error; fn try_from(data: BTreeMap>) -> Result { - let mut builder = Self::build(); - for (k, v) in data { - builder.insert(k, v); - } - builder.finish() + Self::new(data) } } diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 9afd64b5b5..98654d3965 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -239,9 +239,9 @@ impl Output { protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; let stored_mana = protocol_parameters.mana_with_decay(mana, creation_index, target_index)?; - Ok(potential_mana + potential_mana .checked_add(stored_mana) - .ok_or(Error::ConsumedManaOverflow)?) + .ok_or(Error::ConsumedManaOverflow) } /// Returns the unlock conditions of an [`Output`], if any. diff --git a/sdk/src/types/block/rand/output/feature.rs b/sdk/src/types/block/rand/output/feature.rs index 1cdd2d3793..3191a44e09 100644 --- a/sdk/src/types/block/rand/output/feature.rs +++ b/sdk/src/types/block/rand/output/feature.rs @@ -36,8 +36,8 @@ pub fn rand_issuer_feature() -> IssuerFeature { /// Generates a random [`MetadataFeature`]. pub fn rand_metadata_feature() -> MetadataFeature { let mut map = BTreeMap::new(); - // Starting at 2 for type + entries count bytes - let mut total_size = 2; + // Starting at 1 for entries count bytes + let mut total_size = 1; let max_size = *MetadataFeature::BYTE_LENGTH_RANGE.end() as usize; let key_prefix_length = 1; let value_prefix_length = 2; diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index 58bf158e72..9c6748a316 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/input_selection/nft_outputs.rs @@ -1315,7 +1315,7 @@ fn changed_immutable_metadata() { ) .with_issuer_name("Alice"); #[cfg(not(feature = "irc_27"))] - let metadata = vec![(vec![42], vec![42])]; + let metadata = vec![("42".to_owned(), vec![42])]; let nft_output = NftOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters(), nft_id_1) @@ -1340,7 +1340,7 @@ fn changed_immutable_metadata() { ) .with_issuer_name("Alice"); #[cfg(not(feature = "irc_27"))] - let metadata = vec![(vec![43], vec![43])]; + let metadata = vec![("43".to_owned(), vec![43])]; // New nft output with changed immutable metadata feature let updated_nft_output = NftOutputBuilder::from(nft_output.as_nft()) diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs index 1857a87b89..491e33b506 100644 --- a/sdk/tests/types/output/feature/metadata.rs +++ b/sdk/tests/types/output/feature/metadata.rs @@ -55,6 +55,6 @@ fn unpack_invalid_order() { fn unpack_invalid_length() { assert!(matches!( MetadataFeature::unpack_verified([vec![1, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()), - Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "Out of bounds byte length: 8198" + Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "Out of bounds byte length: 8197" )); } From c6571b34bf23e40351db886933d51d747179e0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Wed, 17 Jan 2024 09:43:48 +0100 Subject: [PATCH 47/51] no_std --- sdk/src/types/block/output/feature/metadata.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 8c79f80593..358148d374 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use alloc::{ + borrow::ToOwned, collections::BTreeMap, format, string::{String, ToString}, From 5cddc9b6461d73d24044423cb5f31c04488b33f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Wed, 17 Jan 2024 10:15:09 +0100 Subject: [PATCH 48/51] Fix doc comment --- sdk/src/types/block/output/feature/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 358148d374..c52981c7d5 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -127,7 +127,7 @@ impl core::ops::Deref for MetadataFeature { pub struct MetadataFeatureMap(BTreeMap>); impl MetadataFeatureMap { - /// Creates a new [`MetadataFeatureBuilder`]. + /// Creates a new [`MetadataFeatureMap`]. #[inline(always)] pub fn new() -> Self { Self(Default::default()) From ecbec94b88ef1d3d6ca1cbb3fdca8056e586cf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Wed, 17 Jan 2024 10:57:49 +0100 Subject: [PATCH 49/51] Review suggestions --- .../types/block/output/feature/metadata.rs | 31 +++++++++---------- sdk/src/types/block/output/feature/mod.rs | 2 +- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index c52981c7d5..8c049d2f59 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -77,31 +77,28 @@ impl MetadataFeature { #[inline(always)] pub fn new(data: impl IntoIterator)>) -> Result { let mut builder = Self::build(); - for (k, v) in data { - builder.insert(k, v); - } + builder.extend(data.into_iter()); builder.finish() } + /// Creates a new [`MetadataFeatureMap`]. pub fn build() -> MetadataFeatureMap { Default::default() } + /// Creates a [`MetadataFeatureMap`] with the data so it can be mutated. pub fn to_map(&self) -> MetadataFeatureMap { - // Unsafe: acceptable because of type level checking, i.e. keys must be ASCII - unsafe { - MetadataFeatureMap( - self.0 - .iter() - .map(|(k, v)| { - ( - String::from_utf8_unchecked(k.as_ref().to_owned()), - v.as_ref().to_owned(), - ) - }) - .collect(), - ) - } + MetadataFeatureMap( + self.0 + .iter() + .map(|(k, v)| { + ( + String::from_utf8(k.as_ref().to_owned()).expect("key must be ASCII"), + v.as_ref().to_owned(), + ) + }) + .collect(), + ) } /// Returns the data for a given key. diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index 19faa7af65..711d7d9df4 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -28,7 +28,7 @@ pub(crate) use self::{ pub use self::{ block_issuer::{BlockIssuerFeature, BlockIssuerKey, BlockIssuerKeys, Ed25519BlockIssuerKey}, issuer::IssuerFeature, - metadata::MetadataFeature, + metadata::{MetadataFeature, MetadataFeatureMap}, native_token::NativeTokenFeature, sender::SenderFeature, staking::StakingFeature, From 5c629841297ace371a1bb21f458ab02186f11cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Wed, 17 Jan 2024 11:28:45 +0100 Subject: [PATCH 50/51] Consistentency --- sdk/src/types/block/output/feature/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 8c049d2f59..e97d210ab9 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -639,7 +639,7 @@ pub(crate) mod dto { .map(|(k, v)| { ( // Safe to unwrap, keys must be ascii - alloc::str::from_utf8(k.as_ref()).expect("invalid ascii").to_string(), + alloc::str::from_utf8(k.as_ref()).unwrap().to_string(), prefix_hex::encode(v.as_ref()), ) }) From 1e3985a65eb70b6421f9032b55a3a0fa89f9954c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Wed, 17 Jan 2024 13:58:08 +0100 Subject: [PATCH 51/51] Remove unwrap --- cli/src/wallet_cli/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 1bb9c3317a..2c02a7a223 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -1265,7 +1265,8 @@ pub async fn prompt_internal( maximum_supply, bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file) .await? - .map(|d| MetadataFeature::new([(foundry_metadata_key, d)]).unwrap()), + .map(|d| MetadataFeature::new([(foundry_metadata_key, d)])) + .transpose()?, ) .await }