From 285820283ee8aed6da1085c2031782b66fbffb90 Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:23:45 +0100 Subject: [PATCH] Fix InvalidTransactionNativeTokensCount error for multiple entries (#1750) * Fix InvalidTransactionNativeTokensCount error for multiple entries with the same native token * Update sdk/CHANGELOG.md Co-authored-by: Thibault Martinez * Update sdk/src/types/block/payload/transaction/essence/regular.rs Co-authored-by: Thibault Martinez --------- Co-authored-by: Thibault Martinez --- sdk/CHANGELOG.md | 1 + .../payload/transaction/essence/regular.rs | 14 +-- .../types/transaction_regular_essence.rs | 108 +++++++++++++----- 3 files changed, 85 insertions(+), 38 deletions(-) diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 49e228b612..3f8a06127b 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `needs_blind_signing()` for non Ed25519 addresses; +- `InvalidTransactionNativeTokensCount` error for multiple entries with the same native token; ## 1.1.2 - 2023-10-26 diff --git a/sdk/src/types/block/payload/transaction/essence/regular.rs b/sdk/src/types/block/payload/transaction/essence/regular.rs index 1ad5b7b632..9ceb54434c 100644 --- a/sdk/src/types/block/payload/transaction/essence/regular.rs +++ b/sdk/src/types/block/payload/transaction/essence/regular.rs @@ -9,7 +9,7 @@ use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; use crate::types::{ block::{ input::{Input, INPUT_COUNT_RANGE}, - output::{InputsCommitment, NativeTokens, Output, OUTPUT_COUNT_RANGE}, + output::{InputsCommitment, NativeTokens, Output, TokenId, OUTPUT_COUNT_RANGE}, payload::{OptionalPayload, Payload}, protocol::ProtocolParameters, Error, @@ -219,7 +219,7 @@ fn verify_inputs_packable(inputs: &[Input], _visitor: &Proto fn verify_outputs(outputs: &[Output], visitor: &ProtocolParameters) -> Result<(), Error> { if VERIFY { let mut amount_sum: u64 = 0; - let mut native_tokens_count: u8 = 0; + let mut total_native_tokens = HashSet::::new(); let mut chain_ids = HashSet::new(); for output in outputs.iter() { @@ -240,12 +240,12 @@ fn verify_outputs(outputs: &[Output], visitor: &ProtocolPara return Err(Error::InvalidTransactionAmountSum(amount_sum as u128)); } - native_tokens_count = native_tokens_count.checked_add(native_tokens.len() as u8).ok_or( - Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16 + native_tokens.len() as u16), - )?; + total_native_tokens.extend(native_tokens.iter().map(|n| n.token_id())); - if native_tokens_count > NativeTokens::COUNT_MAX { - return Err(Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16)); + if total_native_tokens.len() > NativeTokens::COUNT_MAX.into() { + return Err(Error::InvalidTransactionNativeTokensCount( + total_native_tokens.len() as u16 + )); } if let Some(chain_id) = chain_id { diff --git a/sdk/tests/types/transaction_regular_essence.rs b/sdk/tests/types/transaction_regular_essence.rs index d113d1c981..a83e2e7925 100644 --- a/sdk/tests/types/transaction_regular_essence.rs +++ b/sdk/tests/types/transaction_regular_essence.rs @@ -1,6 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use core::str::FromStr; + use iota_sdk::types::block::{ address::{Address, AliasAddress, Ed25519Address}, input::{Input, TreasuryInput, UtxoInput}, @@ -43,8 +45,7 @@ fn build_valid() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -67,8 +68,7 @@ fn build_valid_with_payload() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -92,8 +92,7 @@ fn build_valid_add_inputs_outputs() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -116,8 +115,7 @@ fn build_invalid_payload_kind() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -138,8 +136,7 @@ fn build_invalid_payload_kind() { #[test] fn build_invalid_input_count_low() { let protocol_parameters = protocol_parameters(); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -163,8 +160,7 @@ fn build_invalid_input_count_high() { let protocol_parameters = protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -205,8 +201,7 @@ fn build_invalid_output_count_high() { let protocol_parameters = protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -231,8 +226,7 @@ fn build_invalid_duplicate_utxo() { let protocol_parameters = protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -253,8 +247,7 @@ fn build_invalid_duplicate_utxo() { fn build_invalid_input_kind() { let protocol_parameters = protocol_parameters(); let input = Input::Treasury(TreasuryInput::new(MilestoneId::new(rand_bytes_array()))); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( BasicOutput::build_with_amount(amount) @@ -327,8 +320,7 @@ fn getters() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let outputs = [Output::Basic( BasicOutput::build_with_amount(amount) @@ -355,14 +347,13 @@ fn duplicate_output_nft() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let basic = BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output(protocol_parameters.token_supply()) .unwrap(); - let nft_id = NftId::from(bytes); + let nft_id = NftId::from_str(ED25519_ADDRESS_1).unwrap(); let nft = NftOutput::build_with_amount(1_000_000, nft_id) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output(protocol_parameters.token_supply()) @@ -385,8 +376,7 @@ fn duplicate_output_nft_null() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let basic = BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) @@ -412,14 +402,13 @@ fn duplicate_output_alias() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let basic = BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output(protocol_parameters.token_supply()) .unwrap(); - let alias_id = AliasId::from(bytes); + let alias_id = AliasId::from_str(ED25519_ADDRESS_1).unwrap(); let alias = AliasOutput::build_with_amount(1_000_000, alias_id) .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) .add_unlock_condition(GovernorAddressUnlockCondition::new(address)) @@ -443,14 +432,13 @@ fn duplicate_output_foundry() { let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let basic = BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output(protocol_parameters.token_supply()) .unwrap(); - let alias_id = AliasId::from(bytes); + let alias_id = AliasId::from_str(ED25519_ADDRESS_1).unwrap(); let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new(70, 0, 100).unwrap()); let foundry_id = FoundryId::build(&AliasAddress::from(alias_id), 1, token_scheme.kind()); let token_id = TokenId::from(foundry_id); @@ -470,3 +458,61 @@ fn duplicate_output_foundry() { Err(Error::DuplicateOutputChain(ChainId::Foundry(foundry_id_0))) if foundry_id_0 == foundry_id )); } + +#[test] +fn more_than_64_same_native_tokens() { + let protocol_parameters = protocol_parameters(); + let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); + let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); + let amount = 1_000_000; + let alias_id = AliasId::from_str(ED25519_ADDRESS_1).unwrap(); + let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new(70, 0, 100).unwrap()); + let foundry_id = FoundryId::build(&AliasAddress::from(alias_id), 1, token_scheme.kind()); + let token_id = TokenId::from(foundry_id); + let basic = BasicOutput::build_with_amount(amount) + .add_native_token(NativeToken::new(token_id, 70).unwrap()) + .add_unlock_condition(AddressUnlockCondition::new(address)) + .finish_output(protocol_parameters.token_supply()) + .unwrap(); + + let essence = RegularTransactionEssence::builder(protocol_parameters.network_id(), rand_inputs_commitment()) + .with_inputs([input]) + .with_outputs(vec![basic; 100]) + .finish_with_params(&protocol_parameters); + + assert!(essence.is_ok()); +} + +#[test] +fn more_than_64_distinctive_native_tokens() { + let protocol_parameters = protocol_parameters(); + let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); + let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); + let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); + let amount = 1_000_000; + + let mut outputs = Vec::new(); + for _ in 0..65 { + let alias_id = AliasId::from(rand_bytes_array()); + let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new(70, 0, 100).unwrap()); + let foundry_id = FoundryId::build(&AliasAddress::from(alias_id), 1, token_scheme.kind()); + let token_id = TokenId::from(foundry_id); + let basic = BasicOutput::build_with_amount(amount) + .add_native_token(NativeToken::new(token_id, 70).unwrap()) + .add_unlock_condition(AddressUnlockCondition::new(address)) + .finish_output(protocol_parameters.token_supply()) + .unwrap(); + outputs.push(basic); + } + + let essence = RegularTransactionEssence::builder(protocol_parameters.network_id(), rand_inputs_commitment()) + .with_inputs([input]) + .with_outputs(outputs) + .finish_with_params(&protocol_parameters); + + assert!(matches!( + essence, + Err(Error::InvalidTransactionNativeTokensCount(count)) if count == 65 + )); +}