diff --git a/Cargo.lock b/Cargo.lock index 241e1dc3c3..481a572089 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -510,7 +510,6 @@ dependencies = [ "dialoguer", "dotenvy", "fern-logger", - "humantime", "iota-sdk", "log", "prefix-hex", @@ -1426,12 +1425,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.27" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 22ffcab75a..2b9230b85b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -40,7 +40,6 @@ dialoguer = { version = "0.10.4", default-features = false, features = [ ] } dotenvy = { version = "0.15.7", default-features = false } fern-logger = { version = "0.5.0", default-features = false } -humantime = { version = "2.1.0", default-features = false } log = { version = "0.4.20", default-features = false } prefix-hex = { version = "0.7.1", default-features = false, features = ["std"] } serde_json = { version = "1.0.105", default-features = false } diff --git a/cli/src/account.rs b/cli/src/account.rs index f8006c51ac..d2be1cb14a 100644 --- a/cli/src/account.rs +++ b/cli/src/account.rs @@ -146,7 +146,7 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist address, amount, return_address, - expiration.map(|e| e.as_secs() as u32), + expiration, allow_micro_amount, ) .await diff --git a/cli/src/command/account.rs b/cli/src/command/account.rs index 2a013c00f6..bdad6e01fa 100644 --- a/cli/src/command/account.rs +++ b/cli/src/command/account.rs @@ -15,6 +15,7 @@ use iota_sdk::{ Output, OutputId, TokenId, }, payload::transaction::TransactionId, + slot::SlotIndex, ConvertTo, }, }, @@ -163,11 +164,11 @@ pub enum AccountCommand { /// default to the first address of the account. #[arg(long)] return_address: Option, - /// Expiration in seconds, after which the output will be available for the sender again, if not spent by the - /// receiver already. The expiration will only be used if one is necessary given the provided amount. If an - /// expiration is needed but not provided, it will default to one day. + /// Expiration in slot indices, after which the output will be available for the sender again, if not spent by + /// the receiver already. The expiration will only be used if one is necessary given the provided + /// amount. If an expiration is needed but not provided, it will default to one day. #[arg(long)] - expiration: Option, + expiration: Option, /// Whether to send micro amounts. This will automatically add Storage Deposit Return and Expiration unlock /// conditions if necessary. This flag is implied by the existence of a return address or expiration. #[arg(long, default_value_t = false)] @@ -382,9 +383,16 @@ pub async fn claimable_outputs_command(account: &Account) -> Result<(), Error> { println_log_info!(" - base coin amount: {}", amount); if let Some(expiration) = unlock_conditions.expiration() { - let current_time = iota_sdk::utils::unix_timestamp_now().as_secs() as u32; - let time_left = expiration.timestamp() - current_time; - println_log_info!(" - expires in: {} seconds", time_left); + let slot_index = account.client().get_slot_index().await?; + + if *expiration.slot_index() > *slot_index { + println_log_info!(" - expires in {} slot indices", *expiration.slot_index() - *slot_index); + } else { + println_log_info!( + " - expired {} slot indices ago", + *slot_index - *expiration.slot_index() + ); + } } } } @@ -640,7 +648,7 @@ pub async fn send_command( address: impl ConvertTo, amount: u64, return_address: Option>, - expiration: Option, + expiration: Option, allow_micro_amount: bool, ) -> Result<(), Error> { let params = [SendParams::new(amount, address)? @@ -876,7 +884,7 @@ async fn print_address(account: &Account, address: &Bip44Address) -> Result<(), } let addresses = account.addresses_with_unspent_outputs().await?; - let current_time = iota_sdk::utils::unix_timestamp_now().as_secs() as u32; + let slot_index = account.client().get_slot_index().await?; if let Ok(index) = addresses.binary_search_by_key(&(address.key_index(), address.internal()), |a| { (a.key_index(), a.internal()) @@ -885,10 +893,9 @@ async fn print_address(account: &Account, address: &Bip44Address) -> Result<(), for output_id in addresses[index].output_ids() { if let Some(output_data) = account.get_output(output_id).await { // Output might be associated with the address, but can't unlocked by it, so we check that here - let (required_address, _) = - output_data - .output - .required_and_unlocked_address(current_time, output_id, None)?; + let (required_address, _) = output_data + .output + .required_and_unlocked_address(slot_index, output_id, None)?; if *address.address().as_ref() == required_address { let unlock_conditions = output_data .output diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 6216625851..e56ad031b9 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -27,6 +27,8 @@ derive_more = { version = "0.99.17", default-features = false, features = [ "deref", "deref_mut", "display", + "add", + "add_assign", ] } getset = { version = "0.1.2", default-features = false } hashbrown = { version = "0.14.0", default-features = false, features = [ diff --git a/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs b/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs index 34f7825112..e79eb8a300 100644 --- a/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs +++ b/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs @@ -13,6 +13,7 @@ use iota_sdk::{ unlock_condition::{AddressUnlockCondition, TimelockUnlockCondition}, BasicOutputBuilder, }, + slot::SlotIndex, }, wallet::Result, Wallet, @@ -37,18 +38,14 @@ async fn main() -> Result<()> { .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - // Create an ouput with amount 1_000_000 and a timelock of 1 hour - let in_an_hour = (std::time::SystemTime::now() + std::time::Duration::from_secs(3600)) - .duration_since(std::time::UNIX_EPOCH) - .expect("clock went backwards") - .as_secs() - .try_into() - .unwrap(); + // TODO better time-based UX ? + // Create an output with amount 1_000_000 and a timelock of 1000 slots. + let slot_index = SlotIndex::from(1000); let basic_output = BasicOutputBuilder::new_with_amount(1_000_000) .add_unlock_condition(AddressUnlockCondition::new(Bech32Address::try_from_str( "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu", )?)) - .add_unlock_condition(TimelockUnlockCondition::new(in_an_hour)?) + .add_unlock_condition(TimelockUnlockCondition::new(slot_index)?) .finish_output(account.client().get_token_supply().await?)?; let transaction = account.send_outputs(vec![basic_output], None).await?; diff --git a/sdk/examples/wallet/offline_signing/2_sign_transaction.rs b/sdk/examples/wallet/offline_signing/2_sign_transaction.rs index 86ad68135d..1c9c1113a1 100644 --- a/sdk/examples/wallet/offline_signing/2_sign_transaction.rs +++ b/sdk/examples/wallet/offline_signing/2_sign_transaction.rs @@ -38,7 +38,7 @@ async fn main() -> Result<()> { // Signs prepared transaction offline. let unlocks = SecretManager::Stronghold(secret_manager) - .sign_transaction_essence(&prepared_transaction_data, None) + .sign_transaction_essence(&prepared_transaction_data) .await?; let signed_transaction = TransactionPayload::new(prepared_transaction_data.essence.as_regular().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 947893b4d7..96d1336746 100644 --- a/sdk/src/client/api/block_builder/input_selection/mod.rs +++ b/sdk/src/client/api/block_builder/input_selection/mod.rs @@ -26,8 +26,8 @@ use crate::{ OUTPUT_COUNT_RANGE, }, protocol::ProtocolParameters, + slot::SlotIndex, }, - utils::unix_timestamp_now, }; /// Working state for the input selection algorithm. @@ -41,7 +41,7 @@ pub struct InputSelection { burn: Option, remainder_address: Option
, protocol_parameters: ProtocolParameters, - timestamp: u32, + slot_index: SlotIndex, requirements: Vec, automatically_transitioned: HashMap>, } @@ -63,7 +63,7 @@ impl InputSelection { is_account_transition(&input.output, *input.output_id(), &self.outputs, self.burn.as_ref()); let required_address = input .output - .required_and_unlocked_address(self.timestamp, input.output_id(), account_transition)? + .required_and_unlocked_address(self.slot_index, input.output_id(), account_transition)? .0; match required_address { @@ -186,7 +186,8 @@ impl InputSelection { burn: None, remainder_address: None, protocol_parameters, - timestamp: unix_timestamp_now().as_secs() as u32, + // TODO may want to make this mandatory at some point + slot_index: SlotIndex::from(0), requirements: Vec::new(), automatically_transitioned: HashMap::new(), } @@ -216,9 +217,9 @@ impl InputSelection { self } - /// Sets the timestamp of an [`InputSelection`]. - pub fn timestamp(mut self, timestamp: u32) -> Self { - self.timestamp = timestamp; + /// Sets the slot index of an [`InputSelection`]. + pub fn slot_index(mut self, slot_index: impl Into) -> Self { + self.slot_index = slot_index.into(); self } @@ -237,14 +238,14 @@ 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.timestamp) { + if unlock_conditions.is_time_locked(self.slot_index) { return false; } let required_address = input .output // Account transition is irrelevant here as we keep accounts anyway. - .required_and_unlocked_address(self.timestamp, input.output_id(), None) + .required_and_unlocked_address(self.slot_index, input.output_id(), None) // PANIC: safe to unwrap as non basic/account/foundry/nft outputs are already filtered out. .unwrap() .0; @@ -257,9 +258,8 @@ impl InputSelection { pub(crate) fn sort_input_signing_data( mut inputs: Vec, outputs: &[Output], - time: Option, + slot_index: SlotIndex, ) -> Result, Error> { - let time = time.unwrap_or_else(|| unix_timestamp_now().as_secs() as u32); // 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 // might be a more efficient way to do this @@ -275,7 +275,7 @@ impl InputSelection { ); let (input_address, _) = input_signing_data .output - .required_and_unlocked_address(time, input_signing_data.output_id(), account_transition) + .required_and_unlocked_address(slot_index, input_signing_data.output_id(), account_transition) // PANIC: safe to unwrap, because we filtered irrelevant outputs out before .unwrap(); @@ -287,7 +287,7 @@ impl InputSelection { let (input_address, _) = input .output - .required_and_unlocked_address(time, input.output_id(), account_transition)?; + .required_and_unlocked_address(slot_index, input.output_id(), account_transition)?; match sorted_inputs.iter().position(|input_signing_data| match input_address { Address::Account(unlock_address) => { @@ -334,7 +334,7 @@ impl InputSelection { ); let (input_address, _) = input_signing_data .output - .required_and_unlocked_address(time, input.output_id(), account_transition) + .required_and_unlocked_address(slot_index, input.output_id(), account_transition) // PANIC: safe to unwrap, because we filtered irrelevant outputs out before .unwrap(); @@ -408,7 +408,7 @@ impl InputSelection { self.validate_transitions()?; Ok(Selected { - inputs: Self::sort_input_signing_data(self.selected_inputs, &self.outputs, Some(self.timestamp))?, + inputs: Self::sort_input_signing_data(self.selected_inputs, &self.outputs, self.slot_index)?, 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 03f34bd2cc..5b2e39caa9 100644 --- a/sdk/src/client/api/block_builder/input_selection/remainder.rs +++ b/sdk/src/client/api/block_builder/input_selection/remainder.rs @@ -36,7 +36,7 @@ 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.timestamp, input.output_id(), account_transition) + .required_and_unlocked_address(self.slot_index, input.output_id(), account_transition) .unwrap() .0; @@ -86,7 +86,7 @@ impl InputSelection { &self, ) -> Result<(Option, Vec), Error> { let (inputs_sum, outputs_sum, inputs_sdr, outputs_sdr) = - amount_sums(&self.selected_inputs, &self.outputs, self.timestamp); + amount_sums(&self.selected_inputs, &self.outputs, self.slot_index); let mut storage_deposit_returns = Vec::new(); for (address, amount) in inputs_sdr { 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 ee7c2deb79..549bc43e62 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 @@ -13,18 +13,22 @@ use crate::{ unlock_condition::StorageDepositReturnUnlockCondition, AccountOutputBuilder, AccountTransition, FoundryOutputBuilder, NftOutputBuilder, Output, OutputId, Rent, }, + slot::SlotIndex, }, }; /// Get the `StorageDepositReturnUnlockCondition`, if not expired. -pub(crate) fn sdruc_not_expired(output: &Output, current_time: u32) -> Option<&StorageDepositReturnUnlockCondition> { +pub(crate) fn sdruc_not_expired( + output: &Output, + slot_index: SlotIndex, +) -> Option<&StorageDepositReturnUnlockCondition> { // PANIC: safe to unwrap as outputs without unlock conditions have been filtered out already. let unlock_conditions = output.unlock_conditions().unwrap(); unlock_conditions.storage_deposit_return().and_then(|sdr| { let expired = unlock_conditions .expiration() - .map_or(false, |expiration| current_time >= expiration.timestamp()); + .map_or(false, |expiration| slot_index >= expiration.slot_index()); // We only have to send the storage deposit return back if the output is not expired if !expired { Some(sdr) } else { None } @@ -34,7 +38,7 @@ pub(crate) fn sdruc_not_expired(output: &Output, current_time: u32) -> Option<&S pub(crate) fn amount_sums( selected_inputs: &[InputSigningData], outputs: &[Output], - timestamp: u32, + slot_index: SlotIndex, ) -> (u64, u64, HashMap, HashMap) { let mut inputs_sum = 0; let mut outputs_sum = 0; @@ -44,7 +48,7 @@ pub(crate) fn amount_sums( for selected_input in selected_inputs { inputs_sum += selected_input.output.amount(); - if let Some(sdruc) = sdruc_not_expired(&selected_input.output, timestamp) { + if let Some(sdruc) = sdruc_not_expired(&selected_input.output, slot_index) { *inputs_sdr.entry(*sdruc.return_address()).or_default() += sdruc.amount(); } } @@ -80,7 +84,7 @@ struct AmountSelection { outputs_sdr: HashMap, remainder_amount: u64, native_tokens_remainder: bool, - timestamp: u32, + slot_index: SlotIndex, } impl AmountSelection { @@ -88,7 +92,7 @@ impl AmountSelection { let (inputs_sum, outputs_sum, inputs_sdr, outputs_sdr) = amount_sums( &input_selection.selected_inputs, &input_selection.outputs, - input_selection.timestamp, + input_selection.slot_index, ); let (remainder_amount, native_tokens_remainder) = input_selection.remainder_amount()?; @@ -100,7 +104,7 @@ impl AmountSelection { outputs_sdr, remainder_amount, native_tokens_remainder, - timestamp: input_selection.timestamp, + slot_index: input_selection.slot_index, }) } @@ -129,7 +133,7 @@ impl AmountSelection { continue; } - if let Some(sdruc) = sdruc_not_expired(&input.output, self.timestamp) { + if let Some(sdruc) = sdruc_not_expired(&input.output, self.slot_index) { // Skip if no additional amount is made available if input.output.amount() == sdruc.amount() { continue; @@ -172,7 +176,7 @@ impl InputSelection { // No native tokens, expired SDRUC. let inputs = base_inputs.clone().filter(|input| { input.output.native_tokens().unwrap().is_empty() - && sdruc_not_expired(&input.output, self.timestamp).is_none() + && sdruc_not_expired(&input.output, self.slot_index).is_none() }); if amount_selection.fulfil(inputs) { @@ -182,7 +186,7 @@ impl InputSelection { // No native tokens, unexpired SDRUC. let inputs = base_inputs.clone().filter(|input| { input.output.native_tokens().unwrap().is_empty() - && sdruc_not_expired(&input.output, self.timestamp).is_some() + && sdruc_not_expired(&input.output, self.slot_index).is_some() }); if amount_selection.fulfil(inputs) { @@ -192,7 +196,7 @@ impl InputSelection { // Native tokens, expired SDRUC. let inputs = base_inputs.clone().filter(|input| { !input.output.native_tokens().unwrap().is_empty() - && sdruc_not_expired(&input.output, self.timestamp).is_none() + && sdruc_not_expired(&input.output, self.slot_index).is_none() }); if amount_selection.fulfil(inputs) { @@ -202,7 +206,7 @@ impl InputSelection { // Native tokens, unexpired SDRUC. let inputs = base_inputs.clone().filter(|input| { !input.output.native_tokens().unwrap().is_empty() - && sdruc_not_expired(&input.output, self.timestamp).is_some() + && sdruc_not_expired(&input.output, self.slot_index).is_some() }); if amount_selection.fulfil(inputs) { @@ -352,7 +356,7 @@ impl InputSelection { if let Output::Basic(output) = &input.output { output .unlock_conditions() - .locked_address(output.address(), self.timestamp) + .locked_address(output.address(), self.slot_index) .is_ed25519() } else { false @@ -367,7 +371,7 @@ impl InputSelection { if let Output::Basic(output) = &input.output { !output .unlock_conditions() - .locked_address(output.address(), self.timestamp) + .locked_address(output.address(), self.slot_index) .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 9bbdc7d65e..f9d56c91e1 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 @@ -20,7 +20,7 @@ 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.timestamp, input.output_id(), account_transition) + .required_and_unlocked_address(self.slot_index, input.output_id(), account_transition) .unwrap() .0; @@ -58,7 +58,7 @@ impl InputSelection { } else { let (required_address, _) = input .output - .required_and_unlocked_address(self.timestamp, input.output_id(), None) + .required_and_unlocked_address(self.slot_index, input.output_id(), None) .unwrap(); (&required_address == address, None) diff --git a/sdk/src/client/api/block_builder/transaction.rs b/sdk/src/client/api/block_builder/transaction.rs index c4b78a4681..01028879a9 100644 --- a/sdk/src/client/api/block_builder/transaction.rs +++ b/sdk/src/client/api/block_builder/transaction.rs @@ -27,7 +27,6 @@ const REFERENCE_ACCOUNT_NFT_UNLOCK_LENGTH: usize = 1 + 2; pub fn verify_semantic( input_signing_data: &[InputSigningData], transaction: &TransactionPayload, - current_time: u32, ) -> crate::client::Result> { let transaction_id = transaction.id(); let inputs = input_signing_data @@ -40,7 +39,6 @@ pub fn verify_semantic( transaction.essence(), inputs.iter().map(|(id, input)| (*id, *input)), transaction.unlocks(), - current_time, ); Ok(semantic_validation(context, inputs.as_slice(), transaction.unlocks())?) diff --git a/sdk/src/client/api/high_level.rs b/sdk/src/client/api/high_level.rs index 70118b6c9e..6279d6925a 100644 --- a/sdk/src/client/api/high_level.rs +++ b/sdk/src/client/api/high_level.rs @@ -19,6 +19,7 @@ use crate::{ input::{Input, UtxoInput, INPUT_COUNT_MAX}, output::OutputWithMetadata, payload::{transaction::TransactionId, Payload}, + slot::SlotIndex, BlockId, }, utils::unix_timestamp_now, @@ -134,4 +135,9 @@ impl Client { Ok(current_time) } + + // TODO + pub async fn get_slot_index(&self) -> Result { + todo!() + } } diff --git a/sdk/src/client/node_api/indexer/query_parameters.rs b/sdk/src/client/node_api/indexer/query_parameters.rs index cb482cc148..ae49d93ac0 100644 --- a/sdk/src/client/node_api/indexer/query_parameters.rs +++ b/sdk/src/client/node_api/indexer/query_parameters.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::{ client::{Error, Result}, - types::block::address::Bech32Address, + types::block::{address::Bech32Address, slot::SlotIndex}, }; // https://github.com/iotaledger/inx-indexer/tree/develop/pkg/indexer @@ -81,19 +81,19 @@ pub enum QueryParameter { Address(Bech32Address), /// Filter foundry outputs based on bech32-encoded address of the controlling account. AccountAddress(Bech32Address), - /// Returns outputs that were created after a certain Unix timestamp. - CreatedAfter(u32), - /// Returns outputs that were created before a certain Unix timestamp. - CreatedBefore(u32), + /// Returns outputs that were created after a certain slot index. + CreatedAfter(SlotIndex), + /// Returns outputs that were created before a certain slot index. + CreatedBefore(SlotIndex), /// Starts the search from the cursor (confirmationMS+outputId.pageSize). Cursor(String), /// Filters outputs based on the presence of a specific Bech32-encoded return address in the expiration unlock /// condition. ExpirationReturnAddress(Bech32Address), - /// Returns outputs that expire after a certain Unix timestamp. - ExpiresAfter(u32), - /// Returns outputs that expire before a certain Unix timestamp. - ExpiresBefore(u32), + /// Returns outputs that expire after a certain slot index. + ExpiresAfter(SlotIndex), + /// Returns outputs that expire before a certain slot index. + ExpiresBefore(SlotIndex), /// Filters outputs based on bech32-encoded governor (governance controller) address. Governor(Bech32Address), /// Filters outputs based on the presence of expiration unlock condition. @@ -122,10 +122,10 @@ pub enum QueryParameter { StorageDepositReturnAddress(Bech32Address), /// Filters outputs based on matching Tag Block. Tag(String), - /// Returns outputs that are timelocked after a certain Unix timestamp. - TimelockedAfter(u32), - /// Returns outputs that are timelocked before a certain Unix timestamp. - TimelockedBefore(u32), + /// Returns outputs that are timelocked after a certain slot index. + TimelockedAfter(SlotIndex), + /// Returns outputs that are timelocked before a certain slot index. + TimelockedBefore(SlotIndex), } impl QueryParameter { diff --git a/sdk/src/client/secret/ledger_nano.rs b/sdk/src/client/secret/ledger_nano.rs index 2bb4a8ab71..409ab571a0 100644 --- a/sdk/src/client/secret/ledger_nano.rs +++ b/sdk/src/client/secret/ledger_nano.rs @@ -33,7 +33,6 @@ use crate::{ signature::{Ed25519Signature, Signature}, unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, }, - utils::unix_timestamp_now, }; /// Ledger nano errors. @@ -241,7 +240,6 @@ impl SecretManage for LedgerSecretManager { async fn sign_transaction_essence( &self, prepared_transaction: &PreparedTransactionData, - time: Option, ) -> Result::Error> { let mut input_bip32_indices = Vec::new(); let mut coin_type = None; @@ -398,7 +396,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(), time)?; + unlocks = merge_unlocks(prepared_transaction, unlocks.into_iter())?; } Ok(Unlocks::new(unlocks)?) @@ -515,13 +513,12 @@ impl LedgerSecretManager { fn merge_unlocks( prepared_transaction_data: &PreparedTransactionData, mut unlocks: impl Iterator, - time: Option, ) -> Result, Error> { + let TransactionEssence::Regular(essence) = &prepared_transaction_data.essence; + let slot_index = essence.creation_slot(); // The hashed_essence gets signed let hashed_essence = prepared_transaction_data.essence.hash(); - let time = time.unwrap_or_else(|| unix_timestamp_now().as_secs() as u32); - let mut merged_unlocks = Vec::new(); let mut block_indexes = HashMap::::new(); @@ -530,10 +527,11 @@ fn merge_unlocks( // Get the address that is required to unlock the input let TransactionEssence::Regular(regular) = &prepared_transaction_data.essence; let account_transition = is_account_transition(&input.output, *input.output_id(), regular.outputs(), None); - let (input_address, _) = - input - .output - .required_and_unlocked_address(time, input.output_metadata.output_id(), account_transition)?; + let (input_address, _) = input.output.required_and_unlocked_address( + slot_index, + input.output_metadata.output_id(), + account_transition, + )?; // Check if we already added an [Unlock] for this address match block_indexes.get(&input_address) { diff --git a/sdk/src/client/secret/mnemonic.rs b/sdk/src/client/secret/mnemonic.rs index 485c5b1039..f1f091dd56 100644 --- a/sdk/src/client/secret/mnemonic.rs +++ b/sdk/src/client/secret/mnemonic.rs @@ -124,9 +124,8 @@ impl SecretManage for MnemonicSecretManager { async fn sign_transaction_essence( &self, prepared_transaction_data: &PreparedTransactionData, - time: Option, ) -> Result { - super::default_sign_transaction_essence(self, prepared_transaction_data, time).await + super::default_sign_transaction_essence(self, prepared_transaction_data).await } async fn sign_transaction( diff --git a/sdk/src/client/secret/mod.rs b/sdk/src/client/secret/mod.rs index a1aedac936..67c58865e8 100644 --- a/sdk/src/client/secret/mod.rs +++ b/sdk/src/client/secret/mod.rs @@ -58,7 +58,6 @@ use crate::{ signature::{Ed25519Signature, Signature}, unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, }, - utils::unix_timestamp_now, }; /// The secret manager interface. @@ -106,7 +105,6 @@ pub trait SecretManage: Send + Sync { async fn sign_transaction_essence( &self, prepared_transaction_data: &PreparedTransactionData, - time: Option, ) -> Result; async fn sign_transaction( @@ -405,27 +403,20 @@ impl SecretManage for SecretManager { async fn sign_transaction_essence( &self, prepared_transaction_data: &PreparedTransactionData, - time: Option, ) -> Result { match self { #[cfg(feature = "stronghold")] Self::Stronghold(secret_manager) => Ok(secret_manager - .sign_transaction_essence(prepared_transaction_data, time) + .sign_transaction_essence(prepared_transaction_data) .await?), #[cfg(feature = "ledger_nano")] Self::LedgerNano(secret_manager) => Ok(secret_manager - .sign_transaction_essence(prepared_transaction_data, time) + .sign_transaction_essence(prepared_transaction_data) .await?), - Self::Mnemonic(secret_manager) => { - secret_manager - .sign_transaction_essence(prepared_transaction_data, time) - .await - } + Self::Mnemonic(secret_manager) => secret_manager.sign_transaction_essence(prepared_transaction_data).await, #[cfg(feature = "private_key_secret_manager")] Self::PrivateKey(secret_manager) => { - secret_manager - .sign_transaction_essence(prepared_transaction_data, time) - .await + secret_manager.sign_transaction_essence(prepared_transaction_data).await } Self::Placeholder => Err(Error::PlaceholderSecretManager), } @@ -510,7 +501,6 @@ impl SecretManager { pub(crate) async fn default_sign_transaction_essence( secret_manager: &M, prepared_transaction_data: &PreparedTransactionData, - time: Option, ) -> crate::client::Result where crate::client::Error: From, @@ -519,6 +509,8 @@ where let hashed_essence = prepared_transaction_data.essence.hash(); let mut blocks = Vec::new(); let mut block_indexes = HashMap::::new(); + let TransactionEssence::Regular(essence) = &prepared_transaction_data.essence; + let slot_index = essence.creation_slot(); // Assuming inputs_data is ordered by address type for (current_block_index, input) in prepared_transaction_data.inputs_data.iter().enumerate() { @@ -526,7 +518,7 @@ where let TransactionEssence::Regular(regular) = &prepared_transaction_data.essence; let account_transition = is_account_transition(&input.output, *input.output_id(), regular.outputs(), None); let (input_address, _) = input.output.required_and_unlocked_address( - time.unwrap_or_else(|| unix_timestamp_now().as_secs() as u32), + slot_index, input.output_metadata.output_id(), account_transition, )?; @@ -587,10 +579,9 @@ where crate::client::Error: From, { log::debug!("[sign_transaction] {:?}", prepared_transaction_data); - let current_time = unix_timestamp_now().as_secs() as u32; let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(current_time)) + .sign_transaction_essence(&prepared_transaction_data) .await?; let PreparedTransactionData { @@ -602,7 +593,7 @@ where validate_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&inputs_data, &tx_payload)?; 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 551c7c270c..fc6525c765 100644 --- a/sdk/src/client/secret/private_key.rs +++ b/sdk/src/client/secret/private_key.rs @@ -84,9 +84,8 @@ impl SecretManage for PrivateKeySecretManager { async fn sign_transaction_essence( &self, prepared_transaction_data: &PreparedTransactionData, - time: Option, ) -> Result { - super::default_sign_transaction_essence(self, prepared_transaction_data, time).await + super::default_sign_transaction_essence(self, prepared_transaction_data).await } async fn sign_transaction( diff --git a/sdk/src/client/stronghold/secret.rs b/sdk/src/client/stronghold/secret.rs index 34a7b59739..e52e91135d 100644 --- a/sdk/src/client/stronghold/secret.rs +++ b/sdk/src/client/stronghold/secret.rs @@ -283,9 +283,8 @@ impl SecretManage for StrongholdAdapter { async fn sign_transaction_essence( &self, prepared_transaction_data: &PreparedTransactionData, - time: Option, ) -> Result { - crate::client::secret::default_sign_transaction_essence(self, prepared_transaction_data, time).await + crate::client::secret::default_sign_transaction_essence(self, prepared_transaction_data).await } async fn sign_transaction( diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 6fc5577f52..f8734b6b4e 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -302,7 +302,7 @@ impl BasicOutput { context: &mut ValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() - .locked_address(self.address(), context.milestone_timestamp) + .locked_address(self.address(), context.essence.creation_slot()) .unlock(unlock, inputs, context) } diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index a5c0fe5bc1..b761808122 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -352,7 +352,7 @@ impl DelegationOutput { context: &mut ValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() - .locked_address(self.address(), context.milestone_timestamp) + .locked_address(self.address(), context.essence.creation_slot()) .unlock(unlock, inputs, context) } } diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index eaa7f3dbfd..528d958a8a 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -67,7 +67,7 @@ pub use self::{ unlock_condition::{UnlockCondition, UnlockConditions}, }; use super::protocol::ProtocolParameters; -use crate::types::block::{address::Address, semantic::ValidationContext, Error}; +use crate::types::block::{address::Address, semantic::ValidationContext, slot::SlotIndex, Error}; /// The maximum number of outputs of a transaction. pub const OUTPUT_COUNT_MAX: u16 = 128; @@ -306,15 +306,13 @@ impl Output { /// If no `account_transition` has been provided, assumes a state transition. pub fn required_and_unlocked_address( &self, - current_time: u32, + slot_index: SlotIndex, output_id: &OutputId, account_transition: Option, ) -> Result<(Address, Option
), Error> { match self { Self::Basic(output) => Ok(( - *output - .unlock_conditions() - .locked_address(output.address(), current_time), + *output.unlock_conditions().locked_address(output.address(), slot_index), None, )), Self::Account(output) => { @@ -330,15 +328,11 @@ impl Output { } Self::Foundry(output) => Ok((Address::Account(*output.account_address()), None)), Self::Nft(output) => Ok(( - *output - .unlock_conditions() - .locked_address(output.address(), current_time), + *output.unlock_conditions().locked_address(output.address(), slot_index), Some(Address::Nft(output.nft_address(output_id))), )), Self::Delegation(output) => Ok(( - *output - .unlock_conditions() - .locked_address(output.address(), current_time), + *output.unlock_conditions().locked_address(output.address(), slot_index), None, )), } diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index b8d5e6609a..98133eeb5c 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -380,7 +380,7 @@ impl NftOutput { context: &mut ValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { self.unlock_conditions() - .locked_address(self.address(), context.milestone_timestamp) + .locked_address(self.address(), context.essence.creation_slot()) .unlock(unlock, inputs, 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 2aced8bece..185de6eb6a 100644 --- a/sdk/src/types/block/output/unlock_condition/expiration.rs +++ b/sdk/src/types/block/output/unlock_condition/expiration.rs @@ -3,19 +3,17 @@ use derive_more::From; -use crate::types::block::{address::Address, Error}; +use crate::types::block::{address::Address, slot::SlotIndex, Error}; -/// Defines a unix time until which only Address, defined in Address Unlock Condition, is allowed to unlock the output. -/// After or at the unix time, only Return Address can unlock it. +/// Defines an expiration slot index. Before the slot index is reached, only the Address defined in the Address +/// Unlock Condition is allowed to unlock the output. Afterward, only the Return Address can unlock it. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] pub struct ExpirationUnlockCondition { // The address that can unlock the expired output. return_address: Address, - // Before this unix time, seconds since unix epoch, - // [`AddressUnlockCondition`](crate::types::unlock_condition::AddressUnlockCondition) is allowed to unlock the - // output. After that, only the return [`Address`](crate::types::address::Address) can. - #[packable(verify_with = verify_timestamp)] - timestamp: u32, + /// The slot index that determines when the associated output expires. + #[packable(verify_with = verify_slot_index)] + slot_index: SlotIndex, } impl ExpirationUnlockCondition { @@ -24,12 +22,14 @@ impl ExpirationUnlockCondition { /// Creates a new [`ExpirationUnlockCondition`]. #[inline(always)] - pub fn new(return_address: impl Into
, timestamp: u32) -> Result { - verify_timestamp::(×tamp, &())?; + pub fn new(return_address: impl Into
, slot_index: impl Into) -> Result { + let slot_index = slot_index.into(); + + verify_slot_index::(&slot_index, &())?; Ok(Self { return_address: return_address.into(), - timestamp, + slot_index, }) } @@ -39,15 +39,15 @@ impl ExpirationUnlockCondition { &self.return_address } - /// Returns the timestamp of a [`ExpirationUnlockCondition`]. + /// Returns the slot index of a [`ExpirationUnlockCondition`]. #[inline(always)] - pub fn timestamp(&self) -> u32 { - self.timestamp + pub fn slot_index(&self) -> SlotIndex { + self.slot_index } /// Returns the return address if the condition has expired. - pub fn return_address_expired(&self, timestamp: u32) -> Option<&Address> { - if timestamp >= self.timestamp() { + pub fn return_address_expired(&self, slot_index: SlotIndex) -> Option<&Address> { + if slot_index >= self.slot_index() { Some(&self.return_address) } else { None @@ -56,8 +56,8 @@ impl ExpirationUnlockCondition { } #[inline] -fn verify_timestamp(timestamp: &u32, _: &()) -> Result<(), Error> { - if VERIFY && *timestamp == 0 { +fn verify_slot_index(slot_index: &SlotIndex, _: &()) -> Result<(), Error> { + if VERIFY && *slot_index == 0 { Err(Error::ExpirationUnlockConditionZero) } else { Ok(()) @@ -77,8 +77,7 @@ pub(crate) mod dto { #[serde(rename = "type")] kind: u8, return_address: Address, - #[serde(rename = "unixTime")] - timestamp: u32, + slot_index: SlotIndex, } impl From<&ExpirationUnlockCondition> for ExpirationUnlockConditionDto { @@ -86,7 +85,7 @@ pub(crate) mod dto { Self { kind: ExpirationUnlockCondition::KIND, return_address: *value.return_address(), - timestamp: value.timestamp(), + slot_index: value.slot_index(), } } } @@ -95,7 +94,7 @@ pub(crate) mod dto { type Error = Error; fn try_from(value: ExpirationUnlockConditionDto) -> Result { - Self::new(value.return_address, value.timestamp) + Self::new(value.return_address, value.slot_index) .map_err(|_| Error::InvalidField("expirationUnlockCondition")) } } diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index 644322a9b5..de473e337f 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -30,7 +30,7 @@ pub use self::{ state_controller_address::StateControllerAddressUnlockCondition, storage_deposit_return::StorageDepositReturnUnlockCondition, timelock::TimelockUnlockCondition, }; -use crate::types::block::{address::Address, create_bitflags, protocol::ProtocolParameters, Error}; +use crate::types::block::{address::Address, create_bitflags, protocol::ProtocolParameters, slot::SlotIndex, Error}; /// #[derive(Clone, Eq, PartialEq, Hash, From)] @@ -419,24 +419,28 @@ impl UnlockConditions { /// Returns the address to be unlocked. #[inline(always)] - pub fn locked_address<'a>(&'a self, address: &'a Address, milestone_timestamp: u32) -> &'a Address { + pub fn locked_address<'a>(&'a self, address: &'a Address, slot_index: SlotIndex) -> &'a Address { self.expiration() - .and_then(|e| e.return_address_expired(milestone_timestamp)) + .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, milestone_timestamp: u32) -> bool { + pub fn is_time_locked(&self, slot_index: impl Into) -> bool { + let slot_index = slot_index.into(); + self.timelock() - .map_or(false, |timelock| milestone_timestamp < timelock.timestamp()) + .map_or(false, |timelock| slot_index < timelock.slot_index()) } /// Returns whether an expiration exists and is expired. #[inline(always)] - pub fn is_expired(&self, milestone_timestamp: u32) -> bool { + pub fn is_expired(&self, slot_index: impl Into) -> bool { + let slot_index = slot_index.into(); + self.expiration() - .map_or(false, |expiration| milestone_timestamp >= expiration.timestamp()) + .map_or(false, |expiration| slot_index >= expiration.slot_index()) } } diff --git a/sdk/src/types/block/output/unlock_condition/timelock.rs b/sdk/src/types/block/output/unlock_condition/timelock.rs index 73050a4215..9a93d65ddc 100644 --- a/sdk/src/types/block/output/unlock_condition/timelock.rs +++ b/sdk/src/types/block/output/unlock_condition/timelock.rs @@ -3,12 +3,12 @@ use derive_more::From; -use crate::types::block::Error; +use crate::types::block::{slot::SlotIndex, Error}; -/// Defines a unix timestamp until which the output can not be unlocked. +/// Defines a slot index until which the output can not be unlocked. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] #[packable(unpack_error = Error)] -pub struct TimelockUnlockCondition(#[packable(verify_with = verify_timestamp)] u32); +pub struct TimelockUnlockCondition(#[packable(verify_with = verify_slot_index)] SlotIndex); impl TimelockUnlockCondition { /// The [`UnlockCondition`](crate::types::block::output::UnlockCondition) kind of a [`TimelockUnlockCondition`]. @@ -16,22 +16,24 @@ impl TimelockUnlockCondition { /// Creates a new [`TimelockUnlockCondition`]. #[inline(always)] - pub fn new(timestamp: u32) -> Result { - verify_timestamp::(×tamp, &())?; + pub fn new(slot_index: impl Into) -> Result { + let slot_index = slot_index.into(); - Ok(Self(timestamp)) + verify_slot_index::(&slot_index, &())?; + + Ok(Self(slot_index)) } - /// Returns the timestamp of a [`TimelockUnlockCondition`]. + /// Returns the slot index of a [`TimelockUnlockCondition`]. #[inline(always)] - pub fn timestamp(&self) -> u32 { + pub fn slot_index(&self) -> SlotIndex { self.0 } } #[inline] -fn verify_timestamp(timestamp: &u32, _: &()) -> Result<(), Error> { - if VERIFY && *timestamp == 0 { +fn verify_slot_index(slot_index: &SlotIndex, _: &()) -> Result<(), Error> { + if VERIFY && *slot_index == 0 { Err(Error::TimelockUnlockConditionZero) } else { Ok(()) @@ -49,15 +51,14 @@ pub(crate) mod dto { struct TimelockUnlockConditionDto { #[serde(rename = "type")] kind: u8, - #[serde(rename = "unixTime")] - timestamp: u32, + slot_index: u64, } impl From<&TimelockUnlockCondition> for TimelockUnlockConditionDto { fn from(value: &TimelockUnlockCondition) -> Self { Self { kind: TimelockUnlockCondition::KIND, - timestamp: value.timestamp(), + slot_index: *value.slot_index(), } } } @@ -66,7 +67,7 @@ pub(crate) mod dto { type Error = Error; fn try_from(value: TimelockUnlockConditionDto) -> Result { - Self::new(value.timestamp).map_err(|_| Error::InvalidField("timelockUnlockCondition")) + Self::new(SlotIndex::from(value.slot_index)).map_err(|_| Error::InvalidField("timelockUnlockCondition")) } } diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index 46e48a51a2..fc5a48444e 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -112,8 +112,6 @@ pub struct ValidationContext<'a> { /// pub unlocks: &'a Unlocks, /// - pub milestone_timestamp: u32, - /// pub input_amount: u64, /// pub input_native_tokens: BTreeMap, @@ -140,14 +138,12 @@ impl<'a> ValidationContext<'a> { essence: &'a RegularTransactionEssence, inputs: impl Iterator + Clone, unlocks: &'a Unlocks, - milestone_timestamp: u32, ) -> Self { Self { essence, unlocks, essence_hash: TransactionEssence::from(essence.clone()).hash(), inputs_commitment: InputsCommitment::new(inputs.clone().map(|(_, output)| output)), - milestone_timestamp, input_amount: 0, input_native_tokens: BTreeMap::::new(), input_chains: inputs @@ -229,11 +225,11 @@ pub fn semantic_validation( return Ok(Some(conflict)); } - if unlock_conditions.is_time_locked(context.milestone_timestamp) { + if unlock_conditions.is_time_locked(context.essence.creation_slot()) { return Ok(Some(TransactionFailureReason::TimelockNotExpired)); } - if !unlock_conditions.is_expired(context.milestone_timestamp) { + if !unlock_conditions.is_expired(context.essence.creation_slot()) { if let Some(storage_deposit_return) = unlock_conditions.storage_deposit_return() { let amount = context .storage_deposit_returns diff --git a/sdk/src/types/block/slot/index.rs b/sdk/src/types/block/slot/index.rs index a1fcbab00a..73e9fd2ba8 100644 --- a/sdk/src/types/block/slot/index.rs +++ b/sdk/src/types/block/slot/index.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use derive_more::{Deref, Display, From, FromStr}; +use derive_more::{Add, AddAssign, Deref, Display, From, FromStr, Sub, SubAssign}; use super::EpochIndex; use crate::types::block::Error; @@ -23,7 +23,23 @@ use crate::types::block::Error; /// | 1 | genesis | genesis + 10s | /// | 2 | genesis + 10s | genesis + 20s | #[derive( - Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, Deref, Display, FromStr, packable::Packable, + Copy, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + From, + Deref, + Display, + FromStr, + Add, + AddAssign, + Sub, + SubAssign, + packable::Packable, )] #[repr(transparent)] pub struct SlotIndex(u64); @@ -62,6 +78,40 @@ impl SlotIndex { } } +impl PartialEq for SlotIndex { + fn eq(&self, other: &u64) -> bool { + self.0 == *other + } +} + +impl core::ops::Add for SlotIndex { + type Output = Self; + + fn add(self, other: u64) -> Self { + Self(self.0 + other) + } +} + +impl core::ops::AddAssign for SlotIndex { + fn add_assign(&mut self, other: u64) { + self.0 += other; + } +} + +impl core::ops::Sub for SlotIndex { + type Output = Self; + + fn sub(self, other: u64) -> Self { + Self(self.0 - other) + } +} + +impl core::ops::SubAssign for SlotIndex { + fn sub_assign(&mut self, other: u64) { + self.0 -= other; + } +} + impl From for u64 { fn from(slot_index: SlotIndex) -> Self { *slot_index diff --git a/sdk/src/wallet/account/constants.rs b/sdk/src/wallet/account/constants.rs index 158ee26fe1..bfa9f2b586 100644 --- a/sdk/src/wallet/account/constants.rs +++ b/sdk/src/wallet/account/constants.rs @@ -1,10 +1,10 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/// Amount at which outputs on a single addresses will get consolidated by default if consolidatioin is enabled +/// Amount at which outputs on a single address will get consolidated by default if consolidation is enabled pub(crate) const DEFAULT_OUTPUT_CONSOLIDATION_THRESHOLD: usize = 100; -/// Amount at which outputs on a single addresses will get consolidated by default with a ledger secret_manager if -/// consolidatioin is enabled, needs to be smaller because the memory of the ledger nano s is limited +/// Amount at which outputs on a single address will get consolidated by default with a ledger secret_manager if +/// consolidation is enabled, needs to be smaller because the memory of the ledger nano s is limited #[cfg(feature = "ledger_nano")] pub(crate) const DEFAULT_LEDGER_OUTPUT_CONSOLIDATION_THRESHOLD: usize = 15; @@ -15,5 +15,6 @@ pub(crate) const PARALLEL_REQUESTS_AMOUNT: usize = 500; /// this is done to prevent unnecessary simultaneous synchronizations pub(crate) const MIN_SYNC_INTERVAL: u128 = 5; -// Default expiration time for [ExpirationUnlockCondition] when sending native tokens, one day in seconds -pub(crate) const DEFAULT_EXPIRATION_TIME: u32 = 86400; +// TODO Used to be one day in seconds, what now ? +// Default expiration slots for [ExpirationUnlockCondition] when sending native tokens, +pub(crate) const DEFAULT_EXPIRATION_SLOTS: u64 = 100; diff --git a/sdk/src/wallet/account/operations/balance.rs b/sdk/src/wallet/account/operations/balance.rs index e45485efda..0a6d8fb026 100644 --- a/sdk/src/wallet/account/operations/balance.rs +++ b/sdk/src/wallet/account/operations/balance.rs @@ -164,7 +164,7 @@ where // spend the balance at the moment or in the future let account_addresses = self.addresses().await?; - let local_time = self.client().get_time_checked().await?; + let slot_index = self.client().get_slot_index().await?; let is_claimable = self.claimable_outputs(OutputsToClaim::All).await?.contains(output_id); @@ -182,7 +182,7 @@ where // outputs can't be related to this output &account_details.addresses_with_unspent_outputs, output, - local_time, + slot_index, ); if output_can_be_unlocked_now_and_in_future { @@ -254,7 +254,7 @@ where .expiration() { // Not expired, could get unlockable when it's expired, so we insert it - if local_time < expiration.timestamp() { + if slot_index < expiration.slot_index() { balance.potentially_locked_outputs.insert(*output_id, false); } } else { diff --git a/sdk/src/wallet/account/operations/helpers/time.rs b/sdk/src/wallet/account/operations/helpers/time.rs index efd977912c..271b42929d 100644 --- a/sdk/src/wallet/account/operations/helpers/time.rs +++ b/sdk/src/wallet/account/operations/helpers/time.rs @@ -5,6 +5,7 @@ use crate::{ types::block::{ address::Address, output::{AccountTransition, Output}, + slot::SlotIndex, }, wallet::account::types::{AddressWithUnspentOutputs, OutputData}, }; @@ -17,18 +18,18 @@ pub(crate) fn can_output_be_unlocked_now( account_addresses: &[AddressWithUnspentOutputs], account_and_nft_addresses: &[Address], output_data: &OutputData, - current_time: u32, + slot_index: SlotIndex, account_transition: Option, ) -> crate::wallet::Result { if let Some(unlock_conditions) = output_data.output.unlock_conditions() { - if unlock_conditions.is_time_locked(current_time) { + if unlock_conditions.is_time_locked(slot_index) { return Ok(false); } } let (required_unlock_address, _unlocked_account_or_nft_address) = output_data .output - .required_and_unlocked_address(current_time, &output_data.output_id, account_transition)?; + .required_and_unlocked_address(slot_index, &output_data.output_id, account_transition)?; Ok(account_addresses .iter() @@ -43,17 +44,17 @@ pub(crate) fn can_output_be_unlocked_forever_from_now_on( // be related to this output account_addresses: &[AddressWithUnspentOutputs], output: &Output, - current_time: u32, + slot_index: SlotIndex, ) -> bool { if let Some(unlock_conditions) = output.unlock_conditions() { - if unlock_conditions.is_time_locked(current_time) { + if unlock_conditions.is_time_locked(slot_index) { 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(current_time) { + if let Some(return_address) = expiration.return_address_expired(slot_index) { if !account_addresses.iter().any(|a| a.address.inner == *return_address) { return false; }; diff --git a/sdk/src/wallet/account/operations/output_claiming.rs b/sdk/src/wallet/account/operations/output_claiming.rs index b2f3d30a5b..66192f018a 100644 --- a/sdk/src/wallet/account/operations/output_claiming.rs +++ b/sdk/src/wallet/account/operations/output_claiming.rs @@ -14,6 +14,7 @@ use crate::{ BasicOutputBuilder, MinimumStorageDepositBasicOutput, NativeTokens, NativeTokensBuilder, NftOutputBuilder, Output, OutputId, }, + slot::SlotIndex, }, wallet::account::{ operations::helpers::time::can_output_be_unlocked_now, types::Transaction, Account, OutputData, @@ -46,7 +47,7 @@ where log::debug!("[OUTPUT_CLAIMING] claimable_outputs"); let account_details = self.details().await; - let local_time = self.client().get_time_checked().await?; + let slot_index = self.client().get_slot_index().await?; // Get outputs for the claim let mut output_ids_to_claim: HashSet = HashSet::new(); @@ -69,7 +70,7 @@ where // outputs controlled by an account or nft are currently not considered &[], output_data, - local_time, + slot_index, // Not relevant without account addresses None, )? @@ -78,7 +79,7 @@ where 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(local_time) { + if unlock_conditions.is_expired(slot_index) { continue; } // Only micro transaction if not the same @@ -99,7 +100,7 @@ where } OutputsToClaim::Amount => { let mut claimable_amount = output_data.output.amount(); - if !unlock_conditions.is_expired(local_time) { + if !unlock_conditions.is_expired(slot_index) { claimable_amount -= unlock_conditions .storage_deposit_return() .map(|s| s.amount()) @@ -201,7 +202,7 @@ where { log::debug!("[OUTPUT_CLAIMING] claim_outputs_internal"); - let current_time = self.client().get_time_checked().await?; + let slot_index = self.client().get_slot_index().await?; let rent_structure = self.client().get_rent_structure().await?; let token_supply = self.client().get_token_supply().await?; @@ -252,7 +253,7 @@ where } new_native_tokens.add_native_tokens(native_tokens.clone())?; } - if let Some(sdr) = sdr_not_expired(&output_data.output, current_time) { + if let Some(sdr) = sdr_not_expired(&output_data.output, slot_index) { // for own output subtract the return amount available_amount += output_data.output.amount() - sdr.amount(); @@ -407,12 +408,12 @@ where } /// Get the `StorageDepositReturnUnlockCondition`, if not expired -pub(crate) fn sdr_not_expired(output: &Output, current_time: u32) -> Option<&StorageDepositReturnUnlockCondition> { +pub(crate) fn sdr_not_expired(output: &Output, slot_index: SlotIndex) -> Option<&StorageDepositReturnUnlockCondition> { output.unlock_conditions().and_then(|unlock_conditions| { unlock_conditions.storage_deposit_return().and_then(|sdr| { let expired = unlock_conditions .expiration() - .map_or(false, |expiration| current_time >= expiration.timestamp()); + .map_or(false, |expiration| slot_index >= expiration.slot_index()); // We only have to send the storage deposit return back if the output is not expired (!expired).then_some(sdr) diff --git a/sdk/src/wallet/account/operations/output_consolidation.rs b/sdk/src/wallet/account/operations/output_consolidation.rs index 071b2d1c11..25bec14c21 100644 --- a/sdk/src/wallet/account/operations/output_consolidation.rs +++ b/sdk/src/wallet/account/operations/output_consolidation.rs @@ -13,6 +13,7 @@ use crate::{ output::{ unlock_condition::AddressUnlockCondition, BasicOutputBuilder, NativeTokens, NativeTokensBuilder, Output, }, + slot::SlotIndex, }, }; @@ -76,13 +77,13 @@ where fn should_consolidate_output( &self, output_data: &OutputData, - current_time: u32, + slot_index: SlotIndex, account_addresses: &[AddressWithUnspentOutputs], ) -> Result { Ok(if let Output::Basic(basic_output) = &output_data.output { let unlock_conditions = basic_output.unlock_conditions(); - let is_time_locked = unlock_conditions.is_time_locked(current_time); + let is_time_locked = unlock_conditions.is_time_locked(slot_index); if is_time_locked { // If the output is timelocked, then it cannot be consolidated. return Ok(false); @@ -90,13 +91,13 @@ 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(current_time); + let is_expired = unlock_conditions.is_expired(slot_index); 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(account_addresses, &[], output_data, current_time, None)? + can_output_be_unlocked_now(account_addresses, &[], output_data, slot_index, None)? } else { false }) @@ -125,7 +126,7 @@ where log::debug!("[OUTPUT_CONSOLIDATION] prepare consolidating outputs if needed"); #[cfg(feature = "participation")] let voting_output = self.get_voting_output().await?; - let current_time = self.client().get_time_checked().await?; + let slot_index = self.client().get_slot_index().await?; let token_supply = self.client().get_token_supply().await?; let mut outputs_to_consolidate = Vec::new(); let account_details = self.details().await; @@ -141,7 +142,7 @@ where } let is_locked_output = account_details.locked_outputs.contains(output_id); let should_consolidate_output = - self.should_consolidate_output(output_data, current_time, account_addresses)?; + self.should_consolidate_output(output_data, slot_index, account_addresses)?; if !is_locked_output && should_consolidate_output { outputs_to_consolidate.push(output_data.clone()); } diff --git a/sdk/src/wallet/account/operations/transaction/high_level/send.rs b/sdk/src/wallet/account/operations/transaction/high_level/send.rs index c1ca3514a6..943aa87b9f 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/send.rs +++ b/sdk/src/wallet/account/operations/transaction/high_level/send.rs @@ -14,12 +14,13 @@ use crate::{ }, BasicOutputBuilder, MinimumStorageDepositBasicOutput, }, + slot::SlotIndex, ConvertTo, }, utils::serde::string, wallet::{ account::{ - constants::DEFAULT_EXPIRATION_TIME, operations::transaction::Transaction, Account, TransactionOptions, + constants::DEFAULT_EXPIRATION_SLOTS, operations::transaction::Transaction, Account, TransactionOptions, }, Error, }, @@ -40,11 +41,11 @@ pub struct SendParams { /// default to the first address of the account. #[getset(get = "pub")] return_address: Option, - /// Expiration in seconds, after which the output will be available for the sender again, if not spent by the + /// Expiration in slot indices, after which the output will be available for the sender again, if not spent by the /// receiver already. The expiration will only be used if one is necessary given the provided amount. If an /// expiration is needed but not provided, it will default to one day. #[getset(get = "pub")] - expiration: Option, + expiration: Option, } impl SendParams { @@ -70,7 +71,7 @@ impl SendParams { self } - pub fn with_expiration(mut self, expiration: impl Into>) -> Self { + pub fn with_expiration(mut self, expiration: impl Into>) -> Self { self.expiration = expiration.into(); self } @@ -144,7 +145,7 @@ where let account_addresses = self.addresses().await?; let default_return_address = account_addresses.first().ok_or(Error::FailedToGetRemainder)?; - let local_time = self.client().get_time_checked().await?; + let slot_index = self.client().get_slot_index().await?; let mut outputs = Vec::new(); for SendParams { @@ -180,9 +181,10 @@ where .finish_output(token_supply)?, ) } else { - let expiration_time = expiration.map_or(local_time + DEFAULT_EXPIRATION_TIME, |expiration_time| { - local_time + expiration_time - }); + let expiration_slot_index = expiration + .map_or(slot_index + DEFAULT_EXPIRATION_SLOTS, |expiration_slot_index| { + slot_index + expiration_slot_index + }); // Since it does need a storage deposit, calculate how much that should be let storage_deposit_amount = MinimumStorageDepositBasicOutput::new(rent_structure, token_supply) @@ -211,7 +213,7 @@ where token_supply, )?, ) - .add_unlock_condition(ExpirationUnlockCondition::new(return_address, expiration_time)?) + .add_unlock_condition(ExpirationUnlockCondition::new(return_address, expiration_slot_index)?) .finish_output(token_supply)?, ) } diff --git a/sdk/src/wallet/account/operations/transaction/high_level/send_native_tokens.rs b/sdk/src/wallet/account/operations/transaction/high_level/send_native_tokens.rs index 7ff54cd4bd..758074730e 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/send_native_tokens.rs +++ b/sdk/src/wallet/account/operations/transaction/high_level/send_native_tokens.rs @@ -15,11 +15,12 @@ use crate::{ }, BasicOutputBuilder, MinimumStorageDepositBasicOutput, NativeToken, NativeTokens, TokenId, }, + slot::SlotIndex, ConvertTo, }, wallet::{ account::{ - constants::DEFAULT_EXPIRATION_TIME, operations::transaction::Transaction, Account, TransactionOptions, + constants::DEFAULT_EXPIRATION_SLOTS, operations::transaction::Transaction, Account, TransactionOptions, }, Error, Result, }, @@ -39,10 +40,10 @@ pub struct SendNativeTokensParams { /// first address of the account #[getset(get = "pub")] return_address: Option, - /// Expiration in seconds, after which the output will be available for the sender again, if not spent by the - /// receiver before. Default is 1 day + /// Expiration in slot indices, after which the output will be available for the sender again, if not spent by the + /// receiver before. Default is [`DEFAULT_EXPIRATION_SLOTS`] slots. #[getset(get = "pub")] - expiration: Option, + expiration: Option, } impl SendNativeTokensParams { @@ -72,8 +73,8 @@ impl SendNativeTokensParams { } /// Set the expiration in seconds - pub fn with_expiration(mut self, expiration_secs: Option) -> Self { - self.expiration = expiration_secs; + pub fn with_expiration(mut self, expiration: Option) -> Self { + self.expiration = expiration; self } } @@ -134,7 +135,7 @@ where let account_addresses = self.addresses().await?; let default_return_address = account_addresses.first().ok_or(Error::FailedToGetRemainder)?; - let local_time = self.client().get_time_checked().await?; + let slot_index = self.client().get_slot_index().await?; let mut outputs = Vec::new(); for SendNativeTokensParams { @@ -176,9 +177,10 @@ where .with_expiration()? .finish()?; - let expiration_time = expiration.map_or(local_time + DEFAULT_EXPIRATION_TIME, |expiration_time| { - local_time + expiration_time - }); + let expiration_slot_index = expiration + .map_or(slot_index + DEFAULT_EXPIRATION_SLOTS, |expiration_slot_index| { + slot_index + expiration_slot_index + }); outputs.push( BasicOutputBuilder::new_with_amount(storage_deposit_amount) @@ -189,7 +191,7 @@ where // sent StorageDepositReturnUnlockCondition::new(return_address, storage_deposit_amount, token_supply)?, ) - .add_unlock_condition(ExpirationUnlockCondition::new(return_address, expiration_time)?) + .add_unlock_condition(ExpirationUnlockCondition::new(return_address, expiration_slot_index)?) .finish_output(token_supply)?, ) } diff --git a/sdk/src/wallet/account/operations/transaction/input_selection.rs b/sdk/src/wallet/account/operations/transaction/input_selection.rs index 94304e781e..dacf54d910 100644 --- a/sdk/src/wallet/account/operations/transaction/input_selection.rs +++ b/sdk/src/wallet/account/operations/transaction/input_selection.rs @@ -13,6 +13,7 @@ use crate::{ types::block::{ address::Address, output::{Output, OutputId}, + slot::SlotIndex, }, wallet::account::{ operations::helpers::time::can_output_be_unlocked_forever_from_now_on, Account, AccountDetails, OutputData, @@ -47,7 +48,7 @@ where ) .await; - let current_time = self.client().get_time_checked().await?; + let slot_index = self.client().get_slot_index().await?; #[allow(unused_mut)] let mut forbidden_inputs = account_details.locked_outputs.clone(); @@ -74,7 +75,7 @@ where let available_outputs_signing_data = filter_inputs( &account_details, account_details.unspent_outputs.values(), - current_time, + slot_index, &outputs, burn, custom_inputs.as_ref(), @@ -225,7 +226,7 @@ where fn filter_inputs( account: &AccountDetails, available_outputs: Values<'_, OutputId, OutputData>, - current_time: u32, + slot_index: SlotIndex, outputs: &[Output], burn: Option<&Burn>, custom_inputs: Option<&HashSet>, @@ -246,7 +247,7 @@ fn filter_inputs( // account without unspent outputs can't be related to this output &account.addresses_with_unspent_outputs, &output_data.output, - current_time, + slot_index, ); // Outputs that could get unlocked in the future will not be included @@ -258,9 +259,7 @@ fn filter_inputs( // Defaults to state transition if it is not explicitly a governance transition or a burn. let account_state_transition = is_account_transition(&output_data.output, output_data.output_id, outputs, burn); - if let Some(available_input) = - output_data.input_signing_data(account, current_time, account_state_transition)? - { + if let Some(available_input) = output_data.input_signing_data(account, slot_index, account_state_transition)? { available_outputs_signing_data.push(available_input); } } diff --git a/sdk/src/wallet/account/operations/transaction/mod.rs b/sdk/src/wallet/account/operations/transaction/mod.rs index 4f3f44fe35..f8a507b0e6 100644 --- a/sdk/src/wallet/account/operations/transaction/mod.rs +++ b/sdk/src/wallet/account/operations/transaction/mod.rs @@ -128,12 +128,9 @@ where let options = options.into(); // Validate transaction before sending and storing it - let local_time = self.client().get_time_checked().await?; - let conflict = verify_semantic( &signed_transaction_data.inputs_data, &signed_transaction_data.transaction_payload, - local_time, )?; if let Some(conflict) = conflict { diff --git a/sdk/src/wallet/account/operations/transaction/prepare_output.rs b/sdk/src/wallet/account/operations/transaction/prepare_output.rs index 1646627308..f682314eb9 100644 --- a/sdk/src/wallet/account/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/account/operations/transaction/prepare_output.rs @@ -16,6 +16,7 @@ use crate::{ BasicOutputBuilder, MinimumStorageDepositBasicOutput, NativeToken, NftId, NftOutputBuilder, Output, Rent, RentStructure, UnlockCondition, }, + slot::SlotIndex, Error, }, utils::serde::string, @@ -85,15 +86,17 @@ where } if let Some(unlocks) = params.unlocks { - if let Some(expiration_unix_time) = unlocks.expiration_unix_time { + if let Some(expiration_slot_index) = unlocks.expiration_slot_index { let remainder_address = self.get_remainder_address(transaction_options.clone()).await?; - first_output_builder = first_output_builder - .add_unlock_condition(ExpirationUnlockCondition::new(remainder_address, expiration_unix_time)?); + first_output_builder = first_output_builder.add_unlock_condition(ExpirationUnlockCondition::new( + remainder_address, + expiration_slot_index, + )?); } - if let Some(timelock_unix_time) = unlocks.timelock_unix_time { + if let Some(timelock_slot_index) = unlocks.timelock_slot_index { first_output_builder = - first_output_builder.add_unlock_condition(TimelockUnlockCondition::new(timelock_unix_time)?); + first_output_builder.add_unlock_condition(TimelockUnlockCondition::new(timelock_slot_index)?); } } @@ -346,8 +349,8 @@ pub struct Features { #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Unlocks { - pub expiration_unix_time: Option, - pub timelock_unix_time: Option, + pub expiration_slot_index: Option, + pub timelock_slot_index: Option, } #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] diff --git a/sdk/src/wallet/account/operations/transaction/sign_transaction.rs b/sdk/src/wallet/account/operations/transaction/sign_transaction.rs index 8debbde7f4..883d0631a4 100644 --- a/sdk/src/wallet/account/operations/transaction/sign_transaction.rs +++ b/sdk/src/wallet/account/operations/transaction/sign_transaction.rs @@ -79,7 +79,7 @@ where .secret_manager .read() .await - .sign_transaction_essence(prepared_transaction_data, None) + .sign_transaction_essence(prepared_transaction_data) .await { Ok(res) => res, diff --git a/sdk/src/wallet/account/types/mod.rs b/sdk/src/wallet/account/types/mod.rs index 9e0cd02ecf..10cfb081e9 100644 --- a/sdk/src/wallet/account/types/mod.rs +++ b/sdk/src/wallet/account/types/mod.rs @@ -24,6 +24,7 @@ use crate::{ address::Address, output::{dto::OutputDto, AccountTransition, Output, OutputId, OutputMetadata}, payload::transaction::{dto::TransactionPayloadDto, TransactionId, TransactionPayload}, + slot::SlotIndex, BlockId, Error as BlockError, }, TryFromDto, @@ -55,12 +56,12 @@ impl OutputData { pub fn input_signing_data( &self, account: &AccountDetails, - current_time: u32, + slot_index: SlotIndex, account_transition: Option, ) -> crate::wallet::Result> { let (unlock_address, _unlocked_account_or_nft_address) = self.output - .required_and_unlocked_address(current_time, &self.output_id, account_transition)?; + .required_and_unlocked_address(slot_index, &self.output_id, account_transition)?; let chain = if unlock_address == self.address { self.chain diff --git a/sdk/tests/client/input_selection/expiration.rs b/sdk/tests/client/input_selection/expiration.rs index a90a92dd26..98baac9392 100644 --- a/sdk/tests/client/input_selection/expiration.rs +++ b/sdk/tests/client/input_selection/expiration.rs @@ -49,7 +49,7 @@ fn one_output_expiration_not_expired() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select(); assert!(matches!(selected, Err(Error::NoAvailableInputsProvided))); @@ -86,7 +86,7 @@ fn expiration_equal_timestamp() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(200) + .slot_index(200) .select() .unwrap(); @@ -125,7 +125,7 @@ fn one_output_expiration_expired() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -176,7 +176,7 @@ fn two_outputs_one_expiration_expired() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -219,7 +219,7 @@ fn two_outputs_one_unexpired_one_missing() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -272,7 +272,7 @@ fn two_outputs_two_expired() { addresses([BECH32_ADDRESS_ED25519_2]), protocol_parameters, ) - .timestamp(200) + .slot_index(200) .select() .unwrap(); @@ -324,7 +324,7 @@ fn two_outputs_two_expired_2() { addresses([BECH32_ADDRESS_ED25519_1, BECH32_ADDRESS_ED25519_2]), protocol_parameters, ) - .timestamp(200) + .slot_index(200) .select() .unwrap(); @@ -363,7 +363,7 @@ fn expiration_expired_with_sdr() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -402,7 +402,7 @@ fn expiration_expired_with_sdr_2() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -441,7 +441,7 @@ fn expiration_expired_with_sdr_and_timelock() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -480,7 +480,7 @@ fn expiration_expired_with_sdr_and_timelock_2() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -525,7 +525,7 @@ fn sender_in_expiration() { addresses([BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -565,7 +565,7 @@ fn sender_in_expiration_already_selected() { addresses([BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .required_inputs([*inputs[0].output_id()]) .select() .unwrap(); @@ -605,7 +605,7 @@ fn remainder_in_expiration() { addresses([BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -655,7 +655,7 @@ fn expiration_expired_non_ed25519_in_address_unlock_condition() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -709,7 +709,7 @@ fn expiration_expired_only_account_addresses() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -751,7 +751,7 @@ fn one_nft_output_expiration_unexpired() { addresses([BECH32_ADDRESS_ED25519_1]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -793,7 +793,7 @@ fn one_nft_output_expiration_expired() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); diff --git a/sdk/tests/client/input_selection/timelock.rs b/sdk/tests/client/input_selection/timelock.rs index 4f94df0e43..6df09e3f15 100644 --- a/sdk/tests/client/input_selection/timelock.rs +++ b/sdk/tests/client/input_selection/timelock.rs @@ -42,7 +42,7 @@ fn one_output_timelock_not_expired() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select(); assert!(matches!(selected, Err(Error::NoAvailableInputsProvided))); @@ -79,7 +79,7 @@ fn timelock_equal_timestamp() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(200) + .slot_index(200) .select() .unwrap(); @@ -130,7 +130,7 @@ fn two_outputs_one_timelock_expired() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -173,7 +173,7 @@ fn two_outputs_one_timelocked_one_missing() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); @@ -213,7 +213,7 @@ fn one_output_timelock_expired() { addresses([BECH32_ADDRESS_ED25519_0]), protocol_parameters, ) - .timestamp(100) + .slot_index(100) .select() .unwrap(); diff --git a/sdk/tests/client/mod.rs b/sdk/tests/client/mod.rs index e1ea886413..aebed6d716 100644 --- a/sdk/tests/client/mod.rs +++ b/sdk/tests/client/mod.rs @@ -68,8 +68,8 @@ enum Build<'a> { Option>, Option<&'a str>, Option<(&'a str, u64)>, - Option, - Option<(&'a str, u32)>, + Option, + Option<(&'a str, u64)>, Option, ), Nft( @@ -80,7 +80,7 @@ enum Build<'a> { Option<&'a str>, Option<&'a str>, Option<(&'a str, u64)>, - Option<(&'a str, u32)>, + Option<(&'a str, u64)>, Option, ), Account( @@ -103,8 +103,8 @@ fn build_basic_output( native_tokens: Option>, bech32_sender: Option, sdruc: Option<(Bech32Address, u64)>, - timelock: Option, - expiration: Option<(Bech32Address, u32)>, + timelock: Option, + expiration: Option<(Bech32Address, u64)>, ) -> Output { let mut builder = BasicOutputBuilder::new_with_amount(amount).add_unlock_condition(AddressUnlockCondition::new(bech32_address)); @@ -146,7 +146,7 @@ fn build_nft_output( bech32_sender: Option, bech32_issuer: Option, sdruc: Option<(Bech32Address, u64)>, - expiration: Option<(Bech32Address, u32)>, + expiration: Option<(Bech32Address, u64)>, ) -> Output { let mut builder = NftOutputBuilder::new_with_amount(amount, nft_id) .add_unlock_condition(AddressUnlockCondition::new(bech32_address)); diff --git a/sdk/tests/client/signing/account.rs b/sdk/tests/client/signing/account.rs index e884c30d9d..91d0c49aff 100644 --- a/sdk/tests/client/signing/account.rs +++ b/sdk/tests/client/signing/account.rs @@ -105,7 +105,7 @@ async fn sign_account_state_transition() -> Result<()> { }; let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(0)) + .sign_transaction_essence(&prepared_transaction_data) .await?; assert_eq!(unlocks.len(), 1); @@ -115,9 +115,7 @@ async fn sign_account_state_transition() -> Result<()> { validate_transaction_payload_length(&tx_payload)?; - let current_time = 100; - - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); @@ -197,7 +195,7 @@ async fn sign_account_governance_transition() -> Result<()> { }; let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(0)) + .sign_transaction_essence(&prepared_transaction_data) .await?; assert_eq!(unlocks.len(), 1); @@ -207,9 +205,7 @@ async fn sign_account_governance_transition() -> Result<()> { validate_transaction_payload_length(&tx_payload)?; - let current_time = 100; - - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); @@ -329,7 +325,7 @@ async fn account_reference_unlocks() -> Result<()> { }; let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(0)) + .sign_transaction_essence(&prepared_transaction_data) .await?; assert_eq!(unlocks.len(), 3); @@ -351,9 +347,7 @@ async fn account_reference_unlocks() -> Result<()> { validate_transaction_payload_length(&tx_payload)?; - let current_time = 100; - - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; 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 7eb388c400..bccba46445 100644 --- a/sdk/tests/client/signing/basic.rs +++ b/sdk/tests/client/signing/basic.rs @@ -88,7 +88,7 @@ async fn single_ed25519_unlock() -> Result<()> { }; let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(0)) + .sign_transaction_essence(&prepared_transaction_data) .await?; assert_eq!(unlocks.len(), 1); @@ -98,9 +98,7 @@ async fn single_ed25519_unlock() -> Result<()> { validate_transaction_payload_length(&tx_payload)?; - let current_time = 100; - - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); @@ -191,7 +189,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { }; let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(0)) + .sign_transaction_essence(&prepared_transaction_data) .await?; assert_eq!(unlocks.len(), 3); @@ -213,9 +211,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { validate_transaction_payload_length(&tx_payload)?; - let current_time = 100; - - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); @@ -304,7 +300,7 @@ async fn two_signature_unlocks() -> Result<()> { }; let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(0)) + .sign_transaction_essence(&prepared_transaction_data) .await?; assert_eq!(unlocks.len(), 2); @@ -315,9 +311,7 @@ async fn two_signature_unlocks() -> Result<()> { validate_transaction_payload_length(&tx_payload)?; - let current_time = 100; - - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; 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 469b1ab37f..e78632f79e 100644 --- a/sdk/tests/client/signing/mod.rs +++ b/sdk/tests/client/signing/mod.rs @@ -28,6 +28,7 @@ use iota_sdk::{ }, protocol::protocol_parameters, rand::mana::rand_mana_allotment, + slot::SlotIndex, unlock::{SignatureUnlock, Unlock}, }, }; @@ -353,7 +354,7 @@ async fn all_combined() -> Result<()> { ), ]); - let current_time = 100; + let slot_index = SlotIndex::from(100); let selected = InputSelection::new( inputs.clone(), @@ -365,7 +366,7 @@ async fn all_combined() -> Result<()> { ], protocol_parameters.clone(), ) - .timestamp(current_time) + .slot_index(slot_index) .select() .unwrap(); @@ -382,6 +383,7 @@ async fn all_combined() -> Result<()> { .collect::>(), ) .with_outputs(outputs) + .with_creation_slot(slot_index) .add_mana_allotment(rand_mana_allotment()) .finish_with_params(protocol_parameters)?, ); @@ -393,7 +395,7 @@ async fn all_combined() -> Result<()> { }; let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(current_time)) + .sign_transaction_essence(&prepared_transaction_data) .await?; assert_eq!(unlocks.len(), 15); @@ -478,7 +480,7 @@ async fn all_combined() -> Result<()> { validate_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; 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 aa48cbe97e..bfd579659b 100644 --- a/sdk/tests/client/signing/nft.rs +++ b/sdk/tests/client/signing/nft.rs @@ -131,10 +131,8 @@ async fn nft_reference_unlocks() -> Result<()> { remainder: None, }; - let current_time = 100; - let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(current_time)) + .sign_transaction_essence(&prepared_transaction_data) .await?; assert_eq!(unlocks.len(), 3); @@ -156,7 +154,7 @@ async fn nft_reference_unlocks() -> Result<()> { validate_transaction_payload_length(&tx_payload)?; - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; + let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload)?; if let Some(conflict) = conflict { panic!("{conflict:?}, with {tx_payload:#?}"); diff --git a/sdk/tests/wallet/balance.rs b/sdk/tests/wallet/balance.rs index d83949261a..53f1f279e5 100644 --- a/sdk/tests/wallet/balance.rs +++ b/sdk/tests/wallet/balance.rs @@ -98,7 +98,7 @@ async fn balance_expiration() -> Result<()> { let account_1 = wallet.create_account().finish().await?; let account_2 = wallet.create_account().finish().await?; - let seconds_until_expired = 20; + let slots_until_expired = 20; let token_supply = account_0.client().get_token_supply().await?; let outputs = [BasicOutputBuilder::new_with_amount(1_000_000) // Send to account 1 with expiration to account 2, both have no amount yet @@ -108,8 +108,7 @@ async fn balance_expiration() -> Result<()> { )), UnlockCondition::Expiration(ExpirationUnlockCondition::new( *account_2.addresses().await?[0].address().as_ref(), - // Current time + 20s - account_0.client().get_time_checked().await? + seconds_until_expired, + account_0.client().get_slot_index().await? + slots_until_expired, )?), ]) .with_features([SenderFeature::new(*account_0.addresses().await?[0].address().as_ref())]) @@ -142,7 +141,8 @@ async fn balance_expiration() -> Result<()> { assert_eq!(balance.base_coin().available(), 0); // Wait until expired - tokio::time::sleep(std::time::Duration::from_secs(seconds_until_expired.into())).await; + // TODO wait for slots, not seconds + tokio::time::sleep(std::time::Duration::from_secs(slots_until_expired)).await; // Account 1 balance after expiration let balance = account_1.sync(None).await?; diff --git a/sdk/tests/wallet/claim_outputs.rs b/sdk/tests/wallet/claim_outputs.rs index 723a8009c7..706811796e 100644 --- a/sdk/tests/wallet/claim_outputs.rs +++ b/sdk/tests/wallet/claim_outputs.rs @@ -128,7 +128,8 @@ async fn claim_2_basic_outputs_no_outputs_in_claim_account() -> Result<()> { let token_supply = account_0.client().get_token_supply().await?; let rent_structure = account_0.client().get_rent_structure().await?; - let expiration_time = account_0.client().get_time_checked().await? + 86400; // 1 Day from now + // TODO more fitting value + let expiration_slot = account_0.client().get_slot_index().await? + 86400; let output = BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) .add_unlock_condition(AddressUnlockCondition::new( @@ -136,7 +137,7 @@ async fn claim_2_basic_outputs_no_outputs_in_claim_account() -> Result<()> { )) .add_unlock_condition(ExpirationUnlockCondition::new( *account_0.addresses().await?[0].address().as_ref(), - expiration_time, + expiration_slot, )?) .finish_output(token_supply)?; let amount = output.amount(); @@ -333,7 +334,7 @@ async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<()> { )) .add_unlock_condition(ExpirationUnlockCondition::new( *account_0.addresses().await?[0].address().as_ref(), - account_0.client().get_time_checked().await? + 5000, + account_0.client().get_slot_index().await? + 5000, )?) .add_native_token(NativeToken::new(create_tx_0.token_id, native_token_amount)?) .finish_output(token_supply)?, @@ -343,7 +344,7 @@ async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<()> { )) .add_unlock_condition(ExpirationUnlockCondition::new( *account_0.addresses().await?[0].address().as_ref(), - account_0.client().get_time_checked().await? + 5000, + account_0.client().get_slot_index().await? + 5000, )?) .add_native_token(NativeToken::new(create_tx_1.token_id, native_token_amount)?) .finish_output(token_supply)?, @@ -405,7 +406,7 @@ async fn claim_2_nft_outputs() -> Result<()> { )), UnlockCondition::Expiration(ExpirationUnlockCondition::new( *accounts[1].addresses().await?[0].address().as_ref(), - accounts[1].client().get_time_checked().await? + 5000, + accounts[1].client().get_slot_index().await? + 5000, )?), ]) .finish_output(token_supply)?, @@ -416,7 +417,7 @@ async fn claim_2_nft_outputs() -> Result<()> { )), UnlockCondition::Expiration(ExpirationUnlockCondition::new( *accounts[1].addresses().await?[0].address().as_ref(), - accounts[1].client().get_time_checked().await? + 5000, + accounts[1].client().get_slot_index().await? + 5000, )?), ]) .finish_output(token_supply)?, @@ -466,7 +467,7 @@ async fn claim_2_nft_outputs_no_outputs_in_claim_account() -> Result<()> { )), UnlockCondition::Expiration(ExpirationUnlockCondition::new( *account_0.addresses().await?[0].address().as_ref(), - account_0.client().get_time_checked().await? + 5000, + account_0.client().get_slot_index().await? + 5000, )?), ]) .finish_output(token_supply)?, @@ -477,7 +478,7 @@ async fn claim_2_nft_outputs_no_outputs_in_claim_account() -> Result<()> { )), UnlockCondition::Expiration(ExpirationUnlockCondition::new( *account_0.addresses().await?[0].address().as_ref(), - account_0.client().get_time_checked().await? + 5000, + account_0.client().get_slot_index().await? + 5000, )?), ]) .finish_output(token_supply)?, diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index 245839c754..37e36ba2af 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -7,6 +7,7 @@ use iota_sdk::{ types::block::{ address::{Address, Bech32Address, ToBech32Ext}, output::{MinimumStorageDepositBasicOutput, NativeToken, NftId, Output, Rent, TokenId}, + slot::SlotIndex, }, wallet::{ account::{Assets, Features, OutputParams, ReturnStrategy, StorageDeposit, Unlocks}, @@ -329,8 +330,8 @@ async fn output_preparation() -> Result<()> { sender: Some(issuer_and_sender_address), }), unlocks: Some(Unlocks { - expiration_unix_time: Some(1), - timelock_unix_time: Some(1), + expiration_slot_index: Some(SlotIndex::from(1)), + timelock_slot_index: Some(SlotIndex::from(1)), }), storage_deposit: None, }, @@ -373,8 +374,8 @@ async fn output_preparation() -> Result<()> { sender: None, }), unlocks: Some(Unlocks { - expiration_unix_time: Some(1), - timelock_unix_time: None, + expiration_slot_index: Some(SlotIndex::from(1)), + timelock_slot_index: None, }), storage_deposit: None, }, diff --git a/sdk/tests/wallet/syncing.rs b/sdk/tests/wallet/syncing.rs index 239cb915bf..529e5479f8 100644 --- a/sdk/tests/wallet/syncing.rs +++ b/sdk/tests/wallet/syncing.rs @@ -70,7 +70,7 @@ async fn sync_only_most_basic_outputs() -> Result<()> { UnlockCondition::Expiration(ExpirationUnlockCondition::new( account_1_address, // Already expired - account_0.client().get_time_checked().await? - 5000, + account_0.client().get_slot_index().await? - 5000, )?), ]) .finish_output(token_supply)?, @@ -80,7 +80,7 @@ async fn sync_only_most_basic_outputs() -> Result<()> { UnlockCondition::Expiration(ExpirationUnlockCondition::new( account_1_address, // Not expired - account_0.client().get_time_checked().await? + 5000, + account_0.client().get_slot_index().await? + 5000, )?), ]) .finish_output(token_supply)?, @@ -102,7 +102,7 @@ async fn sync_only_most_basic_outputs() -> Result<()> { UnlockCondition::Address(AddressUnlockCondition::new(account_1_address)), UnlockCondition::Expiration(ExpirationUnlockCondition::new( account_1_address, - account_0.client().get_time_checked().await? + 5000, + account_0.client().get_slot_index().await? + 5000, )?), ]) .finish_output(token_supply)?,