Skip to content

Commit

Permalink
ISA: mana remainder (#1831)
Browse files Browse the repository at this point in the history
* validate some mana semantics

* Cleanup

* delta->diff

* Nit

* Allow passing an index for test inputs IDs

* Fix tests

* rand nit

* Simplify tests

* Add link to issue

* Update sdk/src/types/block/semantic/mod.rs

Co-authored-by: Thoralf-M <[email protected]>

* Format

* ISA: mana remainder

* Some fixes

* More fixes

* Put mana remainder to automatically transitioned chain if possible

* Fix expiration tests

* No need for +1

* Fix timelock tests

* Fix NFT tests

* Fix foundry tests

* Fix storage deposit tests

* Fix last tests

* Nit

* More nits

* Remove commented code

* Set BIC input if account is transitioned

* Allow one NT remainder

* Remove prints

* Cleanup context inputs

* Add comment

* Remove clones

* Nits

* Remove panic

* Use all_mana

* Add link to TODO

* Cleanup

* Remove TODO

* add log

* Ok(Selected

* mana cleanup

* checked_sub

* available_mana

* Prefer accounts

* Remove cloned

* move up

---------

Co-authored-by: Alex Coats <[email protected]>
Co-authored-by: Thoralf-M <[email protected]>
Co-authored-by: /alex/ <[email protected]>
  • Loading branch information
4 people committed Jan 16, 2024
1 parent f80ba17 commit 3952634
Show file tree
Hide file tree
Showing 21 changed files with 482 additions and 287 deletions.
2 changes: 1 addition & 1 deletion cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub struct InitParameters {
#[arg(short, long, value_name = "URL", env = "NODE_URL", default_value = DEFAULT_NODE_URL)]
pub node_url: String,
/// Set the BIP path, `4219/0/0/0` if not provided.
#[arg(short, long, value_parser = parse_bip_path)]
#[arg(short, long, value_parser = parse_bip_path, default_value = "4219/0/0/0")]
pub bip_path: Option<Bip44>,
/// Set the Bech32-encoded wallet address.
#[arg(short, long)]
Expand Down
10 changes: 2 additions & 8 deletions sdk/src/client/api/block_builder/input_selection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ impl InputSelection {
available_inputs: impl Into<Vec<InputSigningData>>,
outputs: impl Into<Vec<Output>>,
addresses: impl IntoIterator<Item = Address>,
slot_index: impl Into<SlotIndex>,
protocol_parameters: ProtocolParameters,
) -> Self {
let available_inputs = available_inputs.into();
Expand Down Expand Up @@ -189,9 +190,8 @@ impl InputSelection {
burn: None,
remainder_address: None,
protocol_parameters,
// TODO may want to make this mandatory at some point
// Should be set from a commitment context input
slot_index: SlotIndex::from(0),
slot_index: slot_index.into(),
requirements: Vec::new(),
automatically_transitioned: HashSet::new(),
mana_allotments: 0,
Expand Down Expand Up @@ -222,12 +222,6 @@ impl InputSelection {
self
}

/// Sets the slot index of an [`InputSelection`].
pub fn with_slot_index(mut self, slot_index: impl Into<SlotIndex>) -> Self {
self.slot_index = slot_index.into();
self
}

/// Sets the mana allotments sum of an [`InputSelection`].
pub fn with_mana_allotments<'a>(mut self, mana_allotments: impl Iterator<Item = &'a ManaAllotment>) -> Self {
self.mana_allotments = mana_allotments.map(ManaAllotment::mana).sum();
Expand Down
85 changes: 71 additions & 14 deletions sdk/src/client/api/block_builder/input_selection/remainder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ use crate::{
client::api::RemainderData,
types::block::{
address::{Address, Ed25519Address},
output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, NativeTokensBuilder, Output},
output::{
unlock_condition::AddressUnlockCondition, AccountOutputBuilder, BasicOutputBuilder, NativeTokensBuilder,
NftOutputBuilder, Output,
},
Error as BlockError,
},
};

Expand Down Expand Up @@ -73,17 +77,18 @@ impl InputSelection {
))));

// TODO https://github.com/iotaledger/iota-sdk/issues/1631
// if let Some(native_tokens) = native_tokens_diff {
// remainder_builder = remainder_builder.with_native_tokens(native_tokens);
// }
// TODO Only putting one in remainder atm so we can at least create foundries
if let Some(native_tokens) = native_tokens_diff {
remainder_builder = remainder_builder.with_native_token(*native_tokens.first().unwrap());
}

Ok((remainder_builder.finish_output()?.amount(), native_tokens_remainder))
}

pub(crate) fn remainder_and_storage_deposit_return_outputs(
&self,
&mut self,
) -> Result<(Option<RemainderData>, Vec<Output>), Error> {
let (inputs_sum, outputs_sum, inputs_sdr, outputs_sdr) =
let (input_amount, output_amount, inputs_sdr, outputs_sdr) =
amount_sums(&self.selected_inputs, &self.outputs, self.slot_index);
let mut storage_deposit_returns = Vec::new();

Expand Down Expand Up @@ -118,30 +123,82 @@ impl InputSelection {

let native_tokens_diff = get_native_tokens_diff(&input_native_tokens, &output_native_tokens)?;

if inputs_sum == outputs_sum && native_tokens_diff.is_none() {
let mut input_mana = 0;

for input in &self.selected_inputs {
input_mana += input.output.available_mana(
&self.protocol_parameters,
input.output_id().transaction_id().slot_index(),
self.slot_index,
)?;
// TODO rewards https://github.com/iotaledger/iota-sdk/issues/1310
}

let output_mana = self.outputs.iter().map(|o| o.mana()).sum::<u64>() + self.mana_allotments;

if input_amount == output_amount && input_mana == output_mana && native_tokens_diff.is_none() {
log::debug!("No remainder required");
return Ok((None, storage_deposit_returns));
}

let amount_diff = input_amount
.checked_sub(output_amount)
.ok_or(BlockError::ConsumedAmountOverflow)?;
let mana_diff = input_mana
.checked_sub(output_mana)
.ok_or(BlockError::ConsumedManaOverflow)?;

// If there is only a mana remainder, try to fit it in an automatically transitioned output.
if input_amount == output_amount && input_mana != output_mana && native_tokens_diff.is_none() {
let filter = |output: &Output| {
output
.chain_id()
.as_ref()
.map(|chain_id| self.automatically_transitioned.contains(chain_id))
.unwrap_or(false)
// Foundries can't hold mana so they are not considered here.
&& !output.is_foundry()
};
let index = self
.outputs
.iter()
.position(|output| filter(output) && output.is_account())
.or_else(|| self.outputs.iter().position(filter));

if let Some(index) = index {
self.outputs[index] = match &self.outputs[index] {
Output::Account(output) => AccountOutputBuilder::from(&*output)
.with_mana(output.mana() + mana_diff)
.finish_output()?,
Output::Nft(output) => NftOutputBuilder::from(&*output)
.with_mana(output.mana() + mana_diff)
.finish_output()?,
_ => panic!("only account, nft can be automatically created and can hold mana"),
};

return Ok((None, storage_deposit_returns));
}
}

let Some((remainder_address, chain)) = self.get_remainder_address()? else {
return Err(Error::MissingInputWithEd25519Address);
};

let diff = inputs_sum - outputs_sum;
let mut remainder_builder = BasicOutputBuilder::new_with_amount(diff);
let mut remainder_builder = BasicOutputBuilder::new_with_amount(amount_diff).with_mana(mana_diff);

remainder_builder =
remainder_builder.add_unlock_condition(AddressUnlockCondition::new(remainder_address.clone()));

// TODO https://github.com/iotaledger/iota-sdk/issues/1631
// if let Some(native_tokens) = native_tokens_diff {
// log::debug!("Adding {native_tokens:?} to remainder output for {remainder_address:?}");
// remainder_builder = remainder_builder.with_native_tokens(native_tokens);
// }
// TODO Only putting one in remainder atm so we can at least create foundries
if let Some(native_tokens) = native_tokens_diff {
log::debug!("Adding {native_tokens:?} to remainder output for {remainder_address:?}");
remainder_builder = remainder_builder.with_native_token(*native_tokens.first().unwrap());
}

let remainder = remainder_builder.finish_output()?;

log::debug!("Created remainder output of {diff} for {remainder_address:?}");
log::debug!("Created remainder output of amount {amount_diff} and mana {mana_diff} for {remainder_address:?}");

remainder.verify_storage_deposit(self.protocol_parameters.storage_score_parameters())?;

Expand Down
10 changes: 5 additions & 5 deletions sdk/src/client/api/high_level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,23 @@ impl Client {

// Returns the slot index corresponding to the current timestamp.
pub async fn get_slot_index(&self) -> Result<SlotIndex> {
let current_time = unix_timestamp_now().as_nanos() as u64;
let unix_timestamp = unix_timestamp_now();
let current_time_nanos = unix_timestamp.as_nanos() as u64;

let network_info = self.get_network_info().await?;

if let Some(tangle_time) = network_info.tangle_time {
// Check the local time is in the range of +-5 minutes of the node to prevent locking funds by accident
if !(tangle_time - FIVE_MINUTES_IN_NANOSECONDS..tangle_time + FIVE_MINUTES_IN_NANOSECONDS)
.contains(&current_time)
.contains(&current_time_nanos)
{
return Err(Error::TimeNotSynced {
current_time,
current_time: current_time_nanos,
tangle_time,
});
}
}

// TODO double check with TIP if this should be seconds or nanoseconds
Ok(network_info.protocol_parameters.slot_index(current_time))
Ok(network_info.protocol_parameters.slot_index(unix_timestamp.as_secs()))
}
}
27 changes: 27 additions & 0 deletions sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,33 @@ impl Output {
}
}

/// Returns all the mana held by the output, which is potential + stored, all decayed.
pub fn available_mana(
&self,
protocol_parameters: &ProtocolParameters,
creation_index: SlotIndex,
target_index: SlotIndex,
) -> Result<u64, Error> {
let (amount, mana) = match self {
Self::Basic(output) => (output.amount(), output.mana()),
Self::Account(output) => (output.amount(), output.mana()),
Self::Anchor(output) => (output.amount(), output.mana()),
Self::Foundry(output) => (output.amount(), 0),
Self::Nft(output) => (output.amount(), output.mana()),
Self::Delegation(output) => (output.amount(), 0),
};

let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters());
let generation_amount = amount.saturating_sub(min_deposit);
let potential_mana =
protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?;
let stored_mana = protocol_parameters.mana_with_decay(mana, creation_index, target_index)?;

Ok(potential_mana
.checked_add(stored_mana)
.ok_or(Error::ConsumedManaOverflow)?)
}

/// Returns the unlock conditions of an [`Output`], if any.
pub fn unlock_conditions(&self) -> Option<&UnlockConditions> {
match self {
Expand Down
6 changes: 4 additions & 2 deletions sdk/src/types/block/payload/signed_transaction/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ impl TransactionBuilder {
}

/// Sets the context inputs of a [`TransactionBuilder`].
pub fn with_context_inputs(mut self, context_inputs: impl Into<Vec<ContextInput>>) -> Self {
self.context_inputs = context_inputs.into();
pub fn with_context_inputs(mut self, context_inputs: impl IntoIterator<Item = ContextInput>) -> Self {
self.context_inputs = context_inputs.into_iter().collect();
self
}

Expand Down Expand Up @@ -164,6 +164,8 @@ impl TransactionBuilder {
.try_into()
.map_err(Error::InvalidContextInputCount)?;

verify_context_inputs(&context_inputs)?;

let inputs: BoxedSlicePrefix<Input, InputCount> = self
.inputs
.into_boxed_slice()
Expand Down
13 changes: 11 additions & 2 deletions sdk/src/types/block/rand/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,22 @@ use crate::types::block::{
rand_state_controller_address_unlock_condition_different_from,
},
},
transaction::rand_transaction_id,
transaction::rand_transaction_id_with_slot_index,
},
slot::SlotIndex,
};

/// Generates a random output id with a given slot index.
pub fn rand_output_id_with_slot_index(slot_index: impl Into<SlotIndex>) -> OutputId {
OutputId::new(
rand_transaction_id_with_slot_index(slot_index),
rand_number_range(OUTPUT_INDEX_RANGE),
)
}

/// Generates a random [`OutputId`].
pub fn rand_output_id() -> OutputId {
OutputId::new(rand_transaction_id(), rand_number_range(OUTPUT_INDEX_RANGE))
rand_output_id_with_slot_index(rand_number::<u32>())
}

/// Generates a random [`BasicOutput`].
Expand Down
52 changes: 12 additions & 40 deletions sdk/src/types/block/semantic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ pub use self::{
};
use crate::types::block::{
address::Address,
output::{
AccountId, AnchorOutput, ChainId, FoundryId, MinimumOutputAmount, NativeTokens, Output, OutputId, TokenId,
},
output::{AccountId, AnchorOutput, ChainId, FoundryId, NativeTokens, Output, OutputId, TokenId},
payload::signed_transaction::{Transaction, TransactionCapabilityFlag, TransactionSigningHash},
protocol::ProtocolParameters,
unlock::Unlock,
Expand Down Expand Up @@ -104,18 +102,13 @@ impl<'a> SemanticValidationContext<'a> {
pub fn validate(mut self) -> Result<Option<TransactionFailureReason>, Error> {
// Validation of inputs.
for (index, (output_id, consumed_output)) in self.inputs.iter().enumerate() {
let (amount, mana, consumed_native_token, unlock_conditions) = match consumed_output {
Output::Basic(output) => (
output.amount(),
output.mana(),
output.native_token(),
output.unlock_conditions(),
),
Output::Account(output) => (output.amount(), output.mana(), None, output.unlock_conditions()),
let (amount, consumed_native_token, unlock_conditions) = match consumed_output {
Output::Basic(output) => (output.amount(), output.native_token(), output.unlock_conditions()),
Output::Account(output) => (output.amount(), None, output.unlock_conditions()),
Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)),
Output::Foundry(output) => (output.amount(), 0, output.native_token(), output.unlock_conditions()),
Output::Nft(output) => (output.amount(), output.mana(), None, output.unlock_conditions()),
Output::Delegation(output) => (output.amount(), 0, None, output.unlock_conditions()),
Output::Foundry(output) => (output.amount(), output.native_token(), output.unlock_conditions()),
Output::Nft(output) => (output.amount(), None, output.unlock_conditions()),
Output::Delegation(output) => (output.amount(), None, output.unlock_conditions()),
};

let commitment_slot_index = self
Expand Down Expand Up @@ -162,34 +155,13 @@ impl<'a> SemanticValidationContext<'a> {
.checked_add(amount)
.ok_or(Error::ConsumedAmountOverflow)?;

let potential_mana = {
// Deposit amount doesn't generate mana
let min_deposit = consumed_output.minimum_amount(self.protocol_parameters.storage_score_parameters());
let generation_amount = consumed_output.amount().saturating_sub(min_deposit);

self.protocol_parameters.generate_mana_with_decay(
generation_amount,
output_id.transaction_id().slot_index(),
self.transaction.creation_slot(),
)
}?;

// Add potential mana
self.input_mana = self
.input_mana
.checked_add(potential_mana)
.ok_or(Error::ConsumedManaOverflow)?;

let stored_mana = self.protocol_parameters.mana_with_decay(
mana,
output_id.transaction_id().slot_index(),
self.transaction.creation_slot(),
)?;

// Add stored mana
self.input_mana = self
.input_mana
.checked_add(stored_mana)
.checked_add(consumed_output.available_mana(
&self.protocol_parameters,
output_id.transaction_id().slot_index(),
self.transaction.creation_slot(),
)?)
.ok_or(Error::ConsumedManaOverflow)?;

// TODO: Add reward mana https://github.com/iotaledger/iota-sdk/issues/1310
Expand Down
Loading

0 comments on commit 3952634

Please sign in to comment.