Skip to content

Commit

Permalink
Allow ignoring missing mana rewards (#2092)
Browse files Browse the repository at this point in the history
* allow ignoring missing mana rewards

* fixes

* clean

* fix semantic mana check

* fix addition of mana rewards and update test

* fmt

* nit

---------

Co-authored-by: Thibault Martinez <[email protected]>
  • Loading branch information
DaughterOfMars and thibault-martinez committed Mar 1, 2024
1 parent a743faf commit 37d52aa
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 14 deletions.
6 changes: 3 additions & 3 deletions bindings/core/src/method/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use iota_sdk::{
unlock::Unlock,
BlockDto,
},
utils::serde::{mana_rewards, string},
utils::serde::{option_mana_rewards, string},
};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -179,8 +179,8 @@ pub enum UtilsMethod {
transaction: TransactionDto,
inputs: Vec<InputSigningData>,
unlocks: Option<Vec<Unlock>>,
#[serde(default, with = "mana_rewards")]
mana_rewards: BTreeMap<OutputId, u64>,
#[serde(default, with = "option_mana_rewards")]
mana_rewards: Option<BTreeMap<OutputId, u64>>,
protocol_parameters: ProtocolParameters,
},
/// Applies mana decay to the given mana.
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/client/api/block_builder/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const REFERENCE_ACCOUNT_NFT_UNLOCK_LENGTH: usize = 1 + 2;
pub fn verify_semantic(
input_signing_data: &[InputSigningData],
transaction_payload: &SignedTransactionPayload,
mana_rewards: BTreeMap<OutputId, u64>,
mana_rewards: impl Into<Option<BTreeMap<OutputId, u64>>>,
protocol_parameters: ProtocolParameters,
) -> Result<(), TransactionFailureReason> {
let inputs = input_signing_data
Expand All @@ -43,7 +43,7 @@ pub fn verify_semantic(
transaction_payload.transaction(),
&inputs,
Some(transaction_payload.unlocks()),
mana_rewards,
mana_rewards.into(),
protocol_parameters,
);

Expand Down
11 changes: 6 additions & 5 deletions sdk/src/types/block/semantic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub struct SemanticValidationContext<'a> {
pub(crate) unlocks: Option<&'a [Unlock]>,
pub(crate) input_amount: u64,
pub(crate) input_mana: u64,
pub(crate) mana_rewards: BTreeMap<OutputId, u64>,
pub(crate) mana_rewards: Option<BTreeMap<OutputId, u64>>,
pub(crate) commitment_context_input: Option<SlotCommitmentId>,
pub(crate) reward_context_inputs: HashMap<OutputId, RewardContextInput>,
pub(crate) input_native_tokens: BTreeMap<TokenId, U256>,
Expand All @@ -52,7 +52,7 @@ impl<'a> SemanticValidationContext<'a> {
transaction: &'a Transaction,
inputs: &'a [(&'a OutputId, &'a Output)],
unlocks: Option<&'a [Unlock]>,
mana_rewards: BTreeMap<OutputId, u64>,
mana_rewards: Option<BTreeMap<OutputId, u64>>,
protocol_parameters: ProtocolParameters,
) -> Self {
let transaction_id = transaction.id();
Expand Down Expand Up @@ -239,8 +239,9 @@ impl<'a> SemanticValidationContext<'a> {
)
.ok_or(TransactionFailureReason::ManaOverflow)?;

if let Some(mana_rewards) = self.mana_rewards.get(*output_id) {
self.input_mana
if let Some(mana_rewards) = self.mana_rewards.as_ref().and_then(|r| r.get(*output_id)) {
self.input_mana = self
.input_mana
.checked_add(*mana_rewards)
.ok_or(TransactionFailureReason::ManaOverflow)?;
}
Expand Down Expand Up @@ -407,7 +408,7 @@ impl<'a> SemanticValidationContext<'a> {
if !self.transaction.has_capability(TransactionCapabilityFlag::BurnMana) {
return Err(TransactionFailureReason::CapabilitiesManaBurningNotAllowed);
}
} else {
} else if self.mana_rewards.is_some() || self.reward_context_inputs.is_empty() {
return Err(TransactionFailureReason::InputOutputManaMismatch);
}
}
Expand Down
22 changes: 18 additions & 4 deletions sdk/src/types/block/semantic/state_transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,10 @@ impl StateTransitionVerifier for AccountOutput {

if staking_input.end_epoch() >= future_bounded_epoch {
return Err(TransactionFailureReason::StakingFeatureRemovedBeforeUnbonding);
} else if !context.mana_rewards.contains_key(current_output_id)
} else if context
.mana_rewards
.as_ref()
.is_some_and(|r| !r.contains_key(current_output_id))
|| !context.reward_context_inputs.contains_key(current_output_id)
{
return Err(TransactionFailureReason::StakingRewardClaimingInvalid);
Expand Down Expand Up @@ -280,7 +283,10 @@ impl StateTransitionVerifier for AccountOutput {
&& (staking_input.start_epoch() != past_bounded_epoch
|| staking_input.end_epoch()
< past_bounded_epoch + context.protocol_parameters.staking_unbonding_period
|| !context.mana_rewards.contains_key(current_output_id)
|| context
.mana_rewards
.as_ref()
.is_some_and(|r| !r.contains_key(current_output_id))
|| !context.reward_context_inputs.contains_key(current_output_id))
{
return Err(TransactionFailureReason::StakingRewardClaimingInvalid);
Expand Down Expand Up @@ -321,7 +327,10 @@ impl StateTransitionVerifier for AccountOutput {

if staking.end_epoch() >= future_bounded_epoch {
return Err(TransactionFailureReason::StakingFeatureRemovedBeforeUnbonding);
} else if !context.mana_rewards.contains_key(output_id)
} else if context
.mana_rewards
.as_ref()
.is_some_and(|r| !r.contains_key(output_id))
|| !context.reward_context_inputs.contains_key(output_id)
{
return Err(TransactionFailureReason::StakingRewardClaimingInvalid);
Expand Down Expand Up @@ -571,7 +580,12 @@ impl StateTransitionVerifier for DelegationOutput {
_current_state: &Self,
context: &SemanticValidationContext<'_>,
) -> Result<(), TransactionFailureReason> {
if !context.mana_rewards.contains_key(output_id) || !context.reward_context_inputs.contains_key(output_id) {
if context
.mana_rewards
.as_ref()
.is_some_and(|r| !r.contains_key(output_id))
|| !context.reward_context_inputs.contains_key(output_id)
{
return Err(TransactionFailureReason::DelegationRewardInputMissing);
}

Expand Down
29 changes: 29 additions & 0 deletions sdk/src/utils/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,32 @@ pub mod mana_rewards {
.collect::<Result<BTreeMap<_, u64>, _>>()
}
}

#[cfg(feature = "client")]
pub mod option_mana_rewards {
use alloc::collections::BTreeMap;

use serde::{Deserialize, Deserializer};

use crate::types::block::output::OutputId;

pub fn serialize<S: serde::Serializer>(
mana_rewards: &Option<BTreeMap<OutputId, u64>>,
s: S,
) -> Result<S::Ok, S::Error> {
match mana_rewards {
Some(map) => super::mana_rewards::serialize(map, s),
None => s.serialize_none(),
}
}

pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<BTreeMap<OutputId, u64>>, D::Error> {
Option::<BTreeMap<OutputId, String>>::deserialize(d)?
.map(|map| {
map.into_iter()
.map(|(k, v)| Ok((k, v.parse().map_err(serde::de::Error::custom)?)))
.collect::<Result<BTreeMap<_, u64>, _>>()
})
.transpose()
}
}
27 changes: 27 additions & 0 deletions sdk/tests/client/input_selection/delegation_outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,33 @@ fn remainder_needed_for_mana() {
.select()
.unwrap();

let inputs = inputs
.iter()
.map(|input| (input.output_id(), &input.output))
.collect::<Vec<_>>();

// validating without rewards
iota_sdk::types::block::semantic::SemanticValidationContext::new(
&selected.transaction,
&inputs,
None,
None,
protocol_parameters.clone(),
)
.validate()
.unwrap();

// validating with rewards
iota_sdk::types::block::semantic::SemanticValidationContext::new(
&selected.transaction,
&inputs,
None,
Some(std::collections::BTreeMap::from([(delegation_output_id, mana_rewards)])),
protocol_parameters.clone(),
)
.validate()
.unwrap();

assert_eq!(selected.inputs_data.len(), 2);
assert_eq!(selected.transaction.outputs().len(), 2);
assert!(selected.transaction.outputs().contains(&outputs[0]));
Expand Down

0 comments on commit 37d52aa

Please sign in to comment.