diff --git a/bindings/core/src/method/secret_manager.rs b/bindings/core/src/method/secret_manager.rs index b70c10db53..c36feb3580 100644 --- a/bindings/core/src/method/secret_manager.rs +++ b/bindings/core/src/method/secret_manager.rs @@ -5,7 +5,7 @@ use crypto::keys::bip44::Bip44; use derivative::Derivative; use iota_sdk::{ client::api::{GetAddressesOptions, PreparedTransactionDataDto}, - types::block::UnsignedBlockDto, + types::block::{protocol::ProtocolParameters, UnsignedBlockDto}, utils::serde::bip44::Bip44Def, }; use serde::{Deserialize, Serialize}; @@ -61,6 +61,7 @@ pub enum SecretManagerMethod { SignTransaction { /// Prepared transaction data prepared_transaction_data: PreparedTransactionDataDto, + protocol_parameters: ProtocolParameters, }, // Sign a block. #[serde(rename_all = "camelCase")] diff --git a/bindings/core/src/method_handler/secret_manager.rs b/bindings/core/src/method_handler/secret_manager.rs index 75d6ddbbfb..2cec14c23d 100644 --- a/bindings/core/src/method_handler/secret_manager.rs +++ b/bindings/core/src/method_handler/secret_manager.rs @@ -75,9 +75,13 @@ where } SecretManagerMethod::SignTransaction { prepared_transaction_data, + protocol_parameters, } => { let transaction = &secret_manager - .sign_transaction(PreparedTransactionData::try_from_dto(prepared_transaction_data)?) + .sign_transaction( + PreparedTransactionData::try_from_dto(prepared_transaction_data)?, + &protocol_parameters, + ) .await .map_err(iota_sdk::client::Error::from)?; Response::SignedTransaction(transaction.into()) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index f534b2e41b..24a7ea7dbc 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -932,6 +932,7 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> { let unspent_outputs = wallet.unspent_outputs(None).await; let slot_index = wallet.client().get_slot_index().await?; + let protocol_parameters = wallet.client().get_protocol_parameters().await?; let mut output_ids = Vec::new(); let mut amount = 0; @@ -947,9 +948,15 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> { output_ids.push(output_id); // Output might be associated with the address, but can't be unlocked by it, so we check that here. - let (required_address, _) = &output_data + let required_address = &output_data .output - .required_and_unlocked_address(slot_index, &output_id)?; + .required_address( + slot_index, + protocol_parameters.min_committable_age(), + protocol_parameters.max_committable_age(), + )? + // TODO + .unwrap(); if address.inner() == required_address { if let Some(nts) = output_data.output.native_tokens() { diff --git a/sdk/examples/wallet/offline_signing/2_sign_transaction.rs b/sdk/examples/wallet/offline_signing/2_sign_transaction.rs index a32f72c9ff..b36cc61f3b 100644 --- a/sdk/examples/wallet/offline_signing/2_sign_transaction.rs +++ b/sdk/examples/wallet/offline_signing/2_sign_transaction.rs @@ -16,7 +16,10 @@ use iota_sdk::{ }, secret::{stronghold::StrongholdSecretManager, SecretManage, SecretManager}, }, - types::{block::payload::SignedTransactionPayload, TryFromDto}, + types::{ + block::{payload::SignedTransactionPayload, protocol::protocol_parameters}, + TryFromDto, + }, wallet::Result, }; @@ -38,7 +41,7 @@ async fn main() -> Result<()> { // Signs prepared transaction offline. let unlocks = SecretManager::Stronghold(secret_manager) - .transaction_unlocks(&prepared_transaction_data) + .transaction_unlocks(&prepared_transaction_data, &protocol_parameters()) .await?; let signed_transaction = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; diff --git a/sdk/src/client/api/block_builder/input_selection/mod.rs b/sdk/src/client/api/block_builder/input_selection/mod.rs index 113513f103..eb4256b689 100644 --- a/sdk/src/client/api/block_builder/input_selection/mod.rs +++ b/sdk/src/client/api/block_builder/input_selection/mod.rs @@ -60,8 +60,13 @@ impl InputSelection { fn required_account_nft_addresses(&self, input: &InputSigningData) -> Result, Error> { let required_address = input .output - .required_and_unlocked_address(self.slot_index, input.output_id())? - .0; + .required_address( + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + )? + // TODO + .unwrap(); match required_address { Address::Ed25519(_) => Ok(None), @@ -223,17 +228,22 @@ impl InputSelection { // PANIC: safe to unwrap as non basic/account/foundry/nft outputs are already filtered out. let unlock_conditions = input.output.unlock_conditions().unwrap(); - if unlock_conditions.is_time_locked(self.slot_index) { + if unlock_conditions.is_timelocked(self.slot_index, self.protocol_parameters.min_committable_age()) { return false; } let required_address = input .output // Account transition is irrelevant here as we keep accounts anyway. - .required_and_unlocked_address(self.slot_index, input.output_id()) - // PANIC: safe to unwrap as non basic/account/foundry/nft outputs are already filtered out. + .required_address( + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + ) + // PANIC: safe to unwrap as non basic/alias/foundry/nft outputs are already filtered out. .unwrap() - .0; + // TODO + .unwrap(); if let Address::Restricted(restricted_address) = required_address { self.addresses.contains(restricted_address.address()) @@ -247,6 +257,8 @@ impl InputSelection { pub(crate) fn sort_input_signing_data( mut inputs: Vec, slot_index: SlotIndex, + min_committable_age: u32, + max_committable_age: u32, ) -> Result, Error> { // initially sort by output to make it deterministic // TODO: rethink this, we only need it deterministic for tests, for the protocol it doesn't matter, also there @@ -255,38 +267,44 @@ impl InputSelection { // filter for ed25519 address first let (mut sorted_inputs, account_nft_address_inputs): (Vec, Vec) = inputs.into_iter().partition(|input_signing_data| { - let (input_address, _) = input_signing_data + let input_address = input_signing_data .output - .required_and_unlocked_address(slot_index, input_signing_data.output_id()) - // PANIC: safe to unwrap, because we filtered irrelevant outputs out before + .required_address(slot_index, min_committable_age, max_committable_age) + // PANIC: safe to unwrap as non basic/alias/foundry/nft outputs are already filtered out. + .unwrap() + // TODO .unwrap(); input_address.is_ed25519() }); for input in account_nft_address_inputs { - let (input_address, _) = input + let required_address = input .output - .required_and_unlocked_address(slot_index, input.output_id())?; + .required_address(slot_index, min_committable_age, max_committable_age)? + // TODO + .unwrap(); - match sorted_inputs.iter().position(|input_signing_data| match input_address { - Address::Account(unlock_address) => { - if let Output::Account(account_output) = &input_signing_data.output { - *unlock_address.account_id() - == account_output.account_id_non_null(input_signing_data.output_id()) - } else { - false + match sorted_inputs + .iter() + .position(|input_signing_data| match required_address { + Address::Account(unlock_address) => { + if let Output::Account(account_output) = &input_signing_data.output { + *unlock_address.account_id() + == account_output.account_id_non_null(input_signing_data.output_id()) + } else { + false + } } - } - Address::Nft(unlock_address) => { - if let Output::Nft(nft_output) = &input_signing_data.output { - *unlock_address.nft_id() == nft_output.nft_id_non_null(input_signing_data.output_id()) - } else { - false + Address::Nft(unlock_address) => { + if let Output::Nft(nft_output) = &input_signing_data.output { + *unlock_address.nft_id() == nft_output.nft_id_non_null(input_signing_data.output_id()) + } else { + false + } } - } - _ => false, - }) { + _ => false, + }) { Some(position) => { // Insert after the output we need sorted_inputs.insert(position + 1, input); @@ -306,13 +324,16 @@ impl InputSelection { if let Some(account_or_nft_address) = account_or_nft_address { // Check for existing outputs for this address, and insert before match sorted_inputs.iter().position(|input_signing_data| { - let (input_address, _) = input_signing_data + let required_address = input_signing_data .output - .required_and_unlocked_address(slot_index, input.output_id()) - // PANIC: safe to unwrap, because we filtered irrelevant outputs out before + .required_address(slot_index, min_committable_age, max_committable_age) + // PANIC: safe to unwrap as non basic/alias/foundry/nft outputs are already filtered + // out. + .unwrap() + // TODO .unwrap(); - input_address == account_or_nft_address + required_address == account_or_nft_address }) { Some(position) => { // Insert before the output with this address required for unlocking @@ -382,7 +403,12 @@ impl InputSelection { self.validate_transitions()?; Ok(Selected { - inputs: Self::sort_input_signing_data(self.selected_inputs, self.slot_index)?, + inputs: Self::sort_input_signing_data( + self.selected_inputs, + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + )?, outputs: self.outputs, remainder, }) diff --git a/sdk/src/client/api/block_builder/input_selection/remainder.rs b/sdk/src/client/api/block_builder/input_selection/remainder.rs index e7442dcdd0..1e90d113c5 100644 --- a/sdk/src/client/api/block_builder/input_selection/remainder.rs +++ b/sdk/src/client/api/block_builder/input_selection/remainder.rs @@ -24,9 +24,15 @@ impl InputSelection { if let Some(remainder_address) = &self.remainder_address { // Search in inputs for the Bip44 chain for the remainder address, so the ledger can regenerate it for input in self.available_inputs.iter().chain(self.selected_inputs.iter()) { - let (required_address, _) = input + let required_address = input .output - .required_and_unlocked_address(self.slot_index, input.output_id())?; + .required_address( + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + )? + // TODO + .unwrap(); if &required_address == remainder_address { return Ok(Some((remainder_address.clone(), input.chain))); @@ -38,8 +44,13 @@ impl InputSelection { for input in &self.selected_inputs { let required_address = input .output - .required_and_unlocked_address(self.slot_index, input.output_id())? - .0; + .required_address( + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + )? + // TODO + .unwrap(); if required_address.is_ed25519() { return Ok(Some((required_address, input.chain))); diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs b/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs index de78c4aaa9..96e798fdce 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs +++ b/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs @@ -347,7 +347,14 @@ impl InputSelection { if let Output::Basic(output) = &input.output { output .unlock_conditions() - .locked_address(output.address(), self.slot_index) + .locked_address( + output.address(), + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + ) + // TODO + .unwrap() .is_ed25519() } else { false @@ -362,7 +369,14 @@ impl InputSelection { if let Output::Basic(output) = &input.output { !output .unlock_conditions() - .locked_address(output.address(), self.slot_index) + .locked_address( + output.address(), + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + ) + // TODO + .unwrap() .is_ed25519() } else { false diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/ed25519.rs b/sdk/src/client/api/block_builder/input_selection/requirement/ed25519.rs index ce6fb8ee6e..6530092816 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/ed25519.rs +++ b/sdk/src/client/api/block_builder/input_selection/requirement/ed25519.rs @@ -10,9 +10,15 @@ impl InputSelection { // PANIC: safe to unwrap as outputs with no address have been filtered out already. let required_address = input .output - .required_and_unlocked_address(self.slot_index, input.output_id()) + .required_address( + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + ) + // PANIC: safe to unwrap as outputs with no address have been filtered out already. .unwrap() - .0; + // TODO + .unwrap(); &required_address == address } @@ -20,9 +26,15 @@ impl InputSelection { // Checks if an available input can unlock a given ED25519 address. // In case an account input is selected, also tells if it needs to be state or governance transitioned. fn available_has_ed25519_address(&self, input: &InputSigningData, address: &Address) -> bool { - let (required_address, _) = input + let required_address = input .output - .required_and_unlocked_address(self.slot_index, input.output_id()) + .required_address( + self.slot_index, + self.protocol_parameters.min_committable_age(), + self.protocol_parameters.max_committable_age(), + ) + .unwrap() + // TODO .unwrap(); &required_address == address diff --git a/sdk/src/client/api/block_builder/transaction.rs b/sdk/src/client/api/block_builder/transaction.rs index be2141f4cc..07b548db83 100644 --- a/sdk/src/client/api/block_builder/transaction.rs +++ b/sdk/src/client/api/block_builder/transaction.rs @@ -10,6 +10,7 @@ use crate::{ types::block::{ output::{Output, OutputId}, payload::signed_transaction::{SignedTransactionPayload, Transaction}, + protocol::ProtocolParameters, semantic::{SemanticValidationContext, TransactionFailureReason}, signature::Ed25519Signature, BlockId, SignedBlock, @@ -29,6 +30,7 @@ const REFERENCE_ACCOUNT_NFT_UNLOCK_LENGTH: usize = 1 + 2; pub fn verify_semantic( input_signing_data: &[InputSigningData], transaction_payload: &SignedTransactionPayload, + protocol_parameters: ProtocolParameters, ) -> crate::client::Result> { let transaction_id = transaction_payload.transaction().id(); let inputs = input_signing_data @@ -37,6 +39,7 @@ pub fn verify_semantic( .collect::>(); let context = SemanticValidationContext::new( + protocol_parameters, transaction_payload.transaction(), &transaction_id, &inputs, diff --git a/sdk/src/client/secret/ledger_nano.rs b/sdk/src/client/secret/ledger_nano.rs index e627293adf..1c10148633 100644 --- a/sdk/src/client/secret/ledger_nano.rs +++ b/sdk/src/client/secret/ledger_nano.rs @@ -29,6 +29,7 @@ use crate::{ address::{AccountAddress, Address, AnchorAddress, Ed25519Address, NftAddress}, output::Output, payload::signed_transaction::SignedTransactionPayload, + protocol::ProtocolParameters, signature::{Ed25519Signature, Signature}, unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, Error as BlockError, @@ -241,6 +242,7 @@ impl SecretManage for LedgerSecretManager { async fn transaction_unlocks( &self, prepared_transaction: &PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result::Error> { let mut input_bip32_indices = Vec::new(); let mut coin_type = None; @@ -392,7 +394,7 @@ impl SecretManage for LedgerSecretManager { // With blind signing the ledger only returns SignatureUnlocks, so we might have to merge them with // Account/Nft/Reference unlocks if blind_signing { - unlocks = merge_unlocks(prepared_transaction, unlocks.into_iter())?; + unlocks = merge_unlocks(prepared_transaction, unlocks.into_iter(), protocol_parameters)?; } Ok(Unlocks::new(unlocks)?) @@ -401,8 +403,9 @@ impl SecretManage for LedgerSecretManager { async fn sign_transaction( &self, prepared_transaction_data: PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { - super::default_sign_transaction(self, prepared_transaction_data).await + super::default_sign_transaction(self, prepared_transaction_data, protocol_parameters).await } } @@ -508,6 +511,7 @@ impl LedgerSecretManager { fn merge_unlocks( prepared_transaction_data: &PreparedTransactionData, mut unlocks: impl Iterator, + protocol_parameters: &ProtocolParameters, ) -> Result, Error> { let slot_index = prepared_transaction_data.transaction.creation_slot(); let transaction_signing_hash = prepared_transaction_data.transaction.signing_hash(); @@ -518,14 +522,20 @@ fn merge_unlocks( // Assuming inputs_data is ordered by address type for (current_block_index, input) in prepared_transaction_data.inputs_data.iter().enumerate() { // Get the address that is required to unlock the input - let (input_address, _) = input + let required_address = input .output - .required_and_unlocked_address(slot_index, input.output_metadata.output_id())?; + .required_address( + slot_index, + protocol_parameters.min_committable_age(), + protocol_parameters.max_committable_age(), + )? + // TODO + .unwrap(); // Check if we already added an [Unlock] for this address - match block_indexes.get(&input_address) { + match block_indexes.get(&required_address) { // If we already have an [Unlock] for this address, add a [Unlock] based on the address type - Some(block_index) => match input_address { + Some(block_index) => match required_address { Address::Ed25519(_ed25519) => { merged_unlocks.push(Unlock::Reference(ReferenceUnlock::new(*block_index as u16)?)); } @@ -540,7 +550,7 @@ fn merge_unlocks( // We can only sign ed25519 addresses and block_indexes needs to contain the account or nft // address already at this point, because the reference index needs to be lower // than the current block index - if !input_address.is_ed25519() { + if !required_address.is_ed25519() { return Err(Error::MissingInputWithEd25519Address); } @@ -548,7 +558,7 @@ fn merge_unlocks( if let Unlock::Signature(signature_unlock) = &unlock { let Signature::Ed25519(ed25519_signature) = signature_unlock.signature(); - let ed25519_address = match input_address { + let ed25519_address = match required_address { Address::Ed25519(ed25519_address) => ed25519_address, _ => return Err(Error::MissingInputWithEd25519Address), }; @@ -559,7 +569,7 @@ fn merge_unlocks( // Add the ed25519 address to the block_indexes, so it gets referenced if further inputs have // the same address in their unlock condition - block_indexes.insert(input_address, current_block_index); + block_indexes.insert(required_address, current_block_index); } } diff --git a/sdk/src/client/secret/mnemonic.rs b/sdk/src/client/secret/mnemonic.rs index 2c7b3af261..60b5e7c6ea 100644 --- a/sdk/src/client/secret/mnemonic.rs +++ b/sdk/src/client/secret/mnemonic.rs @@ -20,8 +20,8 @@ use super::{GenerateAddressOptions, SecretManage}; use crate::{ client::{api::PreparedTransactionData, Client, Error}, types::block::{ - address::Ed25519Address, payload::signed_transaction::SignedTransactionPayload, signature::Ed25519Signature, - unlock::Unlocks, + address::Ed25519Address, payload::signed_transaction::SignedTransactionPayload, protocol::ProtocolParameters, + signature::Ed25519Signature, unlock::Unlocks, }, }; @@ -125,15 +125,17 @@ impl SecretManage for MnemonicSecretManager { async fn transaction_unlocks( &self, prepared_transaction_data: &PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { - super::default_transaction_unlocks(self, prepared_transaction_data).await + super::default_transaction_unlocks(self, prepared_transaction_data, protocol_parameters).await } async fn sign_transaction( &self, prepared_transaction_data: PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { - super::default_sign_transaction(self, prepared_transaction_data).await + super::default_sign_transaction(self, prepared_transaction_data, protocol_parameters).await } } diff --git a/sdk/src/client/secret/mod.rs b/sdk/src/client/secret/mod.rs index 2089dafeaf..cbb8405648 100644 --- a/sdk/src/client/secret/mod.rs +++ b/sdk/src/client/secret/mod.rs @@ -55,6 +55,7 @@ use crate::{ core::UnsignedBlock, output::Output, payload::SignedTransactionPayload, + protocol::ProtocolParameters, signature::{Ed25519Signature, Signature}, unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, Error as BlockError, SignedBlock, @@ -105,11 +106,13 @@ pub trait SecretManage: Send + Sync { async fn transaction_unlocks( &self, prepared_transaction_data: &PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result; async fn sign_transaction( &self, prepared_transaction_data: PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result; } @@ -403,19 +406,28 @@ impl SecretManage for SecretManager { async fn transaction_unlocks( &self, prepared_transaction_data: &PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { match self { #[cfg(feature = "stronghold")] - Self::Stronghold(secret_manager) => { - Ok(secret_manager.transaction_unlocks(prepared_transaction_data).await?) - } + Self::Stronghold(secret_manager) => Ok(secret_manager + .transaction_unlocks(prepared_transaction_data, protocol_parameters) + .await?), #[cfg(feature = "ledger_nano")] - Self::LedgerNano(secret_manager) => { - Ok(secret_manager.transaction_unlocks(prepared_transaction_data).await?) + Self::LedgerNano(secret_manager) => Ok(secret_manager + .transaction_unlocks(prepared_transaction_data, protocol_parameters) + .await?), + Self::Mnemonic(secret_manager) => { + secret_manager + .transaction_unlocks(prepared_transaction_data, protocol_parameters) + .await } - Self::Mnemonic(secret_manager) => secret_manager.transaction_unlocks(prepared_transaction_data).await, #[cfg(feature = "private_key_secret_manager")] - Self::PrivateKey(secret_manager) => secret_manager.transaction_unlocks(prepared_transaction_data).await, + Self::PrivateKey(secret_manager) => { + secret_manager + .transaction_unlocks(prepared_transaction_data, protocol_parameters) + .await + } Self::Placeholder => Err(Error::PlaceholderSecretManager), } } @@ -423,15 +435,28 @@ impl SecretManage for SecretManager { async fn sign_transaction( &self, prepared_transaction_data: PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { match self { #[cfg(feature = "stronghold")] - Self::Stronghold(secret_manager) => Ok(secret_manager.sign_transaction(prepared_transaction_data).await?), + Self::Stronghold(secret_manager) => Ok(secret_manager + .sign_transaction(prepared_transaction_data, protocol_parameters) + .await?), #[cfg(feature = "ledger_nano")] - Self::LedgerNano(secret_manager) => Ok(secret_manager.sign_transaction(prepared_transaction_data).await?), - Self::Mnemonic(secret_manager) => secret_manager.sign_transaction(prepared_transaction_data).await, + Self::LedgerNano(secret_manager) => Ok(secret_manager + .sign_transaction(prepared_transaction_data, protocol_parameters) + .await?), + Self::Mnemonic(secret_manager) => { + secret_manager + .sign_transaction(prepared_transaction_data, protocol_parameters) + .await + } #[cfg(feature = "private_key_secret_manager")] - Self::PrivateKey(secret_manager) => secret_manager.sign_transaction(prepared_transaction_data).await, + Self::PrivateKey(secret_manager) => { + secret_manager + .sign_transaction(prepared_transaction_data, protocol_parameters) + .await + } Self::Placeholder => Err(Error::PlaceholderSecretManager), } } @@ -499,6 +524,7 @@ impl SecretManager { pub(crate) async fn default_transaction_unlocks( secret_manager: &M, prepared_transaction_data: &PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> crate::client::Result where crate::client::Error: From, @@ -511,14 +537,20 @@ where // Assuming inputs_data is ordered by address type for (current_block_index, input) in prepared_transaction_data.inputs_data.iter().enumerate() { // Get the address that is required to unlock the input - let (input_address, _) = input + let required_address = input .output - .required_and_unlocked_address(slot_index, input.output_metadata.output_id())?; + .required_address( + slot_index, + protocol_parameters.min_committable_age(), + protocol_parameters.max_committable_age(), + )? + // TODO + .unwrap(); // Check if we already added an [Unlock] for this address - match block_indexes.get(&input_address) { + match block_indexes.get(&required_address) { // If we already have an [Unlock] for this address, add a [Unlock] based on the address type - Some(block_index) => match input_address { + Some(block_index) => match required_address { Address::Ed25519(_ed25519) => { blocks.push(Unlock::Reference(ReferenceUnlock::new(*block_index as u16)?)); } @@ -531,7 +563,7 @@ where // We can only sign ed25519 addresses and block_indexes needs to contain the account or nft // address already at this point, because the reference index needs to be lower // than the current block index - if !input_address.is_ed25519() { + if !required_address.is_ed25519() { Err(InputSelectionError::MissingInputWithEd25519Address)?; } @@ -544,7 +576,7 @@ where // Add the ed25519 address to the block_indexes, so it gets referenced if further inputs have // the same address in their unlock condition - block_indexes.insert(input_address, current_block_index); + block_indexes.insert(required_address, current_block_index); } } @@ -570,13 +602,16 @@ where pub(crate) async fn default_sign_transaction( secret_manager: &M, prepared_transaction_data: PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> crate::client::Result where crate::client::Error: From, { log::debug!("[sign_transaction] {:?}", prepared_transaction_data); - let unlocks = secret_manager.transaction_unlocks(&prepared_transaction_data).await?; + let unlocks = secret_manager + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) + .await?; let PreparedTransactionData { transaction, @@ -587,7 +622,7 @@ where validate_signed_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&inputs_data, &tx_payload)?; + let conflict = verify_semantic(&inputs_data, &tx_payload, protocol_parameters.clone())?; if let Some(conflict) = conflict { log::debug!("[sign_transaction] conflict: {conflict:?} for {:#?}", tx_payload); diff --git a/sdk/src/client/secret/private_key.rs b/sdk/src/client/secret/private_key.rs index 3f4b9fe7b1..2cdc2f3823 100644 --- a/sdk/src/client/secret/private_key.rs +++ b/sdk/src/client/secret/private_key.rs @@ -20,8 +20,8 @@ use super::{GenerateAddressOptions, SecretManage}; use crate::{ client::{api::PreparedTransactionData, Error}, types::block::{ - address::Ed25519Address, payload::signed_transaction::SignedTransactionPayload, signature::Ed25519Signature, - unlock::Unlocks, + address::Ed25519Address, payload::signed_transaction::SignedTransactionPayload, protocol::ProtocolParameters, + signature::Ed25519Signature, unlock::Unlocks, }, }; @@ -85,15 +85,17 @@ impl SecretManage for PrivateKeySecretManager { async fn transaction_unlocks( &self, prepared_transaction_data: &PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { - super::default_transaction_unlocks(self, prepared_transaction_data).await + super::default_transaction_unlocks(self, prepared_transaction_data, protocol_parameters).await } async fn sign_transaction( &self, prepared_transaction_data: PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { - super::default_sign_transaction(self, prepared_transaction_data).await + super::default_sign_transaction(self, prepared_transaction_data, protocol_parameters).await } } diff --git a/sdk/src/client/stronghold/secret.rs b/sdk/src/client/stronghold/secret.rs index bdcc3e1dd3..c7defc1447 100644 --- a/sdk/src/client/stronghold/secret.rs +++ b/sdk/src/client/stronghold/secret.rs @@ -36,8 +36,8 @@ use crate::{ stronghold::Error, }, types::block::{ - address::Ed25519Address, payload::signed_transaction::SignedTransactionPayload, signature::Ed25519Signature, - unlock::Unlocks, + address::Ed25519Address, payload::signed_transaction::SignedTransactionPayload, protocol::ProtocolParameters, + signature::Ed25519Signature, unlock::Unlocks, }, }; @@ -284,15 +284,17 @@ impl SecretManage for StrongholdAdapter { async fn transaction_unlocks( &self, prepared_transaction_data: &PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { - crate::client::secret::default_transaction_unlocks(self, prepared_transaction_data).await + crate::client::secret::default_transaction_unlocks(self, prepared_transaction_data, protocol_parameters).await } async fn sign_transaction( &self, prepared_transaction_data: PreparedTransactionData, + protocol_parameters: &ProtocolParameters, ) -> Result { - crate::client::secret::default_sign_transaction(self, prepared_transaction_data).await + crate::client::secret::default_sign_transaction(self, prepared_transaction_data, protocol_parameters).await } } diff --git a/sdk/src/types/block/context_input/commitment.rs b/sdk/src/types/block/context_input/commitment.rs index 23b9bcffe2..be6a22f6d3 100644 --- a/sdk/src/types/block/context_input/commitment.rs +++ b/sdk/src/types/block/context_input/commitment.rs @@ -3,7 +3,7 @@ use derive_more::{Display, From}; -use crate::types::block::slot::SlotCommitmentId; +use crate::types::block::slot::{SlotCommitmentId, SlotIndex}; /// A Commitment Context Input references a commitment to a certain slot. #[derive(Clone, Copy, Display, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, From, packable::Packable)] @@ -18,10 +18,15 @@ impl CommitmentContextInput { Self(commitment_id) } - /// Returns the commitment id of the [`CommitmentContextInput`]. - pub fn commitment_id(&self) -> SlotCommitmentId { + /// Returns the slot commitment id of the [`CommitmentContextInput`]. + pub fn slot_commitment_id(&self) -> SlotCommitmentId { self.0 } + + /// Returns the slot index of the [`CommitmentContextInput`]. + pub fn slot_index(&self) -> SlotIndex { + self.0.slot_index() + } } #[cfg(feature = "serde")] @@ -43,7 +48,7 @@ mod dto { fn from(value: &CommitmentContextInput) -> Self { Self { kind: CommitmentContextInput::KIND, - commitment_id: value.commitment_id(), + commitment_id: value.slot_commitment_id(), } } } diff --git a/sdk/src/types/block/context_input/mod.rs b/sdk/src/types/block/context_input/mod.rs index 016931f09d..f0bb9892c9 100644 --- a/sdk/src/types/block/context_input/mod.rs +++ b/sdk/src/types/block/context_input/mod.rs @@ -79,7 +79,7 @@ mod tests { .unwrap(); assert!(commitment.is_commitment()); assert_eq!( - commitment.as_commitment().commitment_id().to_string(), + commitment.as_commitment().slot_commitment_id().to_string(), "0xedf5f572c58ddf4b4f9567d82bf96689cc68b730df796d822b4b9fb643f5efda4f9567d8" ); diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index e389d899c8..7c5d16c676 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -413,7 +413,14 @@ impl AccountOutput { context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() - .locked_address(self.address(), context.transaction.creation_slot()) + .locked_address( + self.address(), + context.transaction.creation_slot(), + context.protocol_parameters.min_committable_age(), + context.protocol_parameters.max_committable_age(), + ) + // TODO + .unwrap() .unlock(unlock, context)?; let account_id = if self.account_id().is_null() { diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 0eb2281963..3e986eeb93 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -288,7 +288,14 @@ impl BasicOutput { context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() - .locked_address(self.address(), context.transaction.creation_slot()) + .locked_address( + self.address(), + context.transaction.creation_slot(), + context.protocol_parameters.min_committable_age(), + context.protocol_parameters.max_committable_age(), + ) + // TODO + .unwrap() .unlock(unlock, context) } diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 1562a35169..3c36f72ff6 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -334,7 +334,14 @@ impl DelegationOutput { context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() - .locked_address(self.address(), context.transaction.creation_slot()) + .locked_address( + self.address(), + context.transaction.creation_slot(), + context.protocol_parameters.min_committable_age(), + context.protocol_parameters.max_committable_age(), + ) + // TODO + .unwrap() .unlock(unlock, context) } diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 74e285bba3..999245a7a4 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -263,45 +263,33 @@ impl Output { crate::def_is_as_opt!(Output: Basic, Account, Foundry, Nft, Delegation, Anchor); - /// Returns the address that is required to unlock this [`Output`] and the account or nft address that gets - /// unlocked by it, if it's an account or nft. + /// Returns the address that is required to unlock this [`Output`]. /// If no `account_transition` has been provided, assumes a state transition. - pub fn required_and_unlocked_address( + pub fn required_address( &self, - slot_index: SlotIndex, - output_id: &OutputId, - ) -> Result<(Address, Option
), Error> { + slot_index: impl Into, + min_committable_age: impl Into, + max_committable_age: impl Into, + ) -> Result, Error> { match self { - Self::Basic(output) => Ok(( - output - .unlock_conditions() - .locked_address(output.address(), slot_index) - .clone(), - None, - )), - Self::Account(output) => Ok(( - output - .unlock_conditions() - .locked_address(output.address(), slot_index) - .clone(), - Some(Address::Account(output.account_address(output_id))), - )), + Self::Basic(output) => Ok(output + .unlock_conditions() + .locked_address(output.address(), slot_index, min_committable_age, max_committable_age) + .cloned()), + Self::Account(output) => Ok(output + .unlock_conditions() + .locked_address(output.address(), slot_index, min_committable_age, max_committable_age) + .cloned()), Self::Anchor(_) => Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), - Self::Foundry(output) => Ok((Address::Account(*output.account_address()), None)), - Self::Nft(output) => Ok(( - output - .unlock_conditions() - .locked_address(output.address(), slot_index) - .clone(), - Some(Address::Nft(output.nft_address(output_id))), - )), - Self::Delegation(output) => Ok(( - output - .unlock_conditions() - .locked_address(output.address(), slot_index) - .clone(), - None, - )), + Self::Foundry(output) => Ok(Some(Address::Account(*output.account_address()))), + Self::Nft(output) => Ok(output + .unlock_conditions() + .locked_address(output.address(), slot_index, min_committable_age, max_committable_age) + .cloned()), + Self::Delegation(output) => Ok(output + .unlock_conditions() + .locked_address(output.address(), slot_index, min_committable_age, max_committable_age) + .cloned()), } } diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index 599d451d07..59d0c9a706 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -394,7 +394,14 @@ impl NftOutput { context: &mut SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() - .locked_address(self.address(), context.transaction.creation_slot()) + .locked_address( + self.address(), + context.transaction.creation_slot(), + context.protocol_parameters.min_committable_age(), + context.protocol_parameters.max_committable_age(), + ) + // TODO + .unwrap() .unlock(unlock, context)?; let nft_id = if self.nft_id().is_null() { diff --git a/sdk/src/types/block/output/unlock_condition/expiration.rs b/sdk/src/types/block/output/unlock_condition/expiration.rs index 73860f81cc..de511aac40 100644 --- a/sdk/src/types/block/output/unlock_condition/expiration.rs +++ b/sdk/src/types/block/output/unlock_condition/expiration.rs @@ -45,9 +45,24 @@ impl ExpirationUnlockCondition { self.slot_index } + /// Checks whether the expiration is expired. + pub fn is_expired(&self, slot_index: impl Into, min_committable_age: impl Into) -> bool { + (slot_index.into() + min_committable_age.into()) >= self.slot_index + } + /// Returns the return address if the condition has expired. - pub fn return_address_expired(&self, slot_index: SlotIndex) -> Option<&Address> { - if slot_index >= self.slot_index() { + pub fn return_address_expired<'a>( + &'a self, + address: &'a Address, + slot_index: impl Into, + min_committable_age: impl Into, + max_committable_age: impl Into, + ) -> Option<&'a Address> { + let slot_index = slot_index.into(); + + if self.slot_index() > (slot_index + max_committable_age.into()) { + Some(address) + } else if self.slot_index() <= (slot_index + min_committable_age.into()) { Some(&self.return_address) } else { None diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index e11f6d36a0..f4f95bac1e 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -294,6 +294,14 @@ impl UnlockConditions { .map(UnlockCondition::as_timelock) } + /// Checks whether a timelock exists and is still relevant. + #[inline(always)] + pub fn is_timelocked(&self, slot_index: impl Into, min_committable_age: impl Into) -> bool { + self.timelock().map_or(false, |timelock| { + timelock.is_timelocked(slot_index, min_committable_age) + }) + } + /// Gets a reference to an [`ExpirationUnlockCondition`], if any. #[inline(always)] pub fn expiration(&self) -> Option<&ExpirationUnlockCondition> { @@ -301,6 +309,14 @@ impl UnlockConditions { .map(UnlockCondition::as_expiration) } + /// Checks whether an expiration exists and is expired. + #[inline(always)] + pub fn is_expired(&self, slot_index: impl Into, min_committable_age: impl Into) -> bool { + self.expiration().map_or(false, |expiration| { + expiration.is_expired(slot_index, min_committable_age) + }) + } + /// Gets a reference to a [`StateControllerAddressUnlockCondition`], if any. #[inline(always)] pub fn state_controller_address(&self) -> Option<&StateControllerAddressUnlockCondition> { @@ -324,28 +340,18 @@ impl UnlockConditions { /// Returns the address to be unlocked. #[inline(always)] - pub fn locked_address<'a>(&'a self, address: &'a Address, slot_index: SlotIndex) -> &'a Address { - self.expiration() - .and_then(|e| e.return_address_expired(slot_index)) - .unwrap_or(address) - } - - /// Returns whether a time lock exists and is still relevant. - #[inline(always)] - pub fn is_time_locked(&self, slot_index: impl Into) -> bool { - let slot_index = slot_index.into(); - - self.timelock() - .map_or(false, |timelock| slot_index < timelock.slot_index()) - } - - /// Returns whether an expiration exists and is expired. - #[inline(always)] - pub fn is_expired(&self, slot_index: impl Into) -> bool { - let slot_index = slot_index.into(); - - self.expiration() - .map_or(false, |expiration| slot_index >= expiration.slot_index()) + pub fn locked_address<'a>( + &'a self, + address: &'a Address, + slot_index: impl Into, + min_committable_age: impl Into, + max_committable_age: impl Into, + ) -> Option<&'a Address> { + if let Some(expiration) = self.expiration() { + expiration.return_address_expired(address, slot_index, min_committable_age, max_committable_age) + } else { + Some(address) + } } } diff --git a/sdk/src/types/block/output/unlock_condition/timelock.rs b/sdk/src/types/block/output/unlock_condition/timelock.rs index aeffb7ab5d..ba61c56bcd 100644 --- a/sdk/src/types/block/output/unlock_condition/timelock.rs +++ b/sdk/src/types/block/output/unlock_condition/timelock.rs @@ -29,6 +29,11 @@ impl TimelockUnlockCondition { pub fn slot_index(&self) -> SlotIndex { self.0 } + + /// Checks whether the timelock is still relevant. + pub fn is_timelocked(&self, slot_index: impl Into, min_committable_age: impl Into) -> bool { + (slot_index.into() + min_committable_age.into()) < self.0 + } } #[inline] diff --git a/sdk/src/types/block/payload/signed_transaction/transaction.rs b/sdk/src/types/block/payload/signed_transaction/transaction.rs index e8b05e5565..004dfd8bc3 100644 --- a/sdk/src/types/block/payload/signed_transaction/transaction.rs +++ b/sdk/src/types/block/payload/signed_transaction/transaction.rs @@ -362,6 +362,7 @@ fn verify_context_inputs(context_inputs: &[ContextInput]) -> Result<(), Error> { let mut reward_index_set = HashSet::new(); let mut bic_account_id_set = HashSet::new(); + for input in context_inputs.iter() { match input { ContextInput::BlockIssuanceCredit(bic) => { diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index 6359cee8e7..1fd2c94e03 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -14,6 +14,7 @@ use crate::types::block::{ UnlockCondition, }, payload::signed_transaction::{Transaction, TransactionCapabilityFlag, TransactionId, TransactionSigningHash}, + protocol::ProtocolParameters, unlock::Unlocks, Error, }; @@ -191,6 +192,7 @@ impl TryFrom for TransactionFailureReason { /// pub struct SemanticValidationContext<'a> { + pub(crate) protocol_parameters: ProtocolParameters, pub(crate) transaction: &'a Transaction, pub(crate) transaction_signing_hash: TransactionSigningHash, pub(crate) inputs: &'a [(&'a OutputId, &'a Output)], @@ -211,12 +213,14 @@ pub struct SemanticValidationContext<'a> { impl<'a> SemanticValidationContext<'a> { /// pub fn new( + protocol_parameters: ProtocolParameters, transaction: &'a Transaction, transaction_id: &TransactionId, inputs: &'a [(&'a OutputId, &'a Output)], unlocks: &'a Unlocks, ) -> Self { Self { + protocol_parameters, transaction, transaction_signing_hash: transaction.signing_hash(), inputs, @@ -301,227 +305,237 @@ impl<'a> SemanticValidationContext<'a> { return Ok(Some(conflict)); } - if unlock_conditions.is_time_locked(self.transaction.creation_slot()) { - return Ok(Some(TransactionFailureReason::TimelockNotExpired)); + if let Some(timelock) = unlock_conditions.timelock() { + if let Some(commitment) = self.transaction.context_inputs().iter().find(|c| c.is_commitment()) { + if timelock.is_timelocked( + commitment.as_commitment().slot_index(), + self.protocol_parameters.min_committable_age(), + ) { + return Ok(Some(TransactionFailureReason::TimelockNotExpired)); + } + } else { + // TODO return an error + } } - if !unlock_conditions.is_expired(self.transaction.creation_slot()) { - if let Some(storage_deposit_return) = unlock_conditions.storage_deposit_return() { - let amount = self - .storage_deposit_returns - .entry(storage_deposit_return.return_address().clone()) - .or_default(); - - *amount = amount - .checked_add(storage_deposit_return.amount()) - .ok_or(Error::StorageDepositReturnOverflow)?; + if let Some(expiration) = unlock_conditions.expiration() { + if let Some(commitment) = self.transaction.context_inputs().iter().find(|c| c.is_commitment()) { + // TODO check is_deadzoned ? + if !expiration.is_expired( + commitment.as_commitment().slot_index(), + self.protocol_parameters.min_committable_age(), + ) { + if let Some(storage_deposit_return) = unlock_conditions.storage_deposit_return() { + let amount = self + .storage_deposit_returns + .entry(storage_deposit_return.return_address().clone()) + .or_default(); + + *amount = amount + .checked_add(storage_deposit_return.amount()) + .ok_or(Error::StorageDepositReturnOverflow)?; + } + } + } else { + // TODO return an error } - } - self.input_amount = self - .input_amount - .checked_add(amount) - .ok_or(Error::ConsumedAmountOverflow)?; + self.input_amount = self + .input_amount + .checked_add(amount) + .ok_or(Error::ConsumedAmountOverflow)?; - self.input_mana = self.input_mana.checked_add(mana).ok_or(Error::ConsumedManaOverflow)?; + self.input_mana = self.input_mana.checked_add(mana).ok_or(Error::ConsumedManaOverflow)?; - if let Some(consumed_native_tokens) = consumed_native_tokens { - for native_token in consumed_native_tokens.iter() { - let native_token_amount = self.input_native_tokens.entry(*native_token.token_id()).or_default(); + if let Some(consumed_native_tokens) = consumed_native_tokens { + for native_token in consumed_native_tokens.iter() { + let native_token_amount = self.input_native_tokens.entry(*native_token.token_id()).or_default(); - *native_token_amount = native_token_amount - .checked_add(native_token.amount()) - .ok_or(Error::ConsumedNativeTokensAmountOverflow)?; + *native_token_amount = native_token_amount + .checked_add(native_token.amount()) + .ok_or(Error::ConsumedNativeTokensAmountOverflow)?; + } } } - } - // Validation of outputs. - for created_output in self.transaction.outputs() { - let (amount, mana, created_native_tokens, features) = match created_output { - Output::Basic(output) => { - if let Some(address) = output.simple_deposit_address() { - let amount = self.simple_deposits.entry(address.clone()).or_default(); + // Validation of outputs. + for created_output in self.transaction.outputs() { + let (amount, mana, created_native_tokens, features) = match created_output { + Output::Basic(output) => { + if let Some(address) = output.simple_deposit_address() { + let amount = self.simple_deposits.entry(address.clone()).or_default(); - *amount = amount - .checked_add(output.amount()) - .ok_or(Error::CreatedAmountOverflow)?; - } + *amount = amount + .checked_add(output.amount()) + .ok_or(Error::CreatedAmountOverflow)?; + } - ( + ( + output.amount(), + output.mana(), + Some(output.native_tokens()), + Some(output.features()), + ) + } + Output::Account(output) => ( output.amount(), output.mana(), Some(output.native_tokens()), Some(output.features()), - ) - } - Output::Account(output) => ( - output.amount(), - output.mana(), - Some(output.native_tokens()), - Some(output.features()), - ), - Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), - Output::Foundry(output) => ( - output.amount(), - 0, - Some(output.native_tokens()), - Some(output.features()), - ), - Output::Nft(output) => ( - output.amount(), - output.mana(), - Some(output.native_tokens()), - Some(output.features()), - ), - Output::Delegation(output) => (output.amount(), 0, None, None), - }; - - if let Some(unlock_conditions) = created_output.unlock_conditions() { - // Check the possibly restricted address-containing conditions - let addresses = unlock_conditions - .iter() - .filter_map(|uc| match uc { - UnlockCondition::Address(uc) => Some(uc.address()), - UnlockCondition::Expiration(uc) => Some(uc.return_address()), - UnlockCondition::StateControllerAddress(uc) => Some(uc.address()), - UnlockCondition::GovernorAddress(uc) => Some(uc.address()), - _ => None, - }) - .filter_map(Address::as_restricted_opt); - for address in addresses { - if created_output.native_tokens().map(|t| t.len()).unwrap_or_default() > 0 - && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + ), + Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), + Output::Foundry(output) => ( + output.amount(), + 0, + Some(output.native_tokens()), + Some(output.features()), + ), + Output::Nft(output) => ( + output.amount(), + output.mana(), + Some(output.native_tokens()), + Some(output.features()), + ), + Output::Delegation(output) => (output.amount(), 0, None, None), + }; + + if let Some(unlock_conditions) = created_output.unlock_conditions() { + // Check the possibly restricted address-containing conditions + let addresses = unlock_conditions + .iter() + .filter_map(|uc| match uc { + UnlockCondition::Address(uc) => Some(uc.address()), + UnlockCondition::Expiration(uc) => Some(uc.return_address()), + UnlockCondition::StateControllerAddress(uc) => Some(uc.address()), + UnlockCondition::GovernorAddress(uc) => Some(uc.address()), + _ => None, + }) + .filter_map(Address::as_restricted_opt); + for address in addresses { + if created_output.native_tokens().map(|t| t.len()).unwrap_or_default() > 0 + && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if created_output.mana() > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if unlock_conditions.timelock().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if unlock_conditions.expiration().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if unlock_conditions.storage_deposit_return().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } + + if match &created_output { + Output::Account(_) => !address.has_capability(AddressCapabilityFlag::AccountOutputs), + Output::Anchor(_) => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), + Output::Nft(_) => !address.has_capability(AddressCapabilityFlag::NftOutputs), + Output::Delegation(_) => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), + _ => false, + } { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + } } + } - if created_output.mana() > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + if let Some(sender) = features.and_then(|f| f.sender()) { + if !self.unlocked_addresses.contains(sender.address()) { + return Ok(Some(TransactionFailureReason::SenderNotUnlocked)); } + } - if unlock_conditions.timelock().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } + self.output_amount = self + .output_amount + .checked_add(amount) + .ok_or(Error::CreatedAmountOverflow)?; - if unlock_conditions.expiration().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } + self.output_mana = self.output_mana.checked_add(mana).ok_or(Error::CreatedManaOverflow)?; - if unlock_conditions.storage_deposit_return().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } + if let Some(created_native_tokens) = created_native_tokens { + for native_token in created_native_tokens.iter() { + let native_token_amount = + self.output_native_tokens.entry(*native_token.token_id()).or_default(); - if match &created_output { - Output::Account(_) => !address.has_capability(AddressCapabilityFlag::AccountOutputs), - Output::Anchor(_) => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), - Output::Nft(_) => !address.has_capability(AddressCapabilityFlag::NftOutputs), - Output::Delegation(_) => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), - _ => false, - } { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + *native_token_amount = native_token_amount + .checked_add(native_token.amount()) + .ok_or(Error::CreatedNativeTokensAmountOverflow)?; } } } - if let Some(sender) = features.and_then(|f| f.sender()) { - if !self.unlocked_addresses.contains(sender.address()) { - return Ok(Some(TransactionFailureReason::SenderNotUnlocked)); + // Validation of storage deposit returns. + for (return_address, return_amount) in self.storage_deposit_returns.iter() { + if let Some(deposit_amount) = self.simple_deposits.get(return_address) { + if deposit_amount < return_amount { + return Ok(Some(TransactionFailureReason::StorageDepositReturnUnfulfilled)); + } + } else { + return Ok(Some(TransactionFailureReason::StorageDepositReturnUnfulfilled)); } } - self.output_amount = self - .output_amount - .checked_add(amount) - .ok_or(Error::CreatedAmountOverflow)?; - - self.output_mana = self.output_mana.checked_add(mana).ok_or(Error::CreatedManaOverflow)?; - - if let Some(created_native_tokens) = created_native_tokens { - for native_token in created_native_tokens.iter() { - let native_token_amount = self.output_native_tokens.entry(*native_token.token_id()).or_default(); - - *native_token_amount = native_token_amount - .checked_add(native_token.amount()) - .ok_or(Error::CreatedNativeTokensAmountOverflow)?; - } + // Validation of amounts. + if self.input_amount != self.output_amount { + return Ok(Some(TransactionFailureReason::SumInputsOutputsAmountMismatch)); } - } - // Validation of storage deposit returns. - for (return_address, return_amount) in self.storage_deposit_returns.iter() { - if let Some(deposit_amount) = self.simple_deposits.get(return_address) { - if deposit_amount < return_amount { - return Ok(Some(TransactionFailureReason::StorageDepositReturnUnfulfilled)); - } - } else { - return Ok(Some(TransactionFailureReason::StorageDepositReturnUnfulfilled)); + if self.input_mana > self.output_mana + && !self.transaction.has_capability(TransactionCapabilityFlag::BurnMana) + { + // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 + return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); } - } - // Validation of amounts. - if self.input_amount != self.output_amount { - return Ok(Some(TransactionFailureReason::SumInputsOutputsAmountMismatch)); - } - - if self.input_mana > self.output_mana && !self.transaction.has_capability(TransactionCapabilityFlag::BurnMana) { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } + // Validation of input native tokens. + let mut native_token_ids = self.input_native_tokens.keys().collect::>(); - // Validation of input native tokens. - let mut native_token_ids = self.input_native_tokens.keys().collect::>(); + // Validation of output native tokens. + for (token_id, output_amount) in self.output_native_tokens.iter() { + let input_amount = self.input_native_tokens.get(token_id).copied().unwrap_or_default(); - // Validation of output native tokens. - for (token_id, output_amount) in self.output_native_tokens.iter() { - let input_amount = self.input_native_tokens.get(token_id).copied().unwrap_or_default(); + if output_amount > &input_amount + && !self + .output_chains + .contains_key(&ChainId::from(FoundryId::from(*token_id))) + { + return Ok(Some(TransactionFailureReason::InvalidNativeTokens)); + } - if output_amount > &input_amount - && !self - .output_chains - .contains_key(&ChainId::from(FoundryId::from(*token_id))) - { - return Ok(Some(TransactionFailureReason::InvalidNativeTokens)); + native_token_ids.insert(token_id); } - native_token_ids.insert(token_id); - } - - if native_token_ids.len() > NativeTokens::COUNT_MAX as usize { - return Ok(Some(TransactionFailureReason::InvalidNativeTokens)); - } - - // Validation of state transitions and destructions. - for (chain_id, current_state) in self.input_chains.iter() { - match Output::verify_state_transition( - Some(current_state), - self.output_chains.get(chain_id).map(core::ops::Deref::deref), - &self, - ) { - Err(StateTransitionError::TransactionFailure(f)) => return Ok(Some(f)), - Err(_) => { - return Ok(Some(TransactionFailureReason::InvalidChainStateTransition)); - } - _ => {} + if native_token_ids.len() > NativeTokens::COUNT_MAX as usize { + return Ok(Some(TransactionFailureReason::InvalidNativeTokens)); } - } - // Validation of state creations. - for (chain_id, next_state) in self.output_chains.iter() { - if self.input_chains.get(chain_id).is_none() { - match Output::verify_state_transition(None, Some(next_state), &self) { + // Validation of state transitions and destructions. + for (chain_id, current_state) in self.input_chains.iter() { + match Output::verify_state_transition( + Some(current_state), + self.output_chains.get(chain_id).map(core::ops::Deref::deref), + &self, + ) { Err(StateTransitionError::TransactionFailure(f)) => return Ok(Some(f)), Err(_) => { return Ok(Some(TransactionFailureReason::InvalidChainStateTransition)); @@ -529,6 +543,19 @@ impl<'a> SemanticValidationContext<'a> { _ => {} } } + + // Validation of state creations. + for (chain_id, next_state) in self.output_chains.iter() { + if self.input_chains.get(chain_id).is_none() { + match Output::verify_state_transition(None, Some(next_state), &self) { + Err(StateTransitionError::TransactionFailure(f)) => return Ok(Some(f)), + Err(_) => { + return Ok(Some(TransactionFailureReason::InvalidChainStateTransition)); + } + _ => {} + } + } + } } Ok(None) diff --git a/sdk/src/wallet/operations/balance.rs b/sdk/src/wallet/operations/balance.rs index 7551a6d537..7ad680cd8e 100644 --- a/sdk/src/wallet/operations/balance.rs +++ b/sdk/src/wallet/operations/balance.rs @@ -33,8 +33,9 @@ where // addresses_with_unspent_outputs: impl Iterator + Send, wallet_data: &WalletData, ) -> Result { - let network_id = self.client().get_network_id().await?; - let rent_structure = self.client().get_rent_structure().await?; + let protocol_parameters = self.client().get_protocol_parameters().await?; + let network_id = protocol_parameters.network_id(); + let rent_structure = protocol_parameters.rent_structure(); let mut balance = Balance::default(); let mut total_rent_amount = 0; let mut total_native_tokens = NativeTokensBuilder::default(); @@ -151,6 +152,8 @@ where wallet_data.address.inner(), output, slot_index, + protocol_parameters.min_committable_age(), + protocol_parameters.max_committable_age(), ); if output_can_be_unlocked_now_and_in_future { diff --git a/sdk/src/wallet/operations/helpers/time.rs b/sdk/src/wallet/operations/helpers/time.rs index ebd3675688..b8bc756cf8 100644 --- a/sdk/src/wallet/operations/helpers/time.rs +++ b/sdk/src/wallet/operations/helpers/time.rs @@ -11,18 +11,22 @@ pub(crate) fn can_output_be_unlocked_now( wallet_address: &Address, output_data: &OutputData, slot_index: SlotIndex, + min_committable_age: u32, + max_committable_age: u32, ) -> crate::wallet::Result { if let Some(unlock_conditions) = output_data.output.unlock_conditions() { - if unlock_conditions.is_time_locked(slot_index) { + if unlock_conditions.is_timelocked(slot_index, min_committable_age) { return Ok(false); } } - let (required_unlock_address, _unlocked_account_or_nft_address) = output_data + let required_address = output_data .output - .required_and_unlocked_address(slot_index, &output_data.output_id)?; + .required_address(slot_index, min_committable_age, max_committable_age)? + // TODO + .unwrap(); - Ok(wallet_address == &required_unlock_address) + Ok(wallet_address == &required_address) } // Check if an output can be unlocked by one of the account addresses at the current time and at any @@ -31,23 +35,28 @@ pub(crate) fn can_output_be_unlocked_forever_from_now_on( wallet_address: &Address, output: &Output, slot_index: SlotIndex, + min_committable_age: u32, + max_committable_age: u32, ) -> bool { if let Some(unlock_conditions) = output.unlock_conditions() { - if unlock_conditions.is_time_locked(slot_index) { + if unlock_conditions.is_timelocked(slot_index, min_committable_age) { return false; } - // If there is an expiration unlock condition, we can only unlock it forever from now on, if it's expired and - // the return address belongs to the account - if let Some(expiration) = unlock_conditions.expiration() { - if let Some(return_address) = expiration.return_address_expired(slot_index) { - if wallet_address != return_address { - return false; - }; - } else { - return false; - } - } + // TODO HELP + // // If there is an expiration unlock condition, we can only unlock it forever from now on, if it's expired and + // // the return address belongs to the account + // if let Some(expiration) = unlock_conditions.expiration() { + // if let Some(return_address) = + // expiration.return_address_expired(slot_index, min_committable_age, max_committable_age) + // { + // if wallet_address != return_address { + // return false; + // }; + // } else { + // return false; + // } + // } true } else { diff --git a/sdk/src/wallet/operations/output_claiming.rs b/sdk/src/wallet/operations/output_claiming.rs index b6db006558..912c6007fb 100644 --- a/sdk/src/wallet/operations/output_claiming.rs +++ b/sdk/src/wallet/operations/output_claiming.rs @@ -50,6 +50,7 @@ where let wallet_data = self.data().await; let slot_index = self.client().get_slot_index().await?; + let protocol_parameters = self.client().get_protocol_parameters().await?; // Get outputs for the claim let mut output_ids_to_claim: HashSet = HashSet::new(); @@ -71,13 +72,16 @@ where wallet_data.address.inner(), output_data, slot_index, + protocol_parameters.min_committable_age(), + protocol_parameters.max_committable_age(), )? { match outputs_to_claim { OutputsToClaim::MicroTransactions => { if let Some(sdr) = unlock_conditions.storage_deposit_return() { // If expired, it's not a micro transaction anymore - if unlock_conditions.is_expired(slot_index) { + if unlock_conditions.is_expired(slot_index, protocol_parameters.min_committable_age) + { continue; } // Only micro transaction if not the same @@ -98,7 +102,8 @@ where } OutputsToClaim::Amount => { let mut claimable_amount = output_data.output.amount(); - if !unlock_conditions.is_expired(slot_index) { + if !unlock_conditions.is_expired(slot_index, protocol_parameters.min_committable_age()) + { claimable_amount -= unlock_conditions .storage_deposit_return() .map(|s| s.amount()) diff --git a/sdk/src/wallet/operations/output_consolidation.rs b/sdk/src/wallet/operations/output_consolidation.rs index b1854a12b0..eb5c576d8e 100644 --- a/sdk/src/wallet/operations/output_consolidation.rs +++ b/sdk/src/wallet/operations/output_consolidation.rs @@ -75,16 +75,17 @@ where crate::wallet::Error: From, crate::client::Error: From, { - fn should_consolidate_output( + async fn should_consolidate_output( &self, output_data: &OutputData, slot_index: SlotIndex, wallet_address: &Address, ) -> Result { Ok(if let Output::Basic(basic_output) = &output_data.output { + let protocol_parameters = self.client().get_protocol_parameters().await?; let unlock_conditions = basic_output.unlock_conditions(); - let is_time_locked = unlock_conditions.is_time_locked(slot_index); + let is_time_locked = unlock_conditions.is_timelocked(slot_index, protocol_parameters.min_committable_age()); if is_time_locked { // If the output is timelocked, then it cannot be consolidated. return Ok(false); @@ -92,13 +93,19 @@ where let has_storage_deposit_return = unlock_conditions.storage_deposit_return().is_some(); let has_expiration = unlock_conditions.expiration().is_some(); - let is_expired = unlock_conditions.is_expired(slot_index); + let is_expired = unlock_conditions.is_expired(slot_index, protocol_parameters.min_committable_age()); if has_storage_deposit_return && (!has_expiration || !is_expired) { // If the output has not expired and must return a storage deposit, then it cannot be consolidated. return Ok(false); } - can_output_be_unlocked_now(wallet_address, output_data, slot_index)? + can_output_be_unlocked_now( + wallet_address, + output_data, + slot_index, + protocol_parameters.min_committable_age(), + protocol_parameters.max_committable_age(), + )? } else { false }) @@ -143,7 +150,9 @@ where } let is_locked_output = wallet_data.locked_outputs.contains(output_id); - let should_consolidate_output = self.should_consolidate_output(output_data, slot_index, wallet_address)?; + let should_consolidate_output = self + .should_consolidate_output(output_data, slot_index, wallet_address) + .await?; if !is_locked_output && should_consolidate_output { outputs_to_consolidate.push(output_data.clone()); } diff --git a/sdk/src/wallet/operations/transaction/input_selection.rs b/sdk/src/wallet/operations/transaction/input_selection.rs index 71e437b38c..8f6e5744cf 100644 --- a/sdk/src/wallet/operations/transaction/input_selection.rs +++ b/sdk/src/wallet/operations/transaction/input_selection.rs @@ -70,6 +70,9 @@ where &wallet_data, wallet_data.unspent_outputs.values(), slot_index, + protocol_parameters.min_committable_age(), + protocol_parameters.max_committable_age(), + &outputs, custom_inputs.as_ref(), mandatory_inputs.as_ref(), )?; @@ -219,6 +222,9 @@ fn filter_inputs( wallet_data: &WalletData, available_outputs: Values<'_, OutputId, OutputData>, slot_index: SlotIndex, + min_committable_age: u32, + max_committable_age: u32, + outputs: &[Output], custom_inputs: Option<&HashSet>, mandatory_inputs: Option<&HashSet>, ) -> crate::wallet::Result> { @@ -238,6 +244,8 @@ fn filter_inputs( &wallet_data.address.inner, &output_data.output, slot_index, + min_committable_age, + max_committable_age, ); // Outputs that could get unlocked in the future will not be included @@ -246,7 +254,9 @@ fn filter_inputs( } } - if let Some(available_input) = output_data.input_signing_data(wallet_data, slot_index)? { + if let Some(available_input) = + output_data.input_signing_data(wallet_data, slot_index, min_committable_age, max_committable_age)? + { available_outputs_signing_data.push(available_input); } } diff --git a/sdk/src/wallet/operations/transaction/mod.rs b/sdk/src/wallet/operations/transaction/mod.rs index b3d49d954a..5d1bee18b3 100644 --- a/sdk/src/wallet/operations/transaction/mod.rs +++ b/sdk/src/wallet/operations/transaction/mod.rs @@ -129,7 +129,11 @@ where let options = options.into(); // Validate transaction before sending and storing it - let conflict = verify_semantic(&signed_transaction_data.inputs_data, &signed_transaction_data.payload)?; + let conflict = verify_semantic( + &signed_transaction_data.inputs_data, + &signed_transaction_data.payload, + self.client().get_protocol_parameters().await?, + )?; if let Some(conflict) = conflict { log::debug!( diff --git a/sdk/src/wallet/operations/transaction/sign_transaction.rs b/sdk/src/wallet/operations/transaction/sign_transaction.rs index 2421bda089..9ebe4128e3 100644 --- a/sdk/src/wallet/operations/transaction/sign_transaction.rs +++ b/sdk/src/wallet/operations/transaction/sign_transaction.rs @@ -78,7 +78,10 @@ where .secret_manager .read() .await - .transaction_unlocks(prepared_transaction_data) + .transaction_unlocks( + prepared_transaction_data, + &self.client().get_protocol_parameters().await?, + ) .await { Ok(res) => res, diff --git a/sdk/src/wallet/types/mod.rs b/sdk/src/wallet/types/mod.rs index 4ecef57081..3dcc9a7841 100644 --- a/sdk/src/wallet/types/mod.rs +++ b/sdk/src/wallet/types/mod.rs @@ -57,14 +57,19 @@ impl OutputData { &self, wallet_data: &WalletData, slot_index: SlotIndex, + min_committable_age: u32, + max_committable_age: u32, ) -> crate::wallet::Result> { - let (unlock_address, _unlocked_account_or_nft_address) = - self.output.required_and_unlocked_address(slot_index, &self.output_id)?; + let required_address = self + .output + .required_address(slot_index, min_committable_age, max_committable_age)? + // TODO + .unwrap(); - let chain = if unlock_address == self.address { + let chain = if required_address == self.address { self.chain - } else if let Address::Ed25519(_) = unlock_address { - if wallet_data.address.inner() == &unlock_address { + } else if required_address.is_ed25519() { + if wallet_data.address.inner() == &required_address { // TODO #1279: do we need a check to make sure that `wallet_data.address` and `wallet_data.bip_path` are // never conflicting? wallet_data.bip_path diff --git a/sdk/tests/client/signing/account.rs b/sdk/tests/client/signing/account.rs index e510d3c0c0..73ce334e1c 100644 --- a/sdk/tests/client/signing/account.rs +++ b/sdk/tests/client/signing/account.rs @@ -78,7 +78,7 @@ async fn sign_account_state_transition() -> Result<()> { ) .with_outputs(outputs) .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(protocol_parameters)?; + .finish_with_params(protocol_parameters.clone())?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -86,7 +86,9 @@ async fn sign_account_state_transition() -> Result<()> { remainder: None, }; - let unlocks = secret_manager.transaction_unlocks(&prepared_transaction_data).await?; + let unlocks = secret_manager + .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .await?; assert_eq!(unlocks.len(), 1); assert_eq!((*unlocks).get(0).unwrap().kind(), SignatureUnlock::KIND); @@ -95,7 +97,7 @@ async fn sign_account_state_transition() -> Result<()> { validate_signed_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, protocol_parameters)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); @@ -186,7 +188,7 @@ async fn account_reference_unlocks() -> Result<()> { ) .with_outputs(outputs) .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(protocol_parameters)?; + .finish_with_params(protocol_parameters.clone())?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -194,7 +196,9 @@ async fn account_reference_unlocks() -> Result<()> { remainder: None, }; - let unlocks = secret_manager.transaction_unlocks(&prepared_transaction_data).await?; + let unlocks = secret_manager + .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .await?; assert_eq!(unlocks.len(), 3); assert_eq!((*unlocks).get(0).unwrap().kind(), SignatureUnlock::KIND); @@ -215,7 +219,7 @@ async fn account_reference_unlocks() -> Result<()> { validate_signed_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, protocol_parameters)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); diff --git a/sdk/tests/client/signing/basic.rs b/sdk/tests/client/signing/basic.rs index d7390f0d30..169db53e7c 100644 --- a/sdk/tests/client/signing/basic.rs +++ b/sdk/tests/client/signing/basic.rs @@ -72,7 +72,7 @@ async fn single_ed25519_unlock() -> Result<()> { ) .with_outputs(outputs) .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(protocol_parameters)?; + .finish_with_params(protocol_parameters.clone())?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -80,7 +80,9 @@ async fn single_ed25519_unlock() -> Result<()> { remainder: None, }; - let unlocks = secret_manager.transaction_unlocks(&prepared_transaction_data).await?; + let unlocks = secret_manager + .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .await?; assert_eq!(unlocks.len(), 1); assert_eq!((*unlocks).get(0).unwrap().kind(), SignatureUnlock::KIND); @@ -89,7 +91,7 @@ async fn single_ed25519_unlock() -> Result<()> { validate_signed_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, protocol_parameters)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); @@ -167,7 +169,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { ) .with_outputs(outputs) .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(protocol_parameters)?; + .finish_with_params(protocol_parameters.clone())?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -175,7 +177,9 @@ async fn ed25519_reference_unlocks() -> Result<()> { remainder: None, }; - let unlocks = secret_manager.transaction_unlocks(&prepared_transaction_data).await?; + let unlocks = secret_manager + .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .await?; assert_eq!(unlocks.len(), 3); assert_eq!((*unlocks).get(0).unwrap().kind(), SignatureUnlock::KIND); @@ -196,7 +200,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { validate_signed_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, protocol_parameters)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); @@ -273,7 +277,7 @@ async fn two_signature_unlocks() -> Result<()> { ) .with_outputs(outputs) .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(protocol_parameters)?; + .finish_with_params(protocol_parameters.clone())?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -281,7 +285,9 @@ async fn two_signature_unlocks() -> Result<()> { remainder: None, }; - let unlocks = secret_manager.transaction_unlocks(&prepared_transaction_data).await?; + let unlocks = secret_manager + .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .await?; assert_eq!(unlocks.len(), 2); assert_eq!((*unlocks).get(0).unwrap().kind(), SignatureUnlock::KIND); @@ -291,7 +297,7 @@ async fn two_signature_unlocks() -> Result<()> { validate_signed_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, protocol_parameters)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); diff --git a/sdk/tests/client/signing/mod.rs b/sdk/tests/client/signing/mod.rs index c2e1171c67..ae65925162 100644 --- a/sdk/tests/client/signing/mod.rs +++ b/sdk/tests/client/signing/mod.rs @@ -377,7 +377,7 @@ async fn all_combined() -> Result<()> { .with_outputs(outputs) .with_creation_slot(slot_index) .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(protocol_parameters)?; + .finish_with_params(protocol_parameters.clone())?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -385,7 +385,9 @@ async fn all_combined() -> Result<()> { remainder: None, }; - let unlocks = secret_manager.transaction_unlocks(&prepared_transaction_data).await?; + let unlocks = secret_manager + .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .await?; assert_eq!(unlocks.len(), 15); assert_eq!((*unlocks).get(0).unwrap().kind(), SignatureUnlock::KIND); @@ -469,7 +471,7 @@ async fn all_combined() -> Result<()> { validate_signed_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, protocol_parameters)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); diff --git a/sdk/tests/client/signing/nft.rs b/sdk/tests/client/signing/nft.rs index 0508bc5bcd..1aa1fc0051 100644 --- a/sdk/tests/client/signing/nft.rs +++ b/sdk/tests/client/signing/nft.rs @@ -117,7 +117,7 @@ async fn nft_reference_unlocks() -> Result<()> { ) .with_outputs(outputs) .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(protocol_parameters)?; + .finish_with_params(protocol_parameters.clone())?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -125,7 +125,9 @@ async fn nft_reference_unlocks() -> Result<()> { remainder: None, }; - let unlocks = secret_manager.transaction_unlocks(&prepared_transaction_data).await?; + let unlocks = secret_manager + .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .await?; assert_eq!(unlocks.len(), 3); assert_eq!((*unlocks).get(0).unwrap().kind(), SignatureUnlock::KIND); @@ -146,7 +148,7 @@ async fn nft_reference_unlocks() -> Result<()> { validate_signed_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, protocol_parameters)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); diff --git a/sdk/tests/types/slot.rs b/sdk/tests/types/slot.rs index 8b2795816a..3fd5dbda57 100644 --- a/sdk/tests/types/slot.rs +++ b/sdk/tests/types/slot.rs @@ -5,8 +5,8 @@ use iota_sdk::types::block::slot::SlotCommitment; use packable::PackableExt; // #[test] -// fn slot_commitment_id() { -// // Test from https://github.com/iotaledger/tips-draft/blob/tip46/tips/TIP-0046/tip-0046.md#slot-commitment-id-1 +// fn slot_commitment_id_index() { +// Test from https://github.com/iotaledger/tips-draft/blob/tip46/tips/TIP-0046/tip-0046.md#slot-commitment-id-1 // let slot_commitment_json = serde_json::json!({ // "protocolVersion": 3, @@ -30,10 +30,11 @@ use packable::PackableExt; // ] // ); -// let slot_commitment_id = slot_commitment.id().to_string(); +// let slot_commitment_id = slot_commitment.id(); // assert_eq!( -// slot_commitment_id, +// slot_commitment_id.to_string(), // "0x3a73079f3dbf8c1744ae0b020b9767546e32f5bbbf4c6f0233da7b64f16581f80a00000000000000" // ); +// assert_eq!(slot_commitment_id.index(), 10); // } diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index 314d3558fa..9a0a4b303c 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -348,8 +348,10 @@ async fn output_preparation() -> Result<()> { assert_eq!(sender_feature.address(), issuer_and_sender_address.inner()); // Unlocks let conditions = output.unlock_conditions().unwrap(); - assert!(conditions.is_time_locked(0)); - assert!(conditions.is_expired(2)); + // TODO double check + assert!(conditions.is_timelocked(0, 0)); + // TODO double check + assert!(conditions.is_expired(2, 0)); // nft with expiration let output = wallet