Skip to content

Commit

Permalink
Fix InvalidTransactionNativeTokensCount error for multiple entries (#…
Browse files Browse the repository at this point in the history
…1750)

* Fix InvalidTransactionNativeTokensCount error for multiple entries with the same native token

* Update sdk/CHANGELOG.md

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

* Update sdk/src/types/block/payload/transaction/essence/regular.rs

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

---------

Co-authored-by: Thibault Martinez <[email protected]>
  • Loading branch information
Thoralf-M and thibault-martinez committed Dec 6, 2023
1 parent 05394f8 commit 2858202
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 38 deletions.
1 change: 1 addition & 0 deletions sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 7 additions & 7 deletions sdk/src/types/block/payload/transaction/essence/regular.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -219,7 +219,7 @@ fn verify_inputs_packable<const VERIFY: bool>(inputs: &[Input], _visitor: &Proto
fn verify_outputs<const VERIFY: bool>(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::<TokenId>::new();
let mut chain_ids = HashSet::new();

for output in outputs.iter() {
Expand All @@ -240,12 +240,12 @@ fn verify_outputs<const VERIFY: bool>(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 {
Expand Down
108 changes: 77 additions & 31 deletions sdk/tests/types/transaction_regular_essence.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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())
Expand All @@ -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))
Expand All @@ -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))
Expand All @@ -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);
Expand All @@ -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
));
}

0 comments on commit 2858202

Please sign in to comment.