diff --git a/bindings/core/src/method/client.rs b/bindings/core/src/method/client.rs index d10431bf32..12272531d9 100644 --- a/bindings/core/src/method/client.rs +++ b/bindings/core/src/method/client.rs @@ -41,7 +41,6 @@ pub enum ClientMethod { // If not provided, minimum amount will be used #[serde(default, with = "option_string")] amount: Option, - // TODO: Determine if `default` is wanted here #[serde(default, with = "string")] mana: u64, account_id: AccountId, @@ -58,7 +57,6 @@ pub enum ClientMethod { // If not provided, minimum amount will be used #[serde(default, with = "option_string")] amount: Option, - // TODO: Determine if `default` is wanted here #[serde(default, with = "string")] mana: u64, unlock_conditions: Vec, @@ -86,7 +84,6 @@ pub enum ClientMethod { // If not provided, minimum amount will be used #[serde(default, with = "option_string")] amount: Option, - // TODO: Determine if `default` is wanted here #[serde(default, with = "string")] mana: u64, nft_id: NftId, diff --git a/sdk/examples/client/block/02_block_custom_parents.rs b/sdk/examples/client/block/02_block_custom_parents.rs index b7ce9ab6c8..ab0f30c289 100644 --- a/sdk/examples/client/block/02_block_custom_parents.rs +++ b/sdk/examples/client/block/02_block_custom_parents.rs @@ -15,7 +15,11 @@ use iota_sdk::{ secret::{SecretManager, SignBlock}, Client, }, - types::block::output::AccountId, + types::block::{ + core::{basic::MaxBurnedManaAmount, BasicBlockBodyBuilder, BlockHeader}, + output::AccountId, + UnsignedBlock, + }, }; #[tokio::main] @@ -39,16 +43,37 @@ async fn main() -> Result<(), Box> { let issuance = client.get_issuance().await?; println!("Issuance:\n{issuance:#?}"); + let protocol_params = client.get_protocol_parameters().await?; + // Create and send the block with custom parents. - // TODO build block with custom parents, but without `build_basic_block()` - let block = client - .build_basic_block(issuer_id, None) - .await? - .sign_ed25519(&secret_manager, Bip44::new(IOTA_COIN_TYPE)) - .await?; + let block = UnsignedBlock::new( + BlockHeader::new( + protocol_params.version(), + protocol_params.network_id(), + time::OffsetDateTime::now_utc().unix_timestamp_nanos() as _, + issuance.latest_commitment.id(), + issuance.latest_finalized_slot, + issuer_id, + ), + BasicBlockBodyBuilder::new( + issuance.strong_parents()?, + MaxBurnedManaAmount::MinimumAmount { + params: protocol_params.work_score_parameters(), + reference_mana_cost: client + .get_account_congestion(&issuer_id, None) + .await? + .reference_mana_cost, + }, + ) + .finish_block_body()?, + ) + .sign_ed25519(&secret_manager, Bip44::new(IOTA_COIN_TYPE)) + .await?; println!("{block:#?}"); + client.post_block(&block).await?; + println!( "Block with custom parents sent: {}/block/{}", std::env::var("EXPLORER_URL").unwrap(), diff --git a/sdk/src/client/api/block_builder/mod.rs b/sdk/src/client/api/block_builder/mod.rs index c632dea9c1..7df509a723 100644 --- a/sdk/src/client/api/block_builder/mod.rs +++ b/sdk/src/client/api/block_builder/mod.rs @@ -24,15 +24,10 @@ impl Client { let issuance = self.get_issuance().await?; let issuing_time = { - #[cfg(feature = "std")] - let issuing_time = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) + let issuing_time = instant::SystemTime::now() + .duration_since(instant::SystemTime::UNIX_EPOCH) .expect("Time went backwards") .as_nanos() as u64; - // TODO no_std way to have a nanosecond timestamp - // https://github.com/iotaledger/iota-sdk/issues/647 - #[cfg(not(feature = "std"))] - let issuing_time = 0; // Check that the issuing_time is in the range of +-5 minutes of the node to prevent potential issues if !(issuance.latest_parent_block_issuing_time - FIVE_MINUTES_IN_NANOSECONDS diff --git a/sdk/src/client/api/block_builder/transaction_builder/error.rs b/sdk/src/client/api/block_builder/transaction_builder/error.rs index 06dfa4c538..8affd1f350 100644 --- a/sdk/src/client/api/block_builder/transaction_builder/error.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/error.rs @@ -45,6 +45,8 @@ pub enum TransactionBuilderError { found: u64, /// The required amount. required: u64, + /// The number of slots remaining before this transaction will have generated enough mana. + slots_remaining: u32, }, /// Insufficient native token amount provided. #[error("insufficient native token amount: found {found}, required {required}")] @@ -76,8 +78,7 @@ pub enum TransactionBuilderError { UnfulfillableRequirement(Requirement), /// Unsupported address type. #[error("unsupported address type {0}")] - // TODO replace with string when 2.0 has Address::kind_str - UnsupportedAddressType(u8), + UnsupportedAddressType(String), /// Block error. #[error("{0}")] Block(#[from] BlockError), 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 6eb1d75e0a..dce9e3a593 100644 --- a/sdk/src/client/api/block_builder/transaction_builder/mod.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/mod.rs @@ -349,9 +349,21 @@ impl TransactionBuilder { let (input_mana, output_mana) = self.mana_sums(false)?; if input_mana < output_mana { + let total_generation_amount = self + .selected_inputs + .iter() + .map(|o| o.output.mana_generation_amount(&self.protocol_parameters)) + .sum::(); + let slots_remaining = self.protocol_parameters.slots_until_generated( + self.creation_slot, + total_generation_amount, + self.total_selected_mana(false)?, + output_mana - input_mana, + )?; return Err(TransactionBuilderError::InsufficientMana { found: input_mana, required: output_mana, + slots_remaining, }); } diff --git a/sdk/src/client/api/block_builder/transaction_builder/requirement/sender.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/sender.rs index 11d951cdef..29326f706f 100644 --- a/sdk/src/client/api/block_builder/transaction_builder/requirement/sender.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/sender.rs @@ -79,7 +79,9 @@ impl TransactionBuilder { self.fulfill_sender_requirement(restricted_address.address()) } - _ => Err(TransactionBuilderError::UnsupportedAddressType(address.kind())), + _ => Err(TransactionBuilderError::UnsupportedAddressType( + address.kind_str().to_owned(), + )), } } } diff --git a/sdk/src/types/block/mana/error.rs b/sdk/src/types/block/mana/error.rs index b4d511d24e..a4c921bed6 100644 --- a/sdk/src/types/block/mana/error.rs +++ b/sdk/src/types/block/mana/error.rs @@ -18,6 +18,10 @@ pub enum ManaError { AllotmentsNotUniqueSorted, #[display(fmt = "invalid epoch diff: created {created}, target {target}")] EpochDiff { created: EpochIndex, target: EpochIndex }, + #[display(fmt = "insufficient amount to generate positive mana")] + InsufficientGenerationAmount, + #[display(fmt = "mana value {value} above maximum {max}")] + AboveMax { value: u64, max: u64 }, } #[cfg(feature = "std")] diff --git a/sdk/src/types/block/mana/parameters.rs b/sdk/src/types/block/mana/parameters.rs index 10fcb8edb6..762c7b96eb 100644 --- a/sdk/src/types/block/mana/parameters.rs +++ b/sdk/src/types/block/mana/parameters.rs @@ -196,6 +196,59 @@ impl ProtocolParameters { - (c >> mana_parameters.decay_factors_exponent()) }) } + + pub fn slots_until_generated( + &self, + current_slot: impl Into, + generation_amount: u64, + stored_mana: u64, + required_mana: u64, + ) -> Result { + if required_mana == 0 { + return Ok(0); + } + if required_mana > self.mana_parameters().max_mana() { + return Err(ManaError::AboveMax { + value: required_mana, + max: self.mana_parameters().max_mana(), + }); + } + let current_slot = current_slot.into(); + let mut num_slots = 0; + let mana_generated_per_epoch = self + .mana_parameters() + .generate_mana(generation_amount, self.slots_per_epoch()); + let mut required_mana_remaining = required_mana; + loop { + // Get the minimum number of slots required to achieve the needed mana (i.e. not including decay) + let additional_slots = u32::try_from( + (required_mana_remaining as u128 * self.slots_per_epoch() as u128) + / mana_generated_per_epoch.max(1) as u128, + ) + .map_err(|_| ManaError::InsufficientGenerationAmount)?; + + num_slots += additional_slots.max(1); + // Get the actual values after that many slots + let decayed_mana = + stored_mana - self.mana_with_decay(stored_mana, current_slot, current_slot + num_slots)?; + let generated_mana = + self.generate_mana_with_decay(generation_amount, current_slot, current_slot + num_slots)?; + // If we generated less than how much we lost, this is not going to work out + if generated_mana <= decayed_mana { + return Err(ManaError::InsufficientGenerationAmount); + } + if generated_mana - decayed_mana >= required_mana { + return Ok(num_slots); + } + let new_required_mana = (required_mana + decayed_mana) + .checked_sub(generated_mana) + .ok_or(ManaError::InsufficientGenerationAmount)?; + if new_required_mana >= required_mana_remaining { + return Err(ManaError::InsufficientGenerationAmount); + } + required_mana_remaining = new_required_mana; + } + } } /// Perform a multiplication and shift. @@ -544,4 +597,122 @@ mod test { 4.0, ) } + + #[derive(Debug)] + struct ManaTest { + current_slot: u32, + generation_amount: u64, + stored_mana: u64, + required_mana: u64, + } + + impl ManaTest { + fn mana_after(&self, slots: u32) -> u64 { + params() + .generate_mana_with_decay(self.generation_amount, self.current_slot, self.current_slot + slots) + .unwrap() + + params() + .mana_with_decay(self.stored_mana, self.current_slot, self.current_slot + slots) + .unwrap() + } + + fn slots_until_generated(&self) -> Result { + params().slots_until_generated( + self.current_slot, + self.generation_amount, + self.stored_mana, + self.required_mana, + ) + } + } + + #[test] + fn slots_until_generated() { + for test in [ + ManaTest { + current_slot: 100, + generation_amount: 100000, + stored_mana: 1000000, + required_mana: 50000, + }, + ManaTest { + current_slot: 1000000, + generation_amount: 500000, + stored_mana: 12345, + required_mana: 999999, + }, + ManaTest { + current_slot: 1294732685, + generation_amount: 300000, + stored_mana: 50, + required_mana: 1, + }, + ManaTest { + current_slot: 1294732685, + generation_amount: 500000, + stored_mana: 0, + required_mana: 600, + }, + ] { + let slots_left = test.slots_until_generated().expect(&format!("{test:?}")); + let mana_after_n_minus_1 = test.mana_after(slots_left - 1); + let mana_after_n = test.mana_after(slots_left); + let expected_mana = test.stored_mana + test.required_mana; + assert!( + mana_after_n_minus_1 < expected_mana, + "{test:?}: mana after {} slots should be lower than {expected_mana}, but found {mana_after_n_minus_1}", + slots_left - 1, + ); + assert!( + mana_after_n >= expected_mana, + "{test:?}: mana after {slots_left} slots should be greater than or equal to {expected_mana}, but found {mana_after_n}", + ); + } + } + + #[test] + fn slots_until_generated_insufficient_amount() { + for test in [ + ManaTest { + current_slot: 10000, + generation_amount: 1000, + stored_mana: 1000000, + required_mana: 50000, + }, + ManaTest { + current_slot: 10000, + generation_amount: 2000000, + stored_mana: 0, + required_mana: 1000000000, + }, + ManaTest { + current_slot: 10000, + generation_amount: 100000, + stored_mana: 1000000, + required_mana: 500000000000, + }, + ] { + let slots_left = test.slots_until_generated().unwrap_err(); + assert_eq!(slots_left, ManaError::InsufficientGenerationAmount); + } + } + + #[test] + fn slots_until_generated_required_above_max() { + let test = ManaTest { + current_slot: 10000, + generation_amount: 100000, + stored_mana: 1000000, + required_mana: 9999999999999999999, + }; + + let slots_left = test.slots_until_generated().unwrap_err(); + assert_eq!( + slots_left, + ManaError::AboveMax { + value: test.required_mana, + max: params().mana_parameters().max_mana() + } + ); + } } diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index ca0ea23646..66c02a1348 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -429,8 +429,7 @@ impl AccountOutput { creation_index: SlotIndex, target_index: SlotIndex, ) -> Result { - let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); - let generation_amount = self.amount().saturating_sub(min_deposit); + let generation_amount = self.mana_generation_amount(protocol_parameters); let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?; let potential_mana = protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; @@ -440,6 +439,12 @@ impl AccountOutput { potential: potential_mana, }) } + + /// Returns the mana generation amount of the output. + pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + self.amount().saturating_sub(min_deposit) + } } impl StorageScore for AccountOutput { diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index 9c59ef5b45..0b1fe7ec31 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -486,8 +486,7 @@ impl AnchorOutput { creation_index: SlotIndex, target_index: SlotIndex, ) -> Result { - let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); - let generation_amount = self.amount().saturating_sub(min_deposit); + let generation_amount = self.mana_generation_amount(protocol_parameters); let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?; let potential_mana = protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; @@ -497,6 +496,12 @@ impl AnchorOutput { potential: potential_mana, }) } + + /// Returns the mana generation amount of the output. + pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + self.amount().saturating_sub(min_deposit) + } } impl StorageScore for AnchorOutput { diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index ab2f6b94e4..e6761ea619 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -386,8 +386,7 @@ impl BasicOutput { creation_index: SlotIndex, target_index: SlotIndex, ) -> Result { - let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); - let generation_amount = self.amount().saturating_sub(min_deposit); + let generation_amount = self.mana_generation_amount(protocol_parameters); let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?; let potential_mana = protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; @@ -397,6 +396,12 @@ impl BasicOutput { potential: potential_mana, }) } + + /// Returns the mana generation amount of the output. + pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + self.amount().saturating_sub(min_deposit) + } } impl StorageScore for BasicOutput { diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index eadc8ccd39..872bf18080 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -372,8 +372,7 @@ impl DelegationOutput { creation_index: SlotIndex, target_index: SlotIndex, ) -> Result { - let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); - let generation_amount = self.amount().saturating_sub(min_deposit); + let generation_amount = self.mana_generation_amount(protocol_parameters); let potential_mana = protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; @@ -382,6 +381,12 @@ impl DelegationOutput { potential: potential_mana, }) } + + /// Returns the mana generation amount of the output. + pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + self.amount().saturating_sub(min_deposit) + } } impl StorageScore for DelegationOutput { diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index 6ed1c2a764..655a18d28b 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -441,8 +441,7 @@ impl FoundryOutput { creation_index: SlotIndex, target_index: SlotIndex, ) -> Result { - let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); - let generation_amount = self.amount().saturating_sub(min_deposit); + let generation_amount = self.mana_generation_amount(protocol_parameters); let potential_mana = protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; @@ -451,6 +450,12 @@ impl FoundryOutput { potential: potential_mana, }) } + + /// Returns the mana generation amount of the output. + pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + self.amount().saturating_sub(min_deposit) + } } impl StorageScore for FoundryOutput { diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 09d8b86761..129ea04627 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -207,6 +207,18 @@ impl Output { } } + /// Returns the mana generation amount of the output. + pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 { + match self { + Self::Basic(output) => output.mana_generation_amount(protocol_parameters), + Self::Account(output) => output.mana_generation_amount(protocol_parameters), + Self::Anchor(output) => output.mana_generation_amount(protocol_parameters), + Self::Foundry(output) => output.mana_generation_amount(protocol_parameters), + Self::Nft(output) => output.mana_generation_amount(protocol_parameters), + Self::Delegation(output) => output.mana_generation_amount(protocol_parameters), + } + } + /// Returns the unlock conditions of an [`Output`], if any. pub fn unlock_conditions(&self) -> Option<&UnlockConditions> { match self { diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index da879a6166..04c0c763bc 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -441,8 +441,7 @@ impl NftOutput { creation_index: SlotIndex, target_index: SlotIndex, ) -> Result { - let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); - let generation_amount = self.amount().saturating_sub(min_deposit); + let generation_amount = self.mana_generation_amount(protocol_parameters); let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?; let potential_mana = protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; @@ -452,6 +451,12 @@ impl NftOutput { potential: potential_mana, }) } + + /// Returns the mana generation amount of the output. + pub fn mana_generation_amount(&self, protocol_parameters: &ProtocolParameters) -> u64 { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + self.amount().saturating_sub(min_deposit) + } } impl StorageScore for NftOutput { diff --git a/sdk/tests/client/transaction_builder/foundry_outputs.rs b/sdk/tests/client/transaction_builder/foundry_outputs.rs index daf19c3dd7..a4a2dc32c8 100644 --- a/sdk/tests/client/transaction_builder/foundry_outputs.rs +++ b/sdk/tests/client/transaction_builder/foundry_outputs.rs @@ -168,10 +168,9 @@ fn minted_native_tokens_in_new_remainder() { // Account next state + foundry + basic output with native tokens assert_eq!(selected.transaction.outputs().len(), 3); selected.transaction.outputs().iter().for_each(|output| { - if let Output::Basic(_basic_output) = &output { + if let Output::Basic(basic_output) = &output { // Basic output remainder has the minted native tokens - // TODO reenable when ISA supports NTs again - // assert_eq!(basic_output.native_token().unwrap().amount().as_u32(), 10); + assert_eq!(basic_output.native_token().unwrap().amount().as_u32(), 10); } }); } @@ -321,10 +320,9 @@ fn melt_native_tokens() { // Account next state + foundry + basic output with native tokens assert_eq!(selected.transaction.outputs().len(), 3); selected.transaction.outputs().iter().for_each(|output| { - if let Output::Basic(_basic_output) = &output { + if let Output::Basic(basic_output) = &output { // Basic output remainder has the remaining native tokens - // TODO reenable when ISA supports NTs again - // assert_eq!(basic_output.native_token().unwrap().amount().as_u32(), 5); + assert_eq!(basic_output.native_token().unwrap().amount().as_u32(), 5); } }); } @@ -1264,10 +1262,9 @@ fn melt_and_burn_native_tokens() { assert_eq!(selected.transaction.outputs().len(), 3); // Account state index is increased selected.transaction.outputs().iter().for_each(|output| { - if let Output::Basic(_basic_output) = &output { + if let Output::Basic(basic_output) = &output { // Basic output remainder has the remaining native tokens - // TODO reenable when ISA supports NTs again - // assert_eq!(basic_output.native_token().unwrap().amount().as_u32(), 421); + assert_eq!(basic_output.native_token().unwrap().amount().as_u32(), 421); } }); } diff --git a/sdk/tests/client/transaction_builder/outputs.rs b/sdk/tests/client/transaction_builder/outputs.rs index 24d8f778f6..2306523045 100644 --- a/sdk/tests/client/transaction_builder/outputs.rs +++ b/sdk/tests/client/transaction_builder/outputs.rs @@ -591,3 +591,97 @@ fn transition_no_more_than_needed_for_nft_amount() { assert_eq!(selected.inputs_data.len(), 1); assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } + +#[test] +fn insufficient_mana() { + let protocol_parameters = iota_mainnet_protocol_parameters(); + + let inputs = build_inputs( + [ + ( + Basic { + amount: 1_000_000, + mana: 0, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + ), + ( + Basic { + amount: 1_000_000, + mana: 0, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); + let outputs = build_outputs([Basic { + amount: 2_000_000, + mana: 10000, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }]); + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .finish() + .unwrap_err(); + + let TransactionBuilderError::InsufficientMana { + found, + required, + slots_remaining, + } = selected + else { + panic!("expected insufficient mana error, found {selected:?}") + }; + assert_eq!(found, 0); + assert_eq!(required, 10000); + + // Re-running with any slot index less than the original plus the slots remaining will still error + let err = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX + slots_remaining - 1, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .finish() + .unwrap_err(); + + assert!(matches!(err, TransactionBuilderError::InsufficientMana { .. })); + + TransactionBuilder::new( + inputs.clone(), + outputs, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX + slots_remaining, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .finish() + .unwrap(); +} diff --git a/sdk/tests/types/tagged_data_payload.rs b/sdk/tests/types/tagged_data_payload.rs index ccaca36750..04e12ac077 100644 --- a/sdk/tests/types/tagged_data_payload.rs +++ b/sdk/tests/types/tagged_data_payload.rs @@ -4,7 +4,6 @@ use iota_sdk::types::block::{ payload::{tagged_data::TaggedDataPayload, PayloadError}, rand::bytes::{rand_bytes, rand_bytes_array}, - Block, }; use packable::{ bounded::{TryIntoBoundedU32Error, TryIntoBoundedU8Error}, @@ -57,19 +56,28 @@ fn new_valid_tag_length_min() { #[test] fn new_invalid_tag_length_more_than_max() { - assert!(matches!( - TaggedDataPayload::new(rand_bytes(65), [0x42, 0xff, 0x84, 0xa2, 0x42, 0xff, 0x84, 0xa2]), - Err(PayloadError::TagLength(TryIntoBoundedU8Error::Invalid(65))) - )); + assert_eq!( + TaggedDataPayload::new( + [0u8; *TaggedDataPayload::TAG_LENGTH_RANGE.end() as usize + 1], + [0x42, 0xff, 0x84, 0xa2, 0x42, 0xff, 0x84, 0xa2], + ), + Err(PayloadError::TagLength(TryIntoBoundedU8Error::Invalid( + TaggedDataPayload::TAG_LENGTH_RANGE.end() + 1 + ))) + ); } #[test] fn new_invalid_data_length_more_than_max() { - assert!(matches!( - // TODO https://github.com/iotaledger/iota-sdk/issues/1226 - TaggedDataPayload::new(rand_bytes(32), [0u8; Block::LENGTH_MAX + 42]), - Err(PayloadError::TaggedDataLength(TryIntoBoundedU32Error::Invalid(l))) if l == Block::LENGTH_MAX as u32 + 42 - )); + assert_eq!( + TaggedDataPayload::new( + rand_bytes(32), + [0u8; *TaggedDataPayload::DATA_LENGTH_RANGE.end() as usize + 1] + ), + Err(PayloadError::TaggedDataLength(TryIntoBoundedU32Error::Invalid( + TaggedDataPayload::DATA_LENGTH_RANGE.end() + 1 + ))) + ); } #[test] diff --git a/sdk/tests/wallet/balance.rs b/sdk/tests/wallet/balance.rs index 5a28905c90..0fd52dc90b 100644 --- a/sdk/tests/wallet/balance.rs +++ b/sdk/tests/wallet/balance.rs @@ -181,8 +181,15 @@ async fn balance_expiration() -> Result<(), Box> { assert_eq!(balance.base_coin().available(), 0); // Wait until expired - // TODO wait for slots, not seconds - tokio::time::sleep(std::time::Duration::from_secs(slots_until_expired as u64)).await; + let seconds_per_slot = wallet_0 + .client() + .get_protocol_parameters() + .await? + .slot_duration_in_seconds(); + tokio::time::sleep(std::time::Duration::from_secs( + seconds_per_slot as u64 * slots_until_expired as u64, + )) + .await; // Wallet 1 balance after expiration let balance = wallet_1.sync(None).await?;