Skip to content

Commit

Permalink
Fix block issuer account mana remainder (#2198)
Browse files Browse the repository at this point in the history
* Add send_amount_from_block_issuer_account_with_generated_mana test

* Use full generated mana for account transition (when not burning)

* Add remainders to chains regardless of remainder address

---------

Co-authored-by: Alex Coats <[email protected]>
  • Loading branch information
Thoralf-M and Alex Coats authored Mar 21, 2024
1 parent 754eaee commit 741268c
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 13 deletions.
7 changes: 5 additions & 2 deletions sdk/src/client/api/block_builder/transaction_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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!(),
};
}
Expand Down
24 changes: 14 additions & 10 deletions sdk/src/client/api/block_builder/transaction_builder/remainder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,15 @@ impl TransactionBuilder {
fn output_for_remainder_exists(&self, chain_id: Option<ChainId>, 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)
})
}

Expand All @@ -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()
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
Expand Down
64 changes: 64 additions & 0 deletions sdk/tests/client/transaction_builder/account_outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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::<Vec<_>>();

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
);
}

0 comments on commit 741268c

Please sign in to comment.