diff --git a/Cargo.lock b/Cargo.lock index d0aa691bb5..2a6072b294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2121,11 +2121,12 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "packable" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe35ea7a5959be5a87d24bcb31ed984580d9cd321c264c266818fff8cd47b3d" +checksum = "2ebbd9715a319d515dbc253604dd00b0e2c8618e4e5e4d3e0b9b4e46b90ef98e" dependencies = [ "autocfg", + "hashbrown", "packable-derive", "primitive-types", "serde", diff --git a/bindings/core/Cargo.toml b/bindings/core/Cargo.toml index 84b855e884..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 = { version = "0.10.0", 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/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/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)] diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 10ef4afdeb..2c02a7a223 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -10,18 +10,15 @@ 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}, - mana::ManaAllotment, - output::{ - unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, FoundryId, NativeToken, - NativeTokensBuilder, NftId, Output, OutputId, TokenId, - }, - payload::signed_transaction::TransactionId, - slot::SlotIndex, + types::block::{ + address::{Bech32Address, ToBech32Ext}, + mana::ManaAllotment, + output::{ + feature::MetadataFeature, unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, + FoundryId, NativeToken, NativeTokensBuilder, NftId, Output, OutputId, TokenId, }, + payload::signed_transaction::TransactionId, + slot::SlotIndex, }, utils::ConvertTo, wallet::{ @@ -96,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, @@ -145,12 +145,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, 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, default_value = "data")] + metadata_key: String, /// Metadata to attach to the NFT, e.g. --metadata-hex 0xdeadbeef. #[arg(long, group = "metadata")] metadata_hex: Option, @@ -245,40 +251,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 @@ -583,7 +589,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() { @@ -769,7 +775,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, @@ -781,13 +789,20 @@ 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([(metadata_key, metadata)])?); + } + if let Some(immutable_metadata) = 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?; println_log_info!( @@ -1000,80 +1015,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; @@ -1240,6 +1255,7 @@ pub async fn prompt_internal( WalletCommand::CreateNativeToken { circulating_supply, maximum_supply, + foundry_metadata_key, foundry_metadata_hex, foundry_metadata_file, } => { @@ -1247,7 +1263,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? + .map(|d| MetadataFeature::new([(foundry_metadata_key, d)])) + .transpose()?, ) .await } @@ -1276,8 +1295,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, @@ -1287,7 +1308,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, @@ -1327,22 +1350,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 d946ff4c62..f72ad700df 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 = { version = "0.10.1", default-features = false, features = [ "primitive-types", ] } paste = { version = "1.0.14", default-features = false } @@ -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", @@ -640,10 +641,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..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(metadata)?) + .add_feature(MetadataFeature::build().with_key_value("data", metadata).finish()?) .add_immutable_feature(IssuerFeature::new(address.clone())) - .add_immutable_feature(MetadataFeature::new(metadata)?) + .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 37102b74c2..fa4f9f13a0 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 str = "Hello"; +const METADATA: &'static [u8; 6] = b"World!"; #[tokio::main] async fn main() -> Result<()> { @@ -44,7 +45,7 @@ async fn main() -> Result<()> { // with metadata feature block basic_output_builder .clone() - .add_feature(MetadataFeature::new(METADATA)?) + .add_feature(MetadataFeature::new([(KEY.to_owned(), METADATA.to_vec())])?) .finish_output()?, // with storage deposit return basic_output_builder @@ -64,7 +65,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 3fedaf5532..478fdae975 100644 --- a/sdk/examples/client/output/build_nft_output.rs +++ b/sdk/examples/client/output/build_nft_output.rs @@ -63,10 +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_METADATA)?) + .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(tip_27_immutable_metadata)?) + .add_immutable_feature( + MetadataFeature::build() + .with_key_value("irc-27", tip_27_immutable_metadata.as_bytes()) + .finish()?, + ) .finish_output()?; println!("{nft_output:#?}"); 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?; 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..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 @@ -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([( + "data".to_owned(), + 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..7af6673613 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,15 @@ 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::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(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/examples/how_tos/outputs/features.rs b/sdk/examples/how_tos/outputs/features.rs index 78d991872f..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("Hello, World!")?) + .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("Hello, World!")?) + .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/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 672be985dc..751270f18a 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; @@ -18,7 +21,8 @@ use crate::types::block::{ output::{ feature::{BlockIssuerKeyCount, FeatureCount}, unlock_condition::UnlockConditionCount, - AccountId, AnchorId, ChainId, MetadataFeatureLength, NativeTokenCount, NftId, OutputIndex, TagFeatureLength, + AccountId, AnchorId, ChainId, MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength, + NativeTokenCount, NftId, OutputIndex, TagFeatureLength, }, payload::{ tagged_data::{TagLength, TaggedDataLength}, @@ -56,6 +60,7 @@ pub enum Error { InvalidAccountIndex(>::Error), InvalidAnchorIndex(>::Error), InvalidBlockBodyKind(u8), + NonGraphicAsciiMetadataKey(Vec), InvalidRewardInputIndex(>::Error), InvalidStorageDepositAmount(u64), /// Invalid transaction failure reason byte. @@ -104,7 +109,10 @@ pub enum Error { }, InvalidBlockLength(usize), InvalidManaValue(u64), - InvalidMetadataFeatureLength(>::Error), + InvalidMetadataFeature(String), + InvalidMetadataFeatureEntryCount(>::Error), + InvalidMetadataFeatureKeyLength(>::Error), + InvalidMetadataFeatureValueLength(>::Error), InvalidNativeTokenCount(>::Error), InvalidNetworkName(FromUtf8Error), InvalidManaDecayFactors, @@ -250,6 +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::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}") @@ -304,8 +313,17 @@ 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::InvalidMetadataFeatureLength(length) => { - write!(f, "invalid metadata feature length: {length}") + Self::InvalidMetadataFeature(e) => { + write!(f, "invalid metadata feature: {e}") + } + Self::InvalidMetadataFeatureEntryCount(count) => { + write!(f, "invalid metadata feature entry count: {count}") + } + 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}"), diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 090e5ebda7..e97d210ab9 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -1,103 +1,247 @@ // 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 packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix}; +use alloc::{ + borrow::ToOwned, + collections::BTreeMap, + format, + string::{String, ToString}, + vec::Vec, +}; +use core::ops::RangeInclusive; + +use packable::{ + bounded::{BoundedU16, BoundedU8}, + error::{UnpackError, UnpackErrorExt}, + packer::Packer, + prefix::{BTreeMapPrefix, BoxedSlicePrefix}, + 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, + 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::InvalidMetadataFeatureLength(err.into_prefix_err().into()))] -pub struct MetadataFeature( - // Binary data. - pub(crate) BoxedSlicePrefix, -); +#[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(feature: &MetadataFeature) -> Result<(), Error> { + if VERIFY { + for key in feature.0.keys() { + if !key.iter().all(|c| c.is_ascii_graphic()) { + return Err(Error::NonGraphicAsciiMetadataKey(key.to_vec())); + } + } + } + Ok(()) +} + +fn verify_byte_length_packable(len: usize) -> Result<(), Error> { + 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}" + ))); + } + Ok(()) +} 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 Into>) -> Result { - Self::try_from(data.into()) + pub fn new(data: impl IntoIterator)>) -> Result { + let mut builder = Self::build(); + builder.extend(data.into_iter()); + builder.finish() + } + + /// Creates a new [`MetadataFeatureMap`]. + pub fn build() -> MetadataFeatureMap { + Default::default() } - /// Returns the data. + /// Creates a [`MetadataFeatureMap`] with the data so it can be mutated. + pub fn to_map(&self) -> MetadataFeatureMap { + 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. #[inline(always)] - pub fn data(&self) -> &[u8] { + 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 } } -impl StorageScore for MetadataFeature {} +/// 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 WorkScore for MetadataFeature {} +impl MetadataFeatureMap { + /// Creates a new [`MetadataFeatureMap`]. + #[inline(always)] + pub fn new() -> Self { + Self(Default::default()) + } -macro_rules! impl_from_vec { - ($type:ty) => { - impl TryFrom<$type> for MetadataFeature { - type Error = Error; + pub fn with_key_value(mut self, key: &str, value: impl Into>) -> Self { + self.insert(key.to_owned(), value.into()); + self + } - fn try_from(value: $type) -> Result { - Vec::::from(value).try_into() - } - } - }; + 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::InvalidMetadataFeatureEntryCount)?, + ); + verify_packable::(&res)?; + Ok(res) + } } -impl_from_vec!(&str); -impl_from_vec!(String); -impl_from_vec!(&[u8]); -impl TryFrom<[u8; N]> for MetadataFeature { - type Error = Error; +impl core::ops::Deref for MetadataFeatureMap { + type Target = BTreeMap>; - fn try_from(value: [u8; N]) -> Result { - value.to_vec().try_into() + fn deref(&self) -> &Self::Target { + &self.0 } } -impl TryFrom> for MetadataFeature { - type Error = Error; +impl core::ops::DerefMut for MetadataFeatureMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +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.pack(packer) + } - fn try_from(data: Vec) -> Result { - data.into_boxed_slice().try_into() + fn unpack( + unpacker: &mut U, + visitor: &Self::UnpackVisitor, + ) -> Result> { + let mut unpacker = CounterUnpacker::new(unpacker); + let res = Self( + 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)?; + + Ok(res) } } -impl TryFrom> for MetadataFeature { +impl TryFrom)>> for MetadataFeature { type Error = Error; - fn try_from(data: Box<[u8]>) -> Result { - data.try_into().map(Self).map_err(Error::InvalidMetadataFeatureLength) + fn try_from(data: Vec<(String, Vec)>) -> Result { + Self::new(data) } } -impl FromStr for MetadataFeature { - type Err = Error; +impl TryFrom>> for MetadataFeature { + type Error = Error; - fn from_str(s: &str) -> Result { - Self::new(prefix_hex::decode::>(s).map_err(Error::Hex)?) + fn try_from(data: BTreeMap>) -> Result { + Self::new(data) } } 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})") + write!( + f, + "MetadataFeature({:?})", + self.0 + .iter() + .map(|(k, v)| (alloc::str::from_utf8(k).unwrap(), prefix_hex::encode(v.as_ref()))) + .collect::>() + ) } } @@ -205,15 +349,17 @@ 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() } } impl TryFrom for MetadataFeature { type Error = Error; + fn try_from(value: Irc27Metadata) -> Result { - Self::new(value.to_bytes()) + // TODO: is this hardcoded key correct or should users provide it? + Self::build().with_key_value("irc-27", value).finish() } } @@ -379,15 +525,17 @@ 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() } } impl TryFrom for MetadataFeature { type Error = Error; + fn try_from(value: Irc30Metadata) -> Result { - Self::new(value.to_bytes()) + // TODO: is this hardcoded key correct or should users provide it? + Self::build().with_key_value("irc-30", value).finish() } } @@ -434,35 +582,81 @@ pub(crate) mod irc_30 { #[cfg(feature = "serde")] pub(crate) mod dto { - use alloc::borrow::Cow; + use alloc::{collections::BTreeMap, format}; - 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<'de> Deserialize<'de> for MetadataFeature { + fn deserialize>(d: D) -> Result { + let value = Value::deserialize(d)?; + Ok( + 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( + 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, prefix_hex::decode::>(value)?))) + .collect::>, prefix_hex::Error>>() + .map_err(de::Error::custom)?, + ) + .map_err(de::Error::custom)? + } + _ => return Err(de::Error::custom("invalid metadata feature")), + }, + ) + } } - impl<'a> From<&'a MetadataFeature> for MetadataFeatureDto<'a> { - fn from(value: &'a MetadataFeature) -> Self { + impl From<&MetadataFeature> for MetadataFeatureDto { + fn from(value: &MetadataFeature) -> Self { + let entries = value + .0 + .iter() + .map(|(k, v)| { + ( + // Safe to unwrap, keys must be ascii + alloc::str::from_utf8(k.as_ref()).unwrap().to_string(), + prefix_hex::encode(v.as_ref()), + ) + }) + .collect::>(); + 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..711d7d9df4 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -20,11 +20,15 @@ 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::{MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength}, + tag::TagFeatureLength, +}; pub use self::{ block_issuer::{BlockIssuerFeature, BlockIssuerKey, BlockIssuerKeys, Ed25519BlockIssuerKey}, issuer::IssuerFeature, - metadata::MetadataFeature, + metadata::{MetadataFeature, MetadataFeatureMap}, native_token::NativeTokenFeature, sender::SenderFeature, staking::StakingFeature, diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 3faf18b62a..98654d3965 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::{MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength, TagFeatureLength}, native_token::NativeTokenCount, output_id::OutputIndex, unlock_condition::AddressUnlockCondition, @@ -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/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 57a8fdd609..3191a44e09 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,58 @@ 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(); + // 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; + + for _ in 0..10 { + // +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 += 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 > max_size - value_prefix_length { + // println!("breaking before adding more"); + break; + } + + // Value length + 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); + } + + 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..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 = { version = "0.10.0", default-features = false } +packable = { version = "0.10.1", default-features = false } # Prevent this from interfering with workspaces [workspace] 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..a06297bd60 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 @@ -87,6 +84,7 @@ where 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, @@ -94,11 +92,10 @@ where }) = params { if let Some(immutable_metadata) = immutable_metadata { - account_output_builder = - account_output_builder.add_immutable_feature(MetadataFeature::new(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(MetadataFeature::new(metadata)?); + account_output_builder = account_output_builder.add_feature(metadata); } } @@ -154,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([("data".to_owned(), b"immutable_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/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..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,7 +170,7 @@ where ))); if let Some(foundry_metadata) = params.foundry_metadata { - foundry_builder = foundry_builder.add_immutable_feature(MetadataFeature::new(foundry_metadata)?) + foundry_builder = foundry_builder.add_immutable_feature(foundry_metadata); } foundry_builder.finish_output()? 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..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 } @@ -192,7 +190,7 @@ where } if let Some(metadata) = metadata { - nft_builder = nft_builder.add_feature(MetadataFeature::new(metadata)?); + nft_builder = nft_builder.add_feature(metadata); } if let Some(tag) = tag { @@ -204,7 +202,7 @@ where } if let Some(immutable_metadata) = immutable_metadata { - nft_builder = nft_builder.add_immutable_feature(MetadataFeature::new(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 70d832d510..75da29a3a7 100644 --- a/sdk/src/wallet/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/operations/transaction/prepare_output.rs @@ -64,9 +64,7 @@ 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"))?, - )?); + first_output_builder = first_output_builder.add_feature(metadata); } if let Some(sender) = features.sender { @@ -316,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/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..6374a50944 100644 --- a/sdk/src/wallet/types/balance.rs +++ b/sdk/src/wallet/types/balance.rs @@ -131,7 +131,6 @@ pub struct NativeTokensBalance { pub(crate) available: U256, /// Token foundry immutable metadata #[getset(get = "pub")] - #[serde(with = "crate::utils::serde::option_string")] pub(crate) metadata: Option, } diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index 96ae5c18b9..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 = [1, 2, 3]; + 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 = [4, 5, 6]; + 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 new file mode 100644 index 0000000000..491e33b506 --- /dev/null +++ b/sdk/tests/types/output/feature/metadata.rs @@ -0,0 +1,60 @@ +// 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 + ); + // Unordered keys are not removed + assert_eq!(metadata_feature.len(), 3); +} + +#[test] +fn unpack_invalid_order() { + assert!(matches!( + 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" + )); +} + +#[test] +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: 8197" + )); +} 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; 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/burn_outputs.rs b/sdk/tests/wallet/burn_outputs.rs index 1d2d7330c3..09c01e3b45 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([("data".to_owned(), b"some nft metadata".to_vec())]).unwrap()) + .with_immutable_metadata( + MetadataFeature::new([("data".to_owned(), 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([("data".to_owned(), b"some nft metadata".to_vec())]).unwrap()) + .with_immutable_metadata( + 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 .reissue_transaction_until_included(&nft_tx.transaction_id, None, None) diff --git a/sdk/tests/wallet/native_tokens.rs b/sdk/tests/wallet/native_tokens.rs index a392e8fdd4..dbc2d3d8f7 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([("13".to_owned(), 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,8 +120,7 @@ async fn native_token_foundry_metadata() -> Result<()> { .unwrap() .metadata() .as_ref() - .unwrap() - .data(), + .unwrap(), &foundry_metadata ); diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index f6bc818ffc..c966f3a434 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, }, @@ -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([("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(prefix_hex::encode(b"Hello world")), + 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(prefix_hex::encode(b"Hello world")), + 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(prefix_hex::encode(b"Hello world")), + 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, @@ -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([("data".to_owned(), b"Large metadata".repeat(100).to_vec())]).unwrap(), + ), tag: Some(prefix_hex::encode(b"My Tag")), issuer: None, sender: None, @@ -551,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(b"some nft metadata".to_vec()) + .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(b"some immutable nft metadata".to_vec())]; + .with_immutable_metadata(MetadataFeature::new([("42".to_owned(), vec![42])])?)]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet @@ -570,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("0x2a".to_string()), + metadata: Some(MetadataFeature::new([("data".to_owned(), b"0x2a".to_vec())]).unwrap()), tag: None, issuer: None, sender: None, @@ -589,10 +591,19 @@ 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" + nft.features().metadata().unwrap().first_key_value().unwrap().1.to_vec(), + [42] + ); + assert_eq!( + nft.immutable_features() + .metadata() + .unwrap() + .first_key_value() + .unwrap() + .1 + .to_vec(), + [42] ); assert_eq!( nft.immutable_features().issuer().unwrap().address(), @@ -827,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(b"some nft metadata".to_vec()) + .with_metadata(MetadataFeature::new([("42".to_owned(), 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([("43".to_owned(), vec![43])])?)]; let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); wallet @@ -866,8 +877,14 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { 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" + nft.immutable_features() + .metadata() + .unwrap() + .first_key_value() + .unwrap() + .1 + .to_vec(), + [43] ); assert_eq!( nft.immutable_features().issuer().unwrap().address(),