Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISA: mana remainder #1831

Merged
merged 61 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c51f7c8
validate some mana semantics
Nov 27, 2023
a48efcc
Merge branch '2.0' into feat/mana-semantics
Nov 27, 2023
73df348
Merge branch '2.0' into feat/mana-semantics
Nov 30, 2023
476095e
Merge branch '2.0' into feat/mana-semantics
thibault-martinez Dec 18, 2023
a0ad146
Cleanup
thibault-martinez Dec 18, 2023
5cc119f
delta->diff
thibault-martinez Dec 18, 2023
d1f8f35
Merge branch '2.0' into feat/mana-semantics
Jan 8, 2024
93ff09b
Merge branch '2.0' into feat/mana-semantics
thibault-martinez Jan 10, 2024
c226598
Merge branch '2.0' into feat/mana-semantics
thibault-martinez Jan 10, 2024
9540ac5
Nit
thibault-martinez Jan 10, 2024
4ff8ed7
Allow passing an index for test inputs IDs
thibault-martinez Jan 10, 2024
2239db3
Fix tests
thibault-martinez Jan 11, 2024
29cecc6
Merge branch '2.0' into feat/mana-semantics
thibault-martinez Jan 11, 2024
71b34ef
rand nit
thibault-martinez Jan 11, 2024
14dd3cc
Simplify tests
thibault-martinez Jan 11, 2024
3923f24
Add link to issue
thibault-martinez Jan 11, 2024
fc9ef2e
Update sdk/src/types/block/semantic/mod.rs
thibault-martinez Jan 11, 2024
524f2f5
Merge branch '2.0' into feat/mana-semantics
Alex6323 Jan 11, 2024
e220ea5
Format
thibault-martinez Jan 11, 2024
2adbb3b
ISA: mana remainder
thibault-martinez Jan 11, 2024
a6afcac
Merge branch '2.0' into isa-mana-remainder
thibault-martinez Jan 12, 2024
31f0d70
Merge branch '2.0' into isa-mana-remainder
thibault-martinez Jan 12, 2024
696ff5d
Some fixes
thibault-martinez Jan 12, 2024
76dade3
More fixes
thibault-martinez Jan 12, 2024
877996f
Put mana remainder to automatically transitioned chain if possible
thibault-martinez Jan 12, 2024
02f2970
Merge branch '2.0' into isa-mana-remainder
thibault-martinez Jan 12, 2024
7d65eac
Fix expiration tests
thibault-martinez Jan 12, 2024
f9d455a
No need for +1
thibault-martinez Jan 12, 2024
b38e29d
Fix timelock tests
thibault-martinez Jan 12, 2024
172821a
Fix NFT tests
thibault-martinez Jan 12, 2024
5ae3bdd
Fix foundry tests
thibault-martinez Jan 12, 2024
9147c98
Fix storage deposit tests
thibault-martinez Jan 12, 2024
675d775
Fix last tests
thibault-martinez Jan 12, 2024
d805779
Nit
thibault-martinez Jan 12, 2024
d86634c
More nits
thibault-martinez Jan 12, 2024
7426c21
Merge branch '2.0' into isa-mana-remainder
thibault-martinez Jan 12, 2024
078fa62
Remove commented code
thibault-martinez Jan 12, 2024
6374079
Set BIC input if account is transitioned
thibault-martinez Jan 14, 2024
645192b
Merge branch '2.0' into isa-mana-remainder
thibault-martinez Jan 15, 2024
4670165
Allow one NT remainder
thibault-martinez Jan 15, 2024
fb68a8a
Remove prints
thibault-martinez Jan 15, 2024
a88b269
Cleanup context inputs
thibault-martinez Jan 15, 2024
1ce140b
Add comment
thibault-martinez Jan 15, 2024
6d627bd
Remove clones
thibault-martinez Jan 15, 2024
1a9f820
Nits
thibault-martinez Jan 15, 2024
e0fc11f
Remove panic
thibault-martinez Jan 15, 2024
bdc5975
Use all_mana
thibault-martinez Jan 15, 2024
3eaffb1
Add link to TODO
thibault-martinez Jan 15, 2024
1f57689
Cleanup
thibault-martinez Jan 15, 2024
8f60248
Remove TODO
thibault-martinez Jan 15, 2024
bcb04e2
add log
thibault-martinez Jan 15, 2024
c268674
Merge branch '2.0' into isa-mana-remainder
thibault-martinez Jan 15, 2024
4697561
Ok(Selected
thibault-martinez Jan 15, 2024
877183e
mana cleanup
thibault-martinez Jan 15, 2024
db6a70d
Merge branch '2.0' into isa-mana-remainder
thibault-martinez Jan 16, 2024
3e28db3
Merge branch '2.0' into isa-mana-remainder
thibault-martinez Jan 16, 2024
5b5d199
checked_sub
thibault-martinez Jan 16, 2024
2159225
available_mana
thibault-martinez Jan 16, 2024
25370d7
Prefer accounts
thibault-martinez Jan 16, 2024
8479a5c
Remove cloned
thibault-martinez Jan 16, 2024
0cee21d
move up
thibault-martinez Jan 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
kwek20 marked this conversation as resolved.
Show resolved Hide resolved
};
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
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
.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
23 changes: 14 additions & 9 deletions sdk/src/wallet/operations/transaction/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,13 @@ where
where
crate::wallet::Error: From<S::Error>,
{
let implicit_account_data = self.data().await.unspent_outputs.get(output_id).cloned();

let implicit_account = if let Some(implicit_account_data) = &implicit_account_data {
if implicit_account_data.output.is_implicit_account() {
implicit_account_data.output.as_basic()
} else {
return Err(Error::ImplicitAccountNotFound);
}
let wallet_data = self.data().await;
let implicit_account_data = wallet_data
.unspent_outputs
.get(output_id)
.ok_or(Error::ImplicitAccountNotFound)?;
let implicit_account = if implicit_account_data.output.is_implicit_account() {
implicit_account_data.output.as_basic()
} else {
return Err(Error::ImplicitAccountNotFound);
};
Expand Down Expand Up @@ -86,7 +85,11 @@ where

let account_id = AccountId::from(output_id);
let account = AccountOutput::build_with_amount(implicit_account.amount(), account_id)
.with_mana(implicit_account.mana())
.with_mana(implicit_account_data.output.available_mana(
&self.client().get_protocol_parameters().await?,
implicit_account_data.output_id.transaction_id().slot_index(),
self.client().get_slot_index().await?,
)?)
.with_unlock_conditions([AddressUnlockCondition::from(Address::from(
*implicit_account
.address()
Expand All @@ -99,6 +102,8 @@ where
)?])
.finish_output()?;

drop(wallet_data);

// TODO https://github.com/iotaledger/iota-sdk/issues/1740
let issuance = self.client().get_issuance().await?;

Expand Down
Loading