diff --git a/sdk/src/client/api/block_builder/transaction_builder/mod.rs b/sdk/src/client/api/block_builder/transaction_builder/mod.rs index f8fd48f363..ea26d99a5f 100644 --- a/sdk/src/client/api/block_builder/transaction_builder/mod.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/mod.rs @@ -33,8 +33,8 @@ use crate::{ input::{Input, UtxoInput, INPUT_COUNT_MAX, INPUT_COUNT_RANGE}, mana::ManaAllotment, output::{ - AccountId, AccountOutputBuilder, BasicOutputBuilder, ChainId, NftOutputBuilder, Output, OutputId, - OUTPUT_COUNT_RANGE, + AccountId, AccountOutputBuilder, BasicOutputBuilder, ChainId, FoundryOutputBuilder, NftOutputBuilder, + Output, OutputId, OUTPUT_COUNT_RANGE, }, payload::{ signed_transaction::{Transaction, TransactionCapabilities, TransactionCapabilityFlag}, @@ -416,6 +416,9 @@ impl TransactionBuilder { .with_amount(new_amount) .with_mana(new_mana) .finish_output()?, + Output::Foundry(f) => FoundryOutputBuilder::from(&*f) + .with_amount(new_amount) + .finish_output()?, _ => unreachable!(), }; } diff --git a/sdk/src/client/api/block_builder/transaction_builder/remainder.rs b/sdk/src/client/api/block_builder/transaction_builder/remainder.rs index 6c805d95a6..de55b5ffc7 100644 --- a/sdk/src/client/api/block_builder/transaction_builder/remainder.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/remainder.rs @@ -168,13 +168,15 @@ impl TransactionBuilder { fn output_for_remainder_exists(&self, chain_id: Option, remainder_address: &Address) -> bool { // Find the first value that matches the remainder address self.added_outputs.iter().any(|o| { - (o.chain_id() == chain_id || chain_id.is_none() && (o.is_basic() || o.is_account() || o.is_nft())) + (o.chain_id() == chain_id + || (chain_id.is_none() + && (o.is_basic() || o.is_account() || o.is_nft()) + && matches!(o.required_address( + self.latest_slot_commitment_id.slot_index(), + self.protocol_parameters.committable_age_range(), + ), Ok(Some(address)) if &address == remainder_address))) && o.unlock_conditions().expiration().is_none() && o.unlock_conditions().timelock().is_none() - && matches!(o.required_address( - self.latest_slot_commitment_id.slot_index(), - self.protocol_parameters.committable_age_range(), - ), Ok(Some(address)) if &address == remainder_address) }) } @@ -184,13 +186,15 @@ impl TransactionBuilder { remainder_address: &Address, ) -> Option<&mut Output> { self.added_outputs.iter_mut().find(|o| { - (o.chain_id() == chain_id || chain_id.is_none() && (o.is_basic() || o.is_account() || o.is_nft())) - && o.unlock_conditions().expiration().is_none() - && o.unlock_conditions().timelock().is_none() - && matches!(o.required_address( + (o.chain_id() == chain_id + || (chain_id.is_none() + && (o.is_basic() || o.is_account() || o.is_nft()) + && matches!(o.required_address( self.latest_slot_commitment_id.slot_index(), self.protocol_parameters.committable_age_range(), - ), Ok(Some(address)) if &address == remainder_address) + ), Ok(Some(address)) if &address == remainder_address))) + && o.unlock_conditions().expiration().is_none() + && o.unlock_conditions().timelock().is_none() }) } diff --git a/sdk/src/client/api/block_builder/transaction_builder/transition.rs b/sdk/src/client/api/block_builder/transaction_builder/transition.rs index 2901b03321..8137dabb62 100644 --- a/sdk/src/client/api/block_builder/transaction_builder/transition.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/transition.rs @@ -150,8 +150,17 @@ impl TransactionBuilder { .with_foundry_counter(u32::max(highest_foundry_serial_number, input.foundry_counter())) .with_features(features); + // Block issuers cannot move their mana elsewhere. if input.is_block_issuer() { - builder = builder.with_mana(input.mana()); + if self.burn.as_ref().map_or(false, |b| b.generated_mana()) { + builder = builder.with_mana(input.mana()); + } else { + builder = builder.with_mana(input.available_mana( + &self.protocol_parameters, + output_id.transaction_id().slot_index(), + self.creation_slot, + )?); + } } let output = builder.finish_output()?; diff --git a/sdk/tests/client/transaction_builder/account_outputs.rs b/sdk/tests/client/transaction_builder/account_outputs.rs index 38611fac17..86533dd7c5 100644 --- a/sdk/tests/client/transaction_builder/account_outputs.rs +++ b/sdk/tests/client/transaction_builder/account_outputs.rs @@ -19,6 +19,7 @@ use iota_sdk::{ payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, protocol::iota_mainnet_protocol_parameters, rand::output::{rand_output_id_with_slot_index, rand_output_metadata_with_id}, + slot::SlotIndex, }, }; use pretty_assertions::{assert_eq, assert_ne}; @@ -2406,3 +2407,66 @@ fn account_transition_with_required_context_inputs() { 1 ); } + +#[test] +fn send_amount_from_block_issuer_account_with_generated_mana() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + let ed25519_address = Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(); + + let inputs = [AccountOutputBuilder::new_with_amount(10_000_000, account_id_1) + .with_mana(20000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_features([BlockIssuerFeature::new( + u32::MAX, + BlockIssuerKeys::from_vec(vec![ + Ed25519PublicKeyHashBlockIssuerKey::new(**ed25519_address.as_ed25519()).into(), + ]) + .unwrap(), + ) + .unwrap()]) + .finish_output() + .unwrap()]; + let inputs = inputs + .into_iter() + .map(|input| InputSigningData { + output: input, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SlotIndex(5))), + chain: None, + }) + .collect::>(); + + let outputs = vec![ + BasicOutputBuilder::new_with_amount(1_000_000) + .add_unlock_condition(AddressUnlockCondition::new(ed25519_address.clone())) + .finish_output() + .unwrap(), + ]; + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_min_mana_allotment(account_id_1, 2) + .with_remainder_address(Address::Account(account_id_1.into())) + .finish() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 2); + assert!(selected.transaction.outputs()[1].is_account()); + assert_eq!(selected.transaction.allotments().len(), 1); + // Required context inputs are added when the account is transitioned + assert_eq!(selected.transaction.context_inputs().len(), 2); + assert!(selected.transaction.context_inputs().commitment().is_some()); + assert_eq!( + selected.transaction.context_inputs().block_issuance_credits().count(), + 1 + ); +}