diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000..0358cdb508 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,2 @@ +allow-unwrap-in-tests = true +allow-expect-in-tests = true diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/events.rs b/common/cosmwasm-smart-contracts/contracts-common/src/events.rs index 06cea03f22..39d5c0007a 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/events.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/events.rs @@ -11,6 +11,8 @@ use cosmwasm_std::Event; /// * `event`: event to search through. /// * `key`: key associated with the particular attribute pub fn must_find_attribute(event: &Event, key: &str) -> String { + // due to how the function is supposed to work, the unwrap is fine in this instance + #[allow(clippy::unwrap_used)] may_find_attribute(event, key).unwrap() } diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs b/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs index 53bf334611..f4d3906558 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + pub mod events; pub mod types; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/constants.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/constants.rs index 2c46abf8ce..2269c20cd7 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/constants.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/constants.rs @@ -1,7 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::Decimal; +use cosmwasm_std::{Decimal, Uint128}; + +pub const TOKEN_SUPPLY: Uint128 = Uint128::new(1_000_000_000_000_000); // I'm still not 100% sure how to feel about existence of this file // This is equivalent of representing our display coin with 6 decimal places. diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs index ce144ebad9..3c696a83ca 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs @@ -4,8 +4,10 @@ // due to code generated by JsonSchema #![allow(clippy::field_reassign_with_default)] +use crate::constants::TOKEN_SUPPLY; +use crate::helpers::IntoBaseDecimal; use crate::{Addr, MixId}; -use cosmwasm_std::{Coin, Decimal}; +use cosmwasm_std::{Coin, Decimal, StdResult}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -60,6 +62,11 @@ impl Delegation { height: u64, proxy: Option, ) -> Self { + assert!( + amount.amount <= TOKEN_SUPPLY, + "delegation cannot be larger than the token supply" + ); + Delegation { owner, mix_id, @@ -87,10 +94,8 @@ impl Delegation { (mix_id, owner_proxy_subkey) } - pub fn dec_amount(&self) -> Decimal { - // the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal - // with 0 decimal places - Decimal::from_atomics(self.amount.amount, 0).unwrap() + pub fn dec_amount(&self) -> StdResult { + self.amount.amount.into_base_decimal() } pub fn proxy_storage_key(&self) -> OwnerProxySubKey { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs index 00f4c4b12c..5dc06ed9c3 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::Decimal; +use cosmwasm_std::{Decimal, StdError, StdResult, Uint128}; pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option) { let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128)); @@ -11,3 +11,23 @@ pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option) { assert!(b - a < epsilon, "{} != {}", a, b) } } + +pub fn into_base_decimal(val: impl Into) -> StdResult { + val.into_base_decimal() +} + +pub trait IntoBaseDecimal { + fn into_base_decimal(self) -> StdResult; +} + +impl IntoBaseDecimal for T +where + T: Into, +{ + fn into_base_decimal(self) -> StdResult { + let atomics = self.into(); + Decimal::from_atomics(atomics, 0).map_err(|_| StdError::GenericErr { + msg: format!("Decimal range exceeded for {atomics} with 0 decimal places."), + }) + } +} diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index 560f1582b7..60e3f08495 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -142,14 +142,17 @@ impl JsonSchema for Interval { impl Interval { /// Initialize epoch in the contract with default values. pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self { + // if this fails it means the value provided from the chain itself (via cosmwasm) is invalid, + // so we really have to panic here as anything beyond that point would be invalid anyway + #[allow(clippy::expect_used)] + let current_epoch_start = + OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64) + .expect("The timestamp provided via env.block.time is invalid"); + Interval { id: 0, epochs_in_interval, - // I really don't see a way for this to fail, unless the blockchain is lying to us - current_epoch_start: OffsetDateTime::from_unix_timestamp( - env.block.time.seconds() as i64 - ) - .expect("Invalid timestamp from env.block.time"), + current_epoch_start, current_epoch_id: 0, epoch_length, total_elapsed_epochs: 0, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index 200b4c5a00..351828585a 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + mod constants; pub mod delegation; pub mod error; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 5eb98bcbb3..9bccf2d1ff 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -4,13 +4,14 @@ // due to code generated by JsonSchema #![allow(clippy::field_reassign_with_default)] -use crate::constants::UNIT_DELEGATION_BASE; +use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE}; use crate::error::MixnetContractError; +use crate::helpers::IntoBaseDecimal; use crate::reward_params::{NodeRewardParams, RewardingParams}; use crate::rewarding::helpers::truncate_reward; use crate::rewarding::RewardDistribution; use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey}; -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -64,7 +65,7 @@ impl MixNodeDetails { self.rewarding_details.pending_operator_reward(pledge) } - pub fn pending_detailed_operator_reward(&self) -> Decimal { + pub fn pending_detailed_operator_reward(&self) -> StdResult { let pledge = self.original_pledge(); self.rewarding_details .pending_detailed_operator_reward(pledge) @@ -107,16 +108,21 @@ impl MixNodeRewarding { cost_params: MixNodeCostParams, initial_pledge: &Coin, current_epoch: EpochId, - ) -> Self { - MixNodeRewarding { + ) -> Result { + assert!( + initial_pledge.amount <= TOKEN_SUPPLY, + "pledge cannot be larger than the token supply" + ); + + Ok(MixNodeRewarding { cost_params, - operator: Decimal::from_atomics(initial_pledge.amount, 0).unwrap(), + operator: initial_pledge.amount.into_base_decimal()?, delegates: Decimal::zero(), total_unit_reward: Decimal::zero(), unit_delegation: UNIT_DELEGATION_BASE, last_rewarded_epoch: current_epoch, unique_delegations: 0, - } + }) } /// Determines whether this node is still bonded. This is performed via a simple check, @@ -135,27 +141,30 @@ impl MixNodeRewarding { } } - pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> Decimal { - let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap(); + pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> StdResult { + let initial_dec = original_pledge.amount.into_base_decimal()?; if initial_dec > self.operator { panic!( "seems slashing has occurred while it has not been implemented nor accounted for!" ) } - self.operator - initial_dec + Ok(self.operator - initial_dec) } pub fn operator_pledge_with_reward(&self, denom: impl Into) -> Coin { truncate_reward(self.operator, denom) } - pub fn pending_delegator_reward(&self, delegation: &Delegation) -> Coin { - let delegator_reward = self.determine_delegation_reward(delegation); - truncate_reward(delegator_reward, &delegation.amount.denom) + pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult { + let delegator_reward = self.determine_delegation_reward(delegation)?; + Ok(truncate_reward(delegator_reward, &delegation.amount.denom)) } - pub fn withdraw_operator_reward(&mut self, original_pledge: &Coin) -> Coin { - let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap(); + pub fn withdraw_operator_reward( + &mut self, + original_pledge: &Coin, + ) -> Result { + let initial_dec = original_pledge.amount.into_base_decimal()?; if initial_dec > self.operator { panic!( "seems slashing has occurred while it has not been implemented nor accounted for!" @@ -164,14 +173,14 @@ impl MixNodeRewarding { let diff = self.operator - initial_dec; self.operator = initial_dec; - truncate_reward(diff, &original_pledge.denom) + Ok(truncate_reward(diff, &original_pledge.denom)) } pub fn withdraw_delegator_reward( &mut self, delegation: &mut Delegation, ) -> Result { - let reward = self.determine_delegation_reward(delegation); + let reward = self.determine_delegation_reward(delegation)?; self.decrease_delegates_decimal(reward)?; delegation.cumulative_reward_ratio = self.full_reward_ratio(); @@ -301,23 +310,27 @@ impl MixNodeRewarding { self.distribute_rewards(reward_distribution, absolute_epoch_id) } - pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal { + pub fn determine_delegation_reward(&self, delegation: &Delegation) -> StdResult { let starting_ratio = delegation.cumulative_reward_ratio; let ending_ratio = self.full_reward_ratio(); let adjust = starting_ratio + self.unit_delegation; - (ending_ratio - starting_ratio) * delegation.dec_amount() / adjust + Ok((ending_ratio - starting_ratio) * delegation.dec_amount()? / adjust) } // this updates `unique_delegations` field - pub fn add_base_delegation(&mut self, amount: Uint128) { - self.increase_delegates_uint128(amount); + pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> { + self.increase_delegates_uint128(amount)?; self.unique_delegations += 1; + Ok(()) } - pub fn increase_delegates_uint128(&mut self, amount: Uint128) { - // the unwrap here is fine as the value is guaranteed to fit under provided constraints - self.delegates += Decimal::from_atomics(amount, 0).unwrap() + pub fn increase_delegates_uint128( + &mut self, + amount: Uint128, + ) -> Result<(), MixnetContractError> { + self.delegates += amount.into_base_decimal()?; + Ok(()) } // this updates `unique_delegations` field @@ -335,7 +348,7 @@ impl MixNodeRewarding { &mut self, amount: Uint128, ) -> Result<(), MixnetContractError> { - let amount_dec = Decimal::from_atomics(amount, 0).unwrap(); + let amount_dec = amount.into_base_decimal()?; self.decrease_delegates_decimal(amount_dec) } @@ -368,8 +381,8 @@ impl MixNodeRewarding { } pub fn undelegate(&mut self, delegation: &Delegation) -> Result { - let reward = self.determine_delegation_reward(delegation); - let full_amount = reward + delegation.dec_amount(); + let reward = self.determine_delegation_reward(delegation)?; + let full_amount = reward + delegation.dec_amount()?; self.remove_delegation_decimal(full_amount)?; Ok(truncate_reward(full_amount, &delegation.amount.denom)) } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index ee172a4c20..897f750569 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::delegation::OwnerProxySubKey; +use crate::error::MixnetContractError; +use crate::helpers::IntoBaseDecimal; use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use crate::reward_params::{ IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams, @@ -40,14 +42,17 @@ pub struct InitialRewardingParams { } impl InitialRewardingParams { - pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams { + pub fn into_rewarding_params( + self, + epochs_in_interval: u32, + ) -> Result { let epoch_reward_budget = self.initial_reward_pool - / Decimal::from_atomics(epochs_in_interval, 0).unwrap() + / epochs_in_interval.into_base_decimal()? * self.interval_pool_emission; let stake_saturation_point = - self.initial_staking_supply / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap(); + self.initial_staking_supply / self.rewarded_set_size.into_base_decimal()?; - RewardingParams { + Ok(RewardingParams { interval: IntervalRewardParams { reward_pool: self.initial_reward_pool, staking_supply: self.initial_staking_supply, @@ -59,7 +64,7 @@ impl InitialRewardingParams { }, rewarded_set_size: self.rewarded_set_size, active_set_size: self.active_set_size, - } + }) } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs index fc543a3c02..5fc9d558ae 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::helpers::IntoBaseDecimal; use crate::{error::MixnetContractError, Percent}; use cosmwasm_std::Decimal; use schemars::JsonSchema; @@ -105,24 +106,33 @@ impl RewardingParams { pub fn dec_rewarded_set_size(&self) -> Decimal { // the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal // with 0 decimal places - Decimal::from_atomics(self.rewarded_set_size, 0).unwrap() + #[allow(clippy::unwrap_used)] + self.rewarded_set_size.into_base_decimal().unwrap() } pub fn dec_active_set_size(&self) -> Decimal { // the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal // with 0 decimal places - Decimal::from_atomics(self.active_set_size, 0).unwrap() + #[allow(clippy::unwrap_used)] + self.active_set_size.into_base_decimal().unwrap() } fn dec_standby_set_size(&self) -> Decimal { // the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal // with 0 decimal places - Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap() + #[allow(clippy::unwrap_used)] + (self.rewarded_set_size - self.active_set_size) + .into_base_decimal() + .unwrap() } pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) { - self.interval.epoch_reward_budget = self.interval.reward_pool - / Decimal::from_atomics(new_epochs_in_interval, 0).unwrap() + // the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal + // with 0 decimal places + #[allow(clippy::unwrap_used)] + let new_epochs_in_interval = new_epochs_in_interval.into_base_decimal().unwrap(); + + self.interval.epoch_reward_budget = self.interval.reward_pool / new_epochs_in_interval * self.interval.interval_pool_emission; } @@ -191,13 +201,13 @@ impl RewardingParams { if recompute_epoch_budget { self.interval.epoch_reward_budget = self.interval.reward_pool - / Decimal::from_atomics(epochs_in_interval, 0).unwrap() + / epochs_in_interval.into_base_decimal()? * self.interval.interval_pool_emission; } if recompute_saturation_point { - self.interval.stake_saturation_point = self.interval.staking_supply - / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap(); + self.interval.stake_saturation_point = + self.interval.staking_supply / self.rewarded_set_size.into_base_decimal()? } Ok(()) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/mod.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/mod.rs index e50df76434..ee091e6b13 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/mod.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/mod.rs @@ -29,7 +29,7 @@ pub struct RewardEstimate { pub operating_cost: Decimal, } -#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] pub struct RewardDistribution { pub operator: Decimal, pub delegates: Decimal, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/mod.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/mod.rs index bd4fc8d8d3..5808bbbe64 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/mod.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::MixnetContractError; +use crate::helpers::IntoBaseDecimal; use crate::reward_params::NodeRewardParams; use crate::rewarding::simulator::simulated_node::SimulatedNode; use crate::rewarding::RewardDistribution; @@ -33,7 +34,7 @@ impl Simulator { } } - fn advance_epoch(&mut self) { + fn advance_epoch(&mut self) -> Result<(), MixnetContractError> { let updated = self.interval.advance_epoch(); // we rolled over an interval @@ -42,10 +43,13 @@ impl Simulator { let reward_pool = old.reward_pool - self.pending_reward_pool_emission; let staking_supply = old.staking_supply + self.pending_reward_pool_emission; let epoch_reward_budget = reward_pool - / Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap() + / self.interval.epochs_in_interval().into_base_decimal()? * old.interval_pool_emission.value(); let stake_saturation_point = staking_supply - / Decimal::from_atomics(self.system_rewarding_params.rewarded_set_size, 0).unwrap(); + / self + .system_rewarding_params + .rewarded_set_size + .into_base_decimal()?; let updated_params = RewardingParams { interval: IntervalRewardParams { @@ -65,9 +69,15 @@ impl Simulator { self.pending_reward_pool_emission = Decimal::zero(); } self.interval = updated; + + Ok(()) } - pub fn bond(&mut self, pledge: Coin, cost_params: MixNodeCostParams) -> MixId { + pub fn bond( + &mut self, + pledge: Coin, + cost_params: MixNodeCostParams, + ) -> Result { let mix_id = self.next_mix_id; self.nodes.insert( @@ -77,16 +87,24 @@ impl Simulator { cost_params, &pledge, self.interval.current_epoch_absolute_id(), - ), + )?, ); self.next_mix_id += 1; - mix_id + Ok(mix_id) } - pub fn delegate>(&mut self, delegator: S, delegation: Coin, mix_id: MixId) { - let node = self.nodes.get_mut(&mix_id).expect("node doesn't exist"); + pub fn delegate>( + &mut self, + delegator: S, + delegation: Coin, + mix_id: MixId, + ) -> Result<(), MixnetContractError> { + let node = self + .nodes + .get_mut(&mix_id) + .ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?; node.delegate(delegator, delegation) } @@ -97,23 +115,35 @@ impl Simulator { delegator: S, mix_id: MixId, ) -> Result<(Coin, Coin), MixnetContractError> { - let node = self.nodes.get_mut(&mix_id).expect("node not found"); + let node = self + .nodes + .get_mut(&mix_id) + .ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?; node.undelegate(delegator) } - pub fn simulate_epoch_single_node(&mut self, params: NodeRewardParams) -> RewardDistribution { + pub fn simulate_epoch_single_node( + &mut self, + params: NodeRewardParams, + ) -> Result { assert_eq!(self.nodes.len(), 1); - let id = *self.nodes.keys().next().unwrap(); - let mut params_map = BTreeMap::new(); - params_map.insert(id, params); - self.simulate_epoch(¶ms_map).remove(&id).unwrap() + if let Some(&id) = self.nodes.keys().next() { + let mut params_map = BTreeMap::new(); + params_map.insert(id, params); + Ok(self + .simulate_epoch(¶ms_map)? + .remove(&id) + .unwrap_or_default()) + } else { + Ok(RewardDistribution::default()) + } } pub fn simulate_epoch( &mut self, node_params: &BTreeMap, - ) -> BTreeMap { + ) -> Result, MixnetContractError> { let mut params_keys = node_params.keys().copied().collect::>(); params_keys.sort_unstable(); let mut node_keys = self.nodes.keys().copied().collect::>(); @@ -141,34 +171,41 @@ impl Simulator { dist.insert(*mix_id, reward_distribution); } - self.advance_epoch(); - dist + self.advance_epoch()?; + Ok(dist) } - pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal { - self.nodes[&delegation.mix_id] + pub fn determine_delegation_reward( + &self, + delegation: &Delegation, + ) -> Result { + Ok(self.nodes[&delegation.mix_id] .rewarding_details - .determine_delegation_reward(delegation) + .determine_delegation_reward(delegation)?) } - pub fn determine_total_delegation_reward(&self) -> Decimal { + pub fn determine_total_delegation_reward(&self) -> Result { let mut total = Decimal::zero(); for node in self.nodes.values() { for delegation in node.delegations.values() { total += node .rewarding_details - .determine_delegation_reward(delegation) + .determine_delegation_reward(delegation)? } } - total + Ok(total) } // assume node state doesn't change in the interval (kinda unrealistic) - pub fn simulate_full_interval(&mut self, node_params: &BTreeMap) { + pub fn simulate_full_interval( + &mut self, + node_params: &BTreeMap, + ) -> Result<(), MixnetContractError> { for _ in 0..self.interval.epochs_in_interval() { - self.simulate_epoch(node_params); + self.simulate_epoch(node_params)?; } + Ok(()) } } @@ -231,20 +268,27 @@ mod tests { profit_margin_percent: profit_margin, interval_operating_cost, }; - simulator.bond(initial_pledge, cost_params); + simulator.bond(initial_pledge, cost_params).unwrap(); simulator } // essentially our delegations + estimated rewards HAVE TO equal to what we actually determined fn check_rewarding_invariant(simulator: &Simulator) { for node in simulator.nodes.values() { - let delegation_sum: Decimal = - node.delegations.values().map(|d| d.dec_amount()).sum(); + let delegation_sum: Decimal = node + .delegations + .values() + .map(|d| d.dec_amount().unwrap()) + .sum(); let reward_sum: Decimal = node .delegations .values() - .map(|d| node.rewarding_details.determine_delegation_reward(d)) + .map(|d| { + node.rewarding_details + .determine_delegation_reward(d) + .unwrap() + }) .sum(); // let reward_sum = simulator.determine_total_delegation_reward(); @@ -262,7 +306,7 @@ mod tests { let epoch_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true); - let rewards = simulator.simulate_epoch_single_node(epoch_params); + let rewards = simulator.simulate_epoch_single_node(epoch_params).unwrap(); assert_eq!(rewards.delegates, Decimal::zero()); compare_decimals( @@ -275,11 +319,13 @@ mod tests { #[test] fn single_delegation_at_genesis() { let mut simulator = base_simulator(10000_000000); - simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0); + simulator + .delegate("alice", Coin::new(18000_000000, "unym"), 0) + .unwrap(); let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true); - let rewards = simulator.simulate_epoch_single_node(node_params); + let rewards = simulator.simulate_epoch_single_node(node_params).unwrap(); compare_decimals( rewards.delegates, @@ -290,7 +336,7 @@ mod tests { compare_decimals( rewards.delegates, - simulator.determine_total_delegation_reward(), + simulator.determine_total_delegation_reward().unwrap(), None, ); let node = &simulator.nodes[&0]; @@ -310,20 +356,22 @@ mod tests { let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true); - let rewards1 = simulator.simulate_epoch_single_node(node_params); + let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap(); let expected_operator1 = "1128452.5416104363".parse().unwrap(); assert_eq!(rewards1.delegates, Decimal::zero()); compare_decimals(rewards1.operator, expected_operator1, None); - simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0); + simulator + .delegate("alice", Coin::new(18000_000000, "unym"), 0) + .unwrap(); - let rewards2 = simulator.simulate_epoch_single_node(node_params); + let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap(); let expected_operator2 = "1363843.413584609".parse().unwrap(); let expected_delegator_reward1 = "1795952.25874404".parse().unwrap(); compare_decimals(rewards2.delegates, expected_delegator_reward1, None); compare_decimals(rewards2.operator, expected_operator2, None); - let rewards3 = simulator.simulate_epoch_single_node(node_params); + let rewards3 = simulator.simulate_epoch_single_node(node_params).unwrap(); let expected_operator3 = "1364017.7824440491".parse().unwrap(); let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap(); compare_decimals(rewards3.delegates, expected_delegator_reward2, None); @@ -357,11 +405,15 @@ mod tests { // add 2 delegations at genesis (because it makes things easier and as shown with previous tests // delegating at different times still work) - simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0); - simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0); + simulator + .delegate("alice", Coin::new(18000_000000, "unym"), 0) + .unwrap(); + simulator + .delegate("bob", Coin::new(4000_000000, "unym"), 0) + .unwrap(); // "normal", sanity check rewarding - let rewards1 = simulator.simulate_epoch_single_node(node_params); + let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap(); let expected_operator1 = "1411087.1007647323".parse().unwrap(); let expected_delegator_reward1 = "2199961.032388664".parse().unwrap(); compare_decimals(rewards1.delegates, expected_delegator_reward1, None); @@ -371,14 +423,15 @@ mod tests { let node = simulator.nodes.get_mut(&0).unwrap(); let reward = node .rewarding_details - .withdraw_operator_reward(&original_pledge); + .withdraw_operator_reward(&original_pledge) + .unwrap(); assert_eq!(reward.amount, truncate_reward_amount(expected_operator1)); assert_eq!( node.rewarding_details.operator, Decimal::from_atomics(original_pledge.amount, 0).unwrap() ); - let rewards2 = simulator.simulate_epoch_single_node(node_params); + let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap(); let expected_operator2 = "1411113.0004067947".parse().unwrap(); let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap(); compare_decimals(rewards2.delegates, expected_delegator_reward2, None); @@ -395,11 +448,15 @@ mod tests { // add 2 delegations at genesis (because it makes things easier and as shown with previous tests // delegating at different times still work) - simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0); - simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0); + simulator + .delegate("alice", Coin::new(18000_000000, "unym"), 0) + .unwrap(); + simulator + .delegate("bob", Coin::new(4000_000000, "unym"), 0) + .unwrap(); // "normal", sanity check rewarding - let rewards1 = simulator.simulate_epoch_single_node(node_params); + let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap(); let expected_operator1 = "1411087.1007647323".parse().unwrap(); let expected_delegator_reward1 = "2199961.032388664".parse().unwrap(); compare_decimals(rewards1.delegates, expected_delegator_reward1, None); @@ -417,7 +474,7 @@ mod tests { assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward)); // new reward after withdrawal - let rewards2 = simulator.simulate_epoch_single_node(node_params); + let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap(); let expected_operator2 = "1411250.1907492676".parse().unwrap(); let expected_delegator_reward2 = "2200004.051009689".parse().unwrap(); compare_decimals(rewards2.delegates, expected_delegator_reward2, None); @@ -460,22 +517,30 @@ mod tests { let mut performance = Percent::from_percentage_value(100).unwrap(); for epoch in 0..720 { if epoch == 0 { - simulator.delegate("a", Coin::new(18000_000000, "unym"), 0) + simulator + .delegate("a", Coin::new(18000_000000, "unym"), 0) + .unwrap() } if epoch == 42 { - simulator.delegate("b", Coin::new(2000_000000, "unym"), 0) + simulator + .delegate("b", Coin::new(2000_000000, "unym"), 0) + .unwrap() } if epoch == 89 { is_active = false; } if epoch == 123 { - simulator.delegate("c", Coin::new(6666_000000, "unym"), 0) + simulator + .delegate("c", Coin::new(6666_000000, "unym"), 0) + .unwrap() } if epoch == 167 { performance = Percent::from_percentage_value(90).unwrap(); } if epoch == 245 { - simulator.delegate("d", Coin::new(2050_000000, "unym"), 0) + simulator + .delegate("d", Coin::new(2050_000000, "unym"), 0) + .unwrap() } if epoch == 264 { let (delegation, _reward) = simulator.undelegate("b", 0).unwrap(); @@ -496,13 +561,15 @@ mod tests { // TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be } if epoch == 545 { - simulator.delegate("e", Coin::new(5000_000000, "unym"), 0) + simulator + .delegate("e", Coin::new(5000_000000, "unym"), 0) + .unwrap() } // this has to always hold check_rewarding_invariant(&simulator); let node_params = NodeRewardParams::new(performance, is_active); - simulator.simulate_epoch_single_node(node_params); + simulator.simulate_epoch_single_node(node_params).unwrap(); } // after everyone undelegates, there should be nothing left in the delegates pool @@ -555,95 +622,135 @@ mod tests { let mut simulator = Simulator::new(rewarding_params, interval); - let n0 = simulator.bond( - Coin::new(11_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: Coin::new(40_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0); + let n0 = simulator + .bond( + Coin::new(11_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(10).unwrap(), + interval_operating_cost: Coin::new(40_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0) + .unwrap(); - let n1 = simulator.bond( - Coin::new(1_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: Coin::new(40_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1); + let n1 = simulator + .bond( + Coin::new(1_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(10).unwrap(), + interval_operating_cost: Coin::new(40_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1) + .unwrap(); - let n2 = simulator.bond( - Coin::new(1_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: Coin::new(40_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2); + let n2 = simulator + .bond( + Coin::new(1_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(10).unwrap(), + interval_operating_cost: Coin::new(40_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2) + .unwrap(); - let n3 = simulator.bond( - Coin::new(1_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(0).unwrap(), - interval_operating_cost: Coin::new(500_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3); + let n3 = simulator + .bond( + Coin::new(1_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(0).unwrap(), + interval_operating_cost: Coin::new(500_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3) + .unwrap(); - let n4 = simulator.bond( - Coin::new(1000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: Coin::new(40_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4); + let n4 = simulator + .bond( + Coin::new(1000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(10).unwrap(), + interval_operating_cost: Coin::new(40_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4) + .unwrap(); - let n5 = simulator.bond( - Coin::new(1_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: Coin::new(40_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5); + let n5 = simulator + .bond( + Coin::new(1_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(10).unwrap(), + interval_operating_cost: Coin::new(40_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5) + .unwrap(); - let n6 = simulator.bond( - Coin::new(11_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: Coin::new(40_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6); + let n6 = simulator + .bond( + Coin::new(11_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(10).unwrap(), + interval_operating_cost: Coin::new(40_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6) + .unwrap(); - let n7 = simulator.bond( - Coin::new(1_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: Coin::new(40_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7); + let n7 = simulator + .bond( + Coin::new(1_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(10).unwrap(), + interval_operating_cost: Coin::new(40_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7) + .unwrap(); - let n8 = simulator.bond( - Coin::new(1_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(0).unwrap(), - interval_operating_cost: Coin::new(500_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8); + let n8 = simulator + .bond( + Coin::new(1_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(0).unwrap(), + interval_operating_cost: Coin::new(500_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8) + .unwrap(); - let n9 = simulator.bond( - Coin::new(1_000_000_000000, "unym"), - MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: Coin::new(40_000_000, "unym"), - }, - ); - simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9); + let n9 = simulator + .bond( + Coin::new(1_000_000_000000, "unym"), + MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(10).unwrap(), + interval_operating_cost: Coin::new(40_000_000, "unym"), + }, + ) + .unwrap(); + simulator + .delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9) + .unwrap(); let uptime_1 = Percent::from_percentage_value(100).unwrap(); let uptime_09 = Percent::from_percentage_value(90).unwrap(); @@ -665,7 +772,7 @@ mod tests { .collect::>(); for _ in 0..23 { - simulator.simulate_full_interval(&node_params); + simulator.simulate_full_interval(&node_params).unwrap(); } // we allow the delta to be within 0.1unym, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/simulated_node.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/simulated_node.rs index c7e1417a17..5af272366e 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/simulated_node.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/simulated_node.rs @@ -20,21 +20,25 @@ impl SimulatedNode { cost_params: MixNodeCostParams, initial_pledge: &Coin, current_epoch: EpochId, - ) -> Self { - SimulatedNode { + ) -> Result { + Ok(SimulatedNode { mix_id, rewarding_details: MixNodeRewarding::initialise_new( cost_params, initial_pledge, current_epoch, - ), + )?, delegations: HashMap::new(), - } + }) } - pub fn delegate>(&mut self, delegator: S, delegation: Coin) { + pub fn delegate>( + &mut self, + delegator: S, + delegation: Coin, + ) -> Result<(), MixnetContractError> { self.rewarding_details - .add_base_delegation(delegation.amount); + .add_base_delegation(delegation.amount)?; let delegator = delegator.into(); let delegation = Delegation::new( @@ -47,6 +51,7 @@ impl SimulatedNode { ); self.delegations.insert(delegator, delegation); + Ok(()) } pub fn undelegate>( @@ -54,16 +59,19 @@ impl SimulatedNode { delegator: S, ) -> Result<(Coin, Coin), MixnetContractError> { let delegator = delegator.into(); - let delegation = self - .delegations - .remove(&delegator) - .expect("delegation not found"); + let delegation = self.delegations.remove(&delegator).ok_or( + MixnetContractError::NoMixnodeDelegationFound { + mix_id: MixId::MAX, + address: delegator, + proxy: None, + }, + )?; let reward = self .rewarding_details - .determine_delegation_reward(&delegation); + .determine_delegation_reward(&delegation)?; self.rewarding_details - .remove_delegation_decimal(delegation.dec_amount() + reward)?; + .remove_delegation_decimal(delegation.dec_amount()? + reward)?; let reward_denom = &delegation.amount.denom; let truncated_reward = truncate_reward(reward, reward_denom); diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index 7d6e8f2d0d..9b0090c014 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -61,7 +61,7 @@ impl Percent { impl Display for Percent { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0; + let adjusted = Decimal::from_ratio(100u32, 1u32) * self.0; write!(f, "{}%", adjusted) } } @@ -119,6 +119,10 @@ impl LayerDistribution { (Layer::Two, self.layer2), (Layer::Three, self.layer3), ]; + + // we explicitly put 3 elements into the iterator, so the iterator is DEFINITELY + // not empty and thus the unwrap cannot fail + #[allow(clippy::unwrap_used)] layers.iter().min_by_key(|x| x.1).unwrap().0 } diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs index 1d7b4d0ab1..39376f33f9 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs @@ -1,5 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + use cosmwasm_std::{Addr, Coin, Timestamp, Uint128}; use mixnet_contract_common::MixId; use schemars::JsonSchema; diff --git a/contracts/clippy.toml b/contracts/clippy.toml new file mode 100644 index 0000000000..0358cdb508 --- /dev/null +++ b/contracts/clippy.toml @@ -0,0 +1,2 @@ +allow-unwrap-in-tests = true +allow-expect-in-tests = true diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 1e91323c6c..f9ce24a1e2 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -63,7 +63,7 @@ pub fn instantiate( Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env); let reward_params = msg .initial_rewarding_params - .into_rewarding_params(msg.epochs_in_interval); + .into_rewarding_params(msg.epochs_in_interval)?; interval_storage::initialise_storage(deps.storage, starting_interval)?; mixnet_params_storage::initialise_storage(deps.storage, state)?; diff --git a/contracts/mixnet/src/interval/pending_events.rs b/contracts/mixnet/src/interval/pending_events.rs index 84a5be1163..8126168e71 100644 --- a/contracts/mixnet/src/interval/pending_events.rs +++ b/contracts/mixnet/src/interval/pending_events.rs @@ -107,7 +107,7 @@ pub(crate) fn delegate( }; // add the amount we're intending to delegate (whether it's fresh or we're adding to the existing one) - mix_rewarding.add_base_delegation(stored_delegation_amount.amount); + mix_rewarding.add_base_delegation(stored_delegation_amount.amount)?; let cosmos_event = new_delegation_event( created_at, diff --git a/contracts/mixnet/src/lib.rs b/contracts/mixnet/src/lib.rs index 3cff5ed93f..c22e7dc1a1 100644 --- a/contracts/mixnet/src/lib.rs +++ b/contracts/mixnet/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + mod constants; pub mod contract; mod delegations; diff --git a/contracts/mixnet/src/mixnodes/helpers.rs b/contracts/mixnet/src/mixnodes/helpers.rs index 15e5e82f98..8244f67b13 100644 --- a/contracts/mixnet/src/mixnodes/helpers.rs +++ b/contracts/mixnet/src/mixnodes/helpers.rs @@ -78,7 +78,7 @@ pub(crate) fn save_new_mixnode( let mix_id = next_mixnode_id_counter(storage)?; let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id(); - let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch); + let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?; let mixnode_bond = MixNodeBond::new( mix_id, owner, diff --git a/contracts/mixnet/src/rewards/helpers.rs b/contracts/mixnet/src/rewards/helpers.rs index 02cdf83e68..752b2714a6 100644 --- a/contracts/mixnet/src/rewards/helpers.rs +++ b/contracts/mixnet/src/rewards/helpers.rs @@ -4,8 +4,9 @@ use super::storage; use crate::delegations::storage as delegations_storage; use crate::interval::storage as interval_storage; -use cosmwasm_std::{Coin, Decimal, Storage}; +use cosmwasm_std::{Coin, Storage}; use mixnet_contract_common::error::MixnetContractError; +use mixnet_contract_common::helpers::IntoBaseDecimal; use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding}; use mixnet_contract_common::Delegation; @@ -21,11 +22,10 @@ pub(crate) fn apply_reward_pool_changes( let reward_pool = rewarding_params.interval.reward_pool - pending_pool_change.removed + pending_pool_change.added; let staking_supply = rewarding_params.interval.staking_supply + pending_pool_change.removed; - let epoch_reward_budget = reward_pool - / Decimal::from_atomics(interval.epochs_in_interval(), 0).unwrap() + let epoch_reward_budget = reward_pool / interval.epochs_in_interval().into_base_decimal()? * rewarding_params.interval.interval_pool_emission; let stake_saturation_point = - staking_supply / Decimal::from_atomics(rewarding_params.rewarded_set_size, 0).unwrap(); + staking_supply / rewarding_params.rewarded_set_size.into_base_decimal()?; rewarding_params.interval.reward_pool = reward_pool; rewarding_params.interval.staking_supply = staking_supply; @@ -45,7 +45,7 @@ pub(crate) fn withdraw_operator_reward( let mix_id = mix_details.mix_id(); let mut mix_rewarding = mix_details.rewarding_details; let original_pledge = mix_details.bond_information.original_pledge; - let reward = mix_rewarding.withdraw_operator_reward(&original_pledge); + let reward = mix_rewarding.withdraw_operator_reward(&original_pledge)?; // save updated rewarding info storage::MIXNODE_REWARDING.save(store, mix_id, &mix_rewarding)?; @@ -86,7 +86,7 @@ mod tests { let mut test = TestSetup::new(); let epochs_in_interval = test.current_interval().epochs_in_interval(); - let epochs_in_interval_dec = Decimal::from_atomics(epochs_in_interval, 0).unwrap(); + let epochs_in_interval_dec = epochs_in_interval.into_base_decimal().unwrap(); let start_rewarding_params = test.rewarding_params(); // nothing changes if pending changes are empty @@ -94,7 +94,7 @@ mod tests { assert_eq!(start_rewarding_params, test.rewarding_params()); // normal case of having distributed some rewards - let distributed_rewards = Decimal::from_atomics(100_000_000u32, 0).unwrap(); + let distributed_rewards = 100_000_000u32.into_base_decimal().unwrap(); storage::PENDING_REWARD_POOL_CHANGE .save( test.deps_mut().storage, @@ -131,7 +131,10 @@ mod tests { assert_eq!( updated_rewarding_params.interval.stake_saturation_point, updated_rewarding_params.interval.staking_supply - / Decimal::from_atomics(updated_rewarding_params.rewarded_set_size, 0).unwrap() + / updated_rewarding_params + .rewarded_set_size + .into_base_decimal() + .unwrap() ); // resets changes back to 0 @@ -143,7 +146,7 @@ mod tests { ); // future case of having to also increase the reward pool - let added_credentials = Decimal::from_atomics(50_000_000u32, 0).unwrap(); + let added_credentials = 50_000_000u32.into_base_decimal().unwrap(); storage::PENDING_REWARD_POOL_CHANGE .save( test.deps_mut().storage, @@ -180,7 +183,10 @@ mod tests { assert_eq!( updated_rewarding_params2.interval.stake_saturation_point, updated_rewarding_params2.interval.staking_supply - / Decimal::from_atomics(updated_rewarding_params2.rewarded_set_size, 0).unwrap() + / updated_rewarding_params2 + .rewarded_set_size + .into_base_decimal() + .unwrap() ); // resets changes back to 0 @@ -197,7 +203,7 @@ mod tests { let mut test = TestSetup::new(); let pledge = Uint128::new(250_000_000); - let pledge_dec = Decimal::from_atomics(250_000_000u32, 0).unwrap(); + let pledge_dec = 250_000_000u32.into_base_decimal().unwrap(); let mix_id = test.add_dummy_mixnode("mix-owner", Some(pledge)); // no rewards @@ -234,7 +240,7 @@ mod tests { let mut test = TestSetup::new(); let delegation_amount = Uint128::new(2500_000_000); - let delegation_dec = Decimal::from_atomics(2500_000_000u32, 0).unwrap(); + let delegation_dec = 2500_000_000u32.into_base_decimal().unwrap(); let mix_id = test.add_dummy_mixnode("mix-owner", None); let delegator = "delegator"; test.add_immediate_delegation(delegator, delegation_amount, mix_id); diff --git a/contracts/mixnet/src/rewards/queries.rs b/contracts/mixnet/src/rewards/queries.rs index 2b969c410b..d49b1165c6 100644 --- a/contracts/mixnet/src/rewards/queries.rs +++ b/contracts/mixnet/src/rewards/queries.rs @@ -7,6 +7,7 @@ use crate::interval::storage as interval_storage; use crate::mixnodes; use crate::mixnodes::storage as mixnodes_storage; use cosmwasm_std::{coin, Coin, Decimal, Deps, StdResult}; +use mixnet_contract_common::helpers::into_base_decimal; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams}; use mixnet_contract_common::rewarding::helpers::truncate_reward; @@ -19,16 +20,18 @@ pub(crate) fn query_rewarding_params(deps: Deps<'_>) -> StdResult) -> PendingRewardResponse { - match mix_details { +fn pending_operator_reward( + mix_details: Option, +) -> StdResult { + Ok(match mix_details { Some(mix_details) => PendingRewardResponse { amount_staked: Some(mix_details.original_pledge().clone()), amount_earned: Some(mix_details.pending_operator_reward()), - amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()), + amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()?), mixnode_still_fully_bonded: !mix_details.is_unbonding(), }, None => PendingRewardResponse::default(), - } + }) } pub fn query_pending_operator_reward( @@ -39,7 +42,7 @@ pub fn query_pending_operator_reward( // in order to determine operator's reward we need to know its original pledge and thus // we have to load the entire thing let mix_details = mixnodes::helpers::get_mixnode_details_by_owner(deps.storage, owner_address)?; - Ok(pending_operator_reward(mix_details)) + pending_operator_reward(mix_details) } pub fn query_pending_mixnode_operator_reward( @@ -49,7 +52,7 @@ pub fn query_pending_mixnode_operator_reward( // in order to determine operator's reward we need to know its original pledge and thus // we have to load the entire thing let mix_details = mixnodes::helpers::get_mixnode_details_by_id(deps.storage, mix_id)?; - Ok(pending_operator_reward(mix_details)) + pending_operator_reward(mix_details) } pub fn query_pending_delegator_reward( @@ -74,8 +77,8 @@ pub fn query_pending_delegator_reward( None => return Ok(PendingRewardResponse::default()), }; - let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation); - let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation); + let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation)?; + let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation)?; // check if the mixnode isnt in the process of unbonding (or has already unbonded) let is_bonded = matches!(mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)?, Some(mix_bond) if !mix_bond.is_unbonding); @@ -177,8 +180,8 @@ pub(crate) fn query_estimated_current_epoch_delegator_reward( None => return Ok(EstimatedCurrentEpochRewardResponse::empty_response()), }; - let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap(); - let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation); + let staked_dec = into_base_decimal(delegation.amount.amount)?; + let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation)?; let amount_staked = delegation.amount; // check if the mixnode isnt in the process of unbonding (or has already unbonded) @@ -792,7 +795,10 @@ mod tests { let delegation = test.delegation(mix_id, owner, &None); let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap(); - let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation); + let current_value = staked_dec + + mix_rewarding + .determine_delegation_reward(&delegation) + .unwrap(); let amount_staked = delegation.amount; EstimatedCurrentEpochRewardResponse { diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index b56686651f..2099fcd629 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -744,7 +744,7 @@ pub mod tests { performance, in_active_set: true, }; - let sim_res = sim.simulate_epoch_single_node(node_params); + let sim_res = sim.simulate_epoch_single_node(node_params).unwrap(); assert_eq!(sim_res, dist); } test.skip_to_next_epoch_end(); @@ -768,7 +768,7 @@ pub mod tests { performance, in_active_set: true, }; - let sim_res = sim.simulate_epoch_single_node(node_params); + let sim_res = sim.simulate_epoch_single_node(node_params).unwrap(); assert_eq!(sim_res, dist); } test.skip_to_next_epoch_end(); @@ -809,8 +809,8 @@ pub mod tests { in_active_set: true, }; - let dist1 = sim1.simulate_epoch_single_node(node_params); - let dist2 = sim2.simulate_epoch_single_node(node_params); + let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap(); + let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap(); let env = test.env(); @@ -858,15 +858,17 @@ pub mod tests { let unit_delegation_base = actual_prior1.unit_delegation; // recompute the state of fully compounded delegation from before this rewarding was distributed - let pre_rewarding_del11 = del11.dec_amount() - + (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount() + let pre_rewarding_del11 = del11.dec_amount().unwrap() + + (prior_unit_reward - del11.cumulative_reward_ratio) + * del11.dec_amount().unwrap() / (del11.cumulative_reward_ratio + unit_delegation_base); let computed_del11_reward = pre_rewarding_del11 / prior_delegates1 * delegates_reward1; - let pre_rewarding_del12 = del12.dec_amount() - + (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount() + let pre_rewarding_del12 = del12.dec_amount().unwrap() + + (prior_unit_reward - del12.cumulative_reward_ratio) + * del12.dec_amount().unwrap() / (del12.cumulative_reward_ratio + unit_delegation_base); let computed_del12_reward = @@ -920,8 +922,9 @@ pub mod tests { let unit_delegation_base = actual_prior2.unit_delegation; // recompute the state of fully compounded delegation from before this rewarding was distributed - let pre_rewarding_del21 = del21.dec_amount() - + (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount() + let pre_rewarding_del21 = del21.dec_amount().unwrap() + + (prior_unit_reward - del21.cumulative_reward_ratio) + * del21.dec_amount().unwrap() / (del21.cumulative_reward_ratio + unit_delegation_base); let computed_del21_reward = @@ -949,8 +952,8 @@ pub mod tests { in_active_set: true, }; - let dist1 = sim1.simulate_epoch_single_node(node_params); - let dist2 = sim2.simulate_epoch_single_node(node_params); + let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap(); + let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap(); let env = test.env(); @@ -998,22 +1001,25 @@ pub mod tests { let unit_delegation_base = actual_prior1.unit_delegation; // recompute the state of fully compounded delegation from before this rewarding was distributed - let pre_rewarding_del11 = del11.dec_amount() - + (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount() + let pre_rewarding_del11 = del11.dec_amount().unwrap() + + (prior_unit_reward - del11.cumulative_reward_ratio) + * del11.dec_amount().unwrap() / (del11.cumulative_reward_ratio + unit_delegation_base); let computed_del11_reward = pre_rewarding_del11 / prior_delegates1 * delegates_reward1; - let pre_rewarding_del12 = del12.dec_amount() - + (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount() + let pre_rewarding_del12 = del12.dec_amount().unwrap() + + (prior_unit_reward - del12.cumulative_reward_ratio) + * del12.dec_amount().unwrap() / (del12.cumulative_reward_ratio + unit_delegation_base); let computed_del12_reward = pre_rewarding_del12 / prior_delegates1 * delegates_reward1; - let pre_rewarding_del13 = del13.dec_amount() - + (prior_unit_reward - del13.cumulative_reward_ratio) * del13.dec_amount() + let pre_rewarding_del13 = del13.dec_amount().unwrap() + + (prior_unit_reward - del13.cumulative_reward_ratio) + * del13.dec_amount().unwrap() / (del13.cumulative_reward_ratio + unit_delegation_base); let computed_del13_reward = @@ -1067,15 +1073,17 @@ pub mod tests { let unit_delegation_base = actual_prior2.unit_delegation; // recompute the state of fully compounded delegation from before this rewarding was distributed - let pre_rewarding_del21 = del21.dec_amount() - + (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount() + let pre_rewarding_del21 = del21.dec_amount().unwrap() + + (prior_unit_reward - del21.cumulative_reward_ratio) + * del21.dec_amount().unwrap() / (del21.cumulative_reward_ratio + unit_delegation_base); let computed_del21_reward = pre_rewarding_del21 / prior_delegates2 * delegates_reward2; - let pre_rewarding_del23 = del23.dec_amount() - + (prior_unit_reward - del23.cumulative_reward_ratio) * del23.dec_amount() + let pre_rewarding_del23 = del23.dec_amount().unwrap() + + (prior_unit_reward - del23.cumulative_reward_ratio) + * del23.dec_amount().unwrap() / (del23.cumulative_reward_ratio + unit_delegation_base); let computed_del23_reward = diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index b4ed7f3fd2..9ec82ac2b8 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -69,6 +69,10 @@ pub(crate) fn validate_pledge( }); } + // throughout this function we've been using the value at `pledge[0]` without problems + // (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine, + // since it cannot possibly fail without UB + #[allow(clippy::unwrap_used)] Ok(pledge.pop().unwrap()) } @@ -106,6 +110,10 @@ pub(crate) fn validate_delegation_stake( return Err(MixnetContractError::EmptyDelegation); } + // throughout this function we've been using the value at `delegation[0]` without problems + // (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine, + // since it cannot possibly fail without UB + #[allow(clippy::unwrap_used)] Ok(delegation.pop().unwrap()) } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 99a2acb9f4..27ea0eef6a 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -579,7 +579,7 @@ pub fn try_get_current_vesting_period( env: Env, ) -> Result { let account = account_from_address(address, deps.storage, deps.api)?; - Ok(account.get_current_vesting_period(env.block.time)) + account.get_current_vesting_period(env.block.time) } /// Loads mixnode bond from vesting contract storage. @@ -666,7 +666,7 @@ pub fn try_get_original_vesting( deps: Deps<'_>, ) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; - Ok(account.get_original_vesting()) + account.get_original_vesting() } /// See [crate::traits::VestingAccount::get_delegated_free] diff --git a/contracts/vesting/src/errors.rs b/contracts/vesting/src/errors.rs index 92b8524292..638a654bc8 100644 --- a/contracts/vesting/src/errors.rs +++ b/contracts/vesting/src/errors.rs @@ -48,4 +48,6 @@ pub enum ContractError { MinVestingFunds { sent: u128, need: u128 }, #[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())] LockedPledgeCapReached { current: Uint128, cap: Uint128 }, + #[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())] + UnpopulatedVestingPeriods { owner: Addr }, } diff --git a/contracts/vesting/src/lib.rs b/contracts/vesting/src/lib.rs index 8da47f12a6..ad4fd4f5f4 100644 --- a/contracts/vesting/src/lib.rs +++ b/contracts/vesting/src/lib.rs @@ -1,6 +1,9 @@ #![allow(rustdoc::private_intra_doc_links)] //! Nym vesting contract, providing vesting accounts with ability to stake unvested tokens +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + pub mod contract; mod errors; mod queued_migrations; diff --git a/contracts/vesting/src/traits/vesting_account.rs b/contracts/vesting/src/traits/vesting_account.rs index 04173a56c6..9d44780fbe 100644 --- a/contracts/vesting/src/traits/vesting_account.rs +++ b/contracts/vesting/src/traits/vesting_account.rs @@ -55,7 +55,7 @@ pub trait VestingAccount { /// Returns amount of coins set at account creation /// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl - fn get_original_vesting(&self) -> OriginalVestingResponse; + fn get_original_vesting(&self) -> Result; /// See [/vesting-contract/struct.Account.html/method.get_delegated_free] for impl fn get_delegated_free( diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index 203ab3feb2..4f75c9461c 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -65,8 +65,13 @@ impl Account { self.periods.len() } - pub fn period_duration(&self) -> u64 { - self.periods.get(0).unwrap().period_seconds + pub fn period_duration(&self) -> Result { + self.periods + .get(0) + .ok_or(ContractError::UnpopulatedVestingPeriods { + owner: self.owner_address.clone(), + }) + .map(|p| p.period_seconds) } pub fn storage_key(&self) -> u32 { @@ -103,11 +108,28 @@ impl Account { /// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet. /// In case vesting is over it will always return NUM_VESTING_PERIODS. - pub fn get_current_vesting_period(&self, block_time: Timestamp) -> Period { - if block_time.seconds() < self.periods.first().unwrap().start_time { - Period::Before - } else if self.periods.last().unwrap().end_time() < block_time { - Period::After + pub fn get_current_vesting_period( + &self, + block_time: Timestamp, + ) -> Result { + let first_period = + self.periods + .first() + .ok_or(ContractError::UnpopulatedVestingPeriods { + owner: self.owner_address.clone(), + })?; + + let last_period = self + .periods + .last() + .ok_or(ContractError::UnpopulatedVestingPeriods { + owner: self.owner_address.clone(), + })?; + + if block_time.seconds() < first_period.start_time { + Ok(Period::Before) + } else if last_period.end_time() < block_time { + Ok(Period::After) } else { let mut index = 0; for period in &self.periods { @@ -116,7 +138,7 @@ impl Account { } index += 1; } - Period::In(index) + Ok(Period::In(index)) } } diff --git a/contracts/vesting/src/vesting/account/vesting_account.rs b/contracts/vesting/src/vesting/account/vesting_account.rs index d7eeeb0946..f715e5d5df 100644 --- a/contracts/vesting/src/vesting/account/vesting_account.rs +++ b/contracts/vesting/src/vesting/account/vesting_account.rs @@ -67,7 +67,7 @@ impl VestingAccount for Account { storage: &dyn Storage, ) -> Result { let block_time = block_time.unwrap_or(env.block.time); - let period = self.get_current_vesting_period(block_time); + let period = self.get_current_vesting_period(block_time)?; let denom = MIX_DENOM.load(storage)?; let amount = match period { @@ -94,7 +94,7 @@ impl VestingAccount for Account { storage: &dyn Storage, ) -> Result { Ok(Coin { - amount: self.get_original_vesting().amount().amount + amount: self.get_original_vesting()?.amount().amount - self.get_vested_coins(block_time, env, storage)?.amount, denom: MIX_DENOM.load(storage)?, }) @@ -108,12 +108,12 @@ impl VestingAccount for Account { self.periods[(self.num_vesting_periods() - 1) as usize].end_time() } - fn get_original_vesting(&self) -> OriginalVestingResponse { - OriginalVestingResponse::new( + fn get_original_vesting(&self) -> Result { + Ok(OriginalVestingResponse::new( self.coin.clone(), self.num_vesting_periods(), - self.period_duration(), - ) + self.period_duration()?, + )) } fn get_delegated_free( @@ -123,7 +123,7 @@ impl VestingAccount for Account { storage: &dyn Storage, ) -> Result { let block_time = block_time.unwrap_or(env.block.time); - let period = self.get_current_vesting_period(block_time); + let period = self.get_current_vesting_period(block_time)?; let withdrawn = self.load_withdrawn(storage)?; let max_available = self .get_vested_coins(Some(block_time), env, storage)? @@ -155,7 +155,7 @@ impl VestingAccount for Account { let block_time = block_time.unwrap_or(env.block.time); let delegated_free = self.get_delegated_free(Some(block_time), env, storage)?; - let period = self.get_current_vesting_period(block_time); + let period = self.get_current_vesting_period(block_time)?; let start_time = match period { Period::Before => 0, Period::After => u64::MAX, @@ -180,7 +180,7 @@ impl VestingAccount for Account { storage: &dyn Storage, ) -> Result { let block_time = block_time.unwrap_or(env.block.time); - let period = self.get_current_vesting_period(block_time); + let period = self.get_current_vesting_period(block_time)?; let max_vested = self.get_vested_coins(Some(block_time), env, storage)?; let start_time = match period { Period::Before => 0, diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index 3e46c46496..79246313c4 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -180,12 +180,14 @@ mod tests { assert_eq!(account.periods().len(), num_vesting_periods as usize); - let current_period = account.get_current_vesting_period(Timestamp::from_seconds(0)); + let current_period = account + .get_current_vesting_period(Timestamp::from_seconds(0)) + .unwrap(); assert_eq!(Period::Before, current_period); let block_time = Timestamp::from_seconds(account.start_time().seconds() + vesting_period + 1); - let current_period = account.get_current_vesting_period(block_time); + let current_period = account.get_current_vesting_period(block_time).unwrap(); assert_eq!(current_period, Period::In(1)); let vested_coins = account .get_vested_coins(Some(block_time), &env, &deps.storage) @@ -196,21 +198,37 @@ mod tests { assert_eq!( vested_coins.amount, Uint128::new( - account.get_original_vesting().amount().amount.u128() / num_vesting_periods as u128 + account + .get_original_vesting() + .unwrap() + .amount() + .amount + .u128() + / num_vesting_periods as u128 ) ); assert_eq!( vesting_coins.amount, Uint128::new( - account.get_original_vesting().amount().amount.u128() - - account.get_original_vesting().amount().amount.u128() + account + .get_original_vesting() + .unwrap() + .amount() + .amount + .u128() + - account + .get_original_vesting() + .unwrap() + .amount() + .amount + .u128() / num_vesting_periods as u128 ) ); let block_time = Timestamp::from_seconds(account.start_time().seconds() + 5 * vesting_period + 1); - let current_period = account.get_current_vesting_period(block_time); + let current_period = account.get_current_vesting_period(block_time).unwrap(); assert_eq!(current_period, Period::In(5)); let vested_coins = account .get_vested_coins(Some(block_time), &env, &deps.storage) @@ -221,15 +239,30 @@ mod tests { assert_eq!( vested_coins.amount, Uint128::new( - 5 * account.get_original_vesting().amount().amount.u128() + 5 * account + .get_original_vesting() + .unwrap() + .amount() + .amount + .u128() / num_vesting_periods as u128 ) ); assert_eq!( vesting_coins.amount, Uint128::new( - account.get_original_vesting().amount().amount.u128() - - 5 * account.get_original_vesting().amount().amount.u128() + account + .get_original_vesting() + .unwrap() + .amount() + .amount + .u128() + - 5 * account + .get_original_vesting() + .unwrap() + .amount() + .amount + .u128() / num_vesting_periods as u128 ) ); @@ -237,7 +270,7 @@ mod tests { let block_time = Timestamp::from_seconds( account.start_time().seconds() + vesting_over_period * vesting_period + 1, ); - let current_period = account.get_current_vesting_period(block_time); + let current_period = account.get_current_vesting_period(block_time).unwrap(); assert_eq!(current_period, Period::After); let vested_coins = account .get_vested_coins(Some(block_time), &env, &deps.storage) @@ -247,7 +280,14 @@ mod tests { .unwrap(); assert_eq!( vested_coins.amount, - Uint128::new(account.get_original_vesting().amount().amount.u128()) + Uint128::new( + account + .get_original_vesting() + .unwrap() + .amount() + .amount + .u128() + ) ); assert_eq!(vesting_coins.amount, Uint128::zero()); }