// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::constants::INITIAL_PLEDGE_AMOUNT; use crate::interval::storage as interval_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::nodes::storage as nymnodes_storage; use crate::queued_migrations::introduce_key_rotation_id; use crate::rewards::storage::RewardingStorage; use cosmwasm_std::{ entry_point, to_json_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, }; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::{ ConfigScoreParams, ContractState, ContractStateParams, DelegationsParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, OperatorsParams, QueryMsg, }; use nym_contracts_common::set_build_information; use std::str::FromStr; // version info for migration info const CONTRACT_NAME: &str = "crate:nym-mixnet-contract"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); fn default_initial_state( msg: &InstantiateMsg, owner: Addr, rewarding_validator_address: Addr, vesting_contract_address: Addr, ) -> ContractState { // we have to temporarily preserve this functionalities until it can be removed #[allow(deprecated)] ContractState { owner: Some(owner), rewarding_validator_address, vesting_contract_address, rewarding_denom: msg.rewarding_denom.clone(), params: ContractStateParams { delegations_params: DelegationsParams { minimum_delegation: None, }, operators_params: OperatorsParams { minimum_pledge: Coin { denom: msg.rewarding_denom.clone(), amount: INITIAL_PLEDGE_AMOUNT, }, profit_margin: msg.profit_margin, interval_operating_cost: msg.interval_operating_cost, }, config_score_params: ConfigScoreParams { version_weights: msg.version_score_weights, version_score_formula_params: msg.version_score_params, }, }, } } /// Instantiate the contract. /// /// `deps` contains Storage, API and Querier /// `env` contains block, message and contract info /// `msg` is the contract initialization message, sort of like a constructor call. #[entry_point] pub fn instantiate( mut deps: DepsMut<'_>, env: Env, info: MessageInfo, msg: InstantiateMsg, ) -> Result { if msg.epochs_in_interval == 0 { return Err(MixnetContractError::EpochsInIntervalZero); } if msg.epoch_duration.as_secs() == 0 { return Err(MixnetContractError::EpochDurationZero); } if semver::Version::from_str(&msg.current_nym_node_version).is_err() { return Err(MixnetContractError::InvalidNymNodeSemver { provided: msg.current_nym_node_version, }); } let key_rotation_validity = msg.key_validity_in_epochs(); if key_rotation_validity < InstantiateMsg::MIN_KEY_ROTATION_VALIDITY { return Err(MixnetContractError::TooShortRotationInterval); } let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?; let vesting_contract_address = deps.api.addr_validate(&msg.vesting_contract_address)?; let state = default_initial_state( &msg, info.sender.clone(), rewarding_validator_address.clone(), vesting_contract_address, ); let starting_interval = 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)?; interval_storage::initialise_storage( deps.storage, starting_interval, rewarding_validator_address, )?; mixnet_params_storage::initialise_storage( deps.branch(), &env, state, info.sender, msg.current_nym_node_version, )?; RewardingStorage::new().initialise(deps.storage, reward_params)?; nymnodes_storage::initialise_storage(deps.storage, key_rotation_validity)?; cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; set_build_information!(deps.storage)?; Ok(Response::default()) } /// Handle an incoming message #[entry_point] pub fn execute( deps: DepsMut<'_>, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { match msg { // state/sys-params-related ExecuteMsg::UpdateAdmin { admin } => { crate::mixnet_contract_settings::transactions::try_update_contract_admin( deps, info, admin, ) } ExecuteMsg::UpdateRewardingValidatorAddress { address } => { crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address( deps, info, address, ) } ExecuteMsg::UpdateContractStateParams { update } => { crate::mixnet_contract_settings::transactions::try_update_contract_settings( deps, info, update, ) } ExecuteMsg::UpdateCurrentNymNodeSemver { current_version } => { crate::mixnet_contract_settings::transactions::try_update_current_nym_node_semver( deps, env, info, current_version, ) } ExecuteMsg::UpdateActiveSetDistribution { update, force_immediately, } => crate::rewards::transactions::try_update_active_set_distribution( deps, env, info, update, force_immediately, ), ExecuteMsg::UpdateRewardingParams { updated_params, force_immediately, } => crate::rewards::transactions::try_update_rewarding_params( deps, env, info, updated_params, force_immediately, ), ExecuteMsg::UpdateIntervalConfig { epochs_in_interval, epoch_duration_secs, force_immediately, } => crate::interval::transactions::try_update_interval_config( deps, env, info, epochs_in_interval, epoch_duration_secs, force_immediately, ), ExecuteMsg::BeginEpochTransition {} => { crate::interval::transactions::try_begin_epoch_transition(deps, env, info) } ExecuteMsg::AssignRoles { assignment } => { crate::interval::transactions::try_assign_roles(deps, env, info, assignment) } ExecuteMsg::ReconcileEpochEvents { limit } => { crate::interval::transactions::try_reconcile_epoch_events(deps, env, info, limit) } // mixnode-related: ExecuteMsg::BondMixnode { mix_node, cost_params, owner_signature, } => crate::mixnodes::transactions::try_add_mixnode( deps, env, info, mix_node, cost_params, owner_signature, ), ExecuteMsg::UnbondMixnode {} => { crate::mixnodes::transactions::try_remove_mixnode(deps, env, info) } ExecuteMsg::UpdateMixnodeConfig { new_config } => { crate::mixnodes::transactions::try_update_mixnode_config(deps, info, new_config) } ExecuteMsg::MigrateMixnode {} => { crate::mixnodes::transactions::try_migrate_to_nymnode(deps, info) } // gateway-related: ExecuteMsg::BondGateway { gateway, owner_signature, } => crate::gateways::transactions::try_add_gateway( deps, env, info, gateway, owner_signature, ), ExecuteMsg::UnbondGateway {} => { crate::gateways::transactions::try_remove_gateway(deps, info) } ExecuteMsg::UpdateGatewayConfig { new_config } => { crate::gateways::transactions::try_update_gateway_config(deps, info, new_config) } ExecuteMsg::MigrateGateway { cost_params } => { crate::gateways::transactions::try_migrate_to_nymnode(deps, info, cost_params) } // nym-node related: ExecuteMsg::BondNymNode { node, cost_params, owner_signature, } => crate::nodes::transactions::try_add_nym_node( deps, env, info, node, cost_params, owner_signature, ), ExecuteMsg::UnbondNymNode {} => { crate::nodes::transactions::try_remove_nym_node(deps, env, info) } ExecuteMsg::UpdateNodeConfig { update } => { crate::nodes::transactions::try_update_node_config(deps, info, update) } // nym-node/mixnode-related: ExecuteMsg::PledgeMore {} => { crate::compat::transactions::try_increase_pledge(deps, env, info) } ExecuteMsg::DecreasePledge { decrease_by } => { crate::compat::transactions::try_decrease_pledge(deps, env, info, decrease_by) } ExecuteMsg::UpdateCostParams { new_costs } => { crate::compat::transactions::try_update_cost_params(deps, env, info, new_costs) } // delegation-related: ExecuteMsg::Delegate { node_id } => { crate::delegations::transactions::try_delegate_to_node(deps, env, info, node_id) } ExecuteMsg::Undelegate { node_id } => { crate::delegations::transactions::try_remove_delegation_from_node( deps, env, info, node_id, ) } // reward-related ExecuteMsg::RewardNode { node_id, params } => { crate::rewards::transactions::try_reward_node(deps, env, info, node_id, params) } ExecuteMsg::WithdrawOperatorReward {} => { crate::compat::transactions::try_withdraw_operator_reward(deps, info) } ExecuteMsg::WithdrawDelegatorReward { node_id: mix_id } => { crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id) } // vesting migration: ExecuteMsg::MigrateVestedMixNode { .. } => { crate::vesting_migration::try_migrate_vested_mixnode(deps, info) } ExecuteMsg::MigrateVestedDelegation { mix_id } => { crate::vesting_migration::try_migrate_vested_delegation(deps, env, info, mix_id) } // legacy vesting ExecuteMsg::BondMixnodeOnBehalf { .. } | ExecuteMsg::PledgeMoreOnBehalf { .. } | ExecuteMsg::DecreasePledgeOnBehalf { .. } | ExecuteMsg::UnbondMixnodeOnBehalf { .. } | ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. } | ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. } | ExecuteMsg::BondGatewayOnBehalf { .. } | ExecuteMsg::UnbondGatewayOnBehalf { .. } | ExecuteMsg::UpdateGatewayConfigOnBehalf { .. } | ExecuteMsg::DelegateToMixnodeOnBehalf { .. } | ExecuteMsg::UndelegateFromMixnodeOnBehalf { .. } | ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. } | ExecuteMsg::WithdrawDelegatorRewardOnBehalf { .. } => { Err(MixnetContractError::DisabledVestingOperation) } // testing-only #[cfg(feature = "contract-testing")] ExecuteMsg::TestingResolveAllPendingEvents { limit } => { crate::testing::transactions::try_resolve_all_pending_events(deps, env, limit) } } } #[entry_point] pub fn query( deps: Deps<'_>, env: Env, msg: QueryMsg, ) -> Result { let query_res = match msg { QueryMsg::GetContractVersion {} => { to_json_binary(&crate::mixnet_contract_settings::queries::query_contract_version()) } QueryMsg::GetCW2ContractVersion {} => { to_json_binary(&cw2::get_contract_version(deps.storage)?) } QueryMsg::GetRewardingValidatorAddress {} => to_json_binary( &crate::mixnet_contract_settings::queries::query_rewarding_validator_address(deps)?, ), QueryMsg::GetStateParams {} => to_json_binary( &crate::mixnet_contract_settings::queries::query_contract_settings_params(deps)?, ), QueryMsg::GetState {} => { to_json_binary(&crate::mixnet_contract_settings::queries::query_contract_state(deps)?) } QueryMsg::GetCurrentNymNodeVersion {} => to_json_binary( &crate::mixnet_contract_settings::queries::query_current_nym_node_version(deps)?, ), QueryMsg::GetNymNodeVersionHistory { limit, start_after } => to_json_binary( &crate::mixnet_contract_settings::queries::query_nym_node_version_history_paged( deps, start_after, limit, )?, ), QueryMsg::Admin {} => to_json_binary( &crate::mixnet_contract_settings::queries::query_admin(deps)?, ), QueryMsg::GetRewardingParams {} => { to_json_binary(&crate::rewards::queries::query_rewarding_params(deps)?) } QueryMsg::GetEpochStatus {} => { to_json_binary(&crate::interval::queries::query_epoch_status(deps)?) } QueryMsg::GetCurrentIntervalDetails {} => to_json_binary( &crate::interval::queries::query_current_interval_details(deps, env)?, ), // mixnode-related: QueryMsg::GetMixNodeBonds { start_after, limit } => to_json_binary( &crate::mixnodes::queries::query_mixnode_bonds_paged(deps, start_after, limit)?, ), QueryMsg::GetMixNodesDetailed { start_after, limit } => to_json_binary( &crate::mixnodes::queries::query_mixnodes_details_paged(deps, start_after, limit)?, ), QueryMsg::GetUnbondedMixNodes { limit, start_after } => to_json_binary( &crate::mixnodes::queries::query_unbonded_mixnodes_paged(deps, start_after, limit)?, ), QueryMsg::GetUnbondedMixNodesByOwner { owner, limit, start_after, } => to_json_binary( &crate::mixnodes::queries::query_unbonded_mixnodes_by_owner_paged( deps, owner, start_after, limit, )?, ), QueryMsg::GetUnbondedMixNodesByIdentityKey { identity_key, limit, start_after, } => to_json_binary( &crate::mixnodes::queries::query_unbonded_mixnodes_by_identity_paged( deps, identity_key, start_after, limit, )?, ), QueryMsg::GetOwnedMixnode { address } => to_json_binary( &crate::mixnodes::queries::query_owned_mixnode(deps, address)?, ), QueryMsg::GetMixnodeDetails { mix_id } => to_json_binary( &crate::mixnodes::queries::query_mixnode_details(deps, mix_id)?, ), QueryMsg::GetMixnodeRewardingDetails { mix_id } => to_json_binary( &crate::mixnodes::queries::query_mixnode_rewarding_details(deps, mix_id)?, ), QueryMsg::GetStakeSaturation { mix_id } => to_json_binary( &crate::mixnodes::queries::query_stake_saturation(deps, mix_id)?, ), QueryMsg::GetUnbondedMixNodeInformation { mix_id } => to_json_binary( &crate::mixnodes::queries::query_unbonded_mixnode(deps, mix_id)?, ), QueryMsg::GetBondedMixnodeDetailsByIdentity { mix_identity } => to_json_binary( &crate::mixnodes::queries::query_mixnode_details_by_identity(deps, mix_identity)?, ), // gateway-related: QueryMsg::GetGateways { limit, start_after } => to_json_binary( &crate::gateways::queries::query_gateways_paged(deps, start_after, limit)?, ), QueryMsg::GetGatewayBond { identity } => to_json_binary( &crate::gateways::queries::query_gateway_bond(deps, identity)?, ), QueryMsg::GetOwnedGateway { address } => to_json_binary( &crate::gateways::queries::query_owned_gateway(deps, address)?, ), QueryMsg::GetPreassignedGatewayIds { limit, start_after } => to_json_binary( &crate::gateways::queries::query_preassigned_ids_paged(deps, start_after, limit)?, ), // nym-node-related: QueryMsg::GetNymNodeBondsPaged { start_after, limit } => to_json_binary( &crate::nodes::queries::query_nymnode_bonds_paged(deps, start_after, limit)?, ), QueryMsg::GetNymNodesDetailedPaged { limit, start_after } => to_json_binary( &crate::nodes::queries::query_nymnodes_details_paged(deps, start_after, limit)?, ), QueryMsg::GetUnbondedNymNode { node_id } => to_json_binary( &crate::nodes::queries::query_unbonded_nymnode(deps, node_id)?, ), QueryMsg::GetUnbondedNymNodesPaged { limit, start_after } => to_json_binary( &crate::nodes::queries::query_unbonded_nymnodes_paged(deps, limit, start_after)?, ), QueryMsg::GetUnbondedNymNodesByOwnerPaged { owner, limit, start_after, } => to_json_binary( &crate::nodes::queries::query_unbonded_nymnodes_by_owner_paged( deps, owner, limit, start_after, )?, ), QueryMsg::GetUnbondedNymNodesByIdentityKeyPaged { identity_key, limit, start_after, } => to_json_binary( &crate::nodes::queries::query_unbonded_nymnodes_by_identity_paged( deps, identity_key, limit, start_after, )?, ), QueryMsg::GetOwnedNymNode { address } => { to_json_binary(&crate::nodes::queries::query_owned_nymnode(deps, address)?) } QueryMsg::GetNymNodeDetails { node_id } => to_json_binary( &crate::nodes::queries::query_nymnode_details(deps, node_id)?, ), QueryMsg::GetNymNodeDetailsByIdentityKey { node_identity } => to_json_binary( &crate::nodes::queries::query_nymnode_details_by_identity(deps, node_identity)?, ), QueryMsg::GetNodeRewardingDetails { node_id } => to_json_binary( &crate::nodes::queries::query_nymnode_rewarding_details(deps, node_id)?, ), QueryMsg::GetNodeStakeSaturation { node_id } => to_json_binary( &crate::nodes::queries::query_stake_saturation(deps, node_id)?, ), QueryMsg::GetRoleAssignment { role } => { to_json_binary(&crate::nodes::queries::query_epoch_assignment(deps, role)?) } QueryMsg::GetRewardedSetMetadata {} => { to_json_binary(&crate::nodes::queries::query_rewarded_set_metadata(deps)?) } // delegation-related: QueryMsg::GetNodeDelegations { node_id, start_after, limit, } => to_json_binary(&crate::delegations::queries::query_node_delegations_paged( deps, node_id, start_after, limit, )?), QueryMsg::GetDelegatorDelegations { delegator, start_after, limit, } => to_json_binary( &crate::delegations::queries::query_delegator_delegations_paged( deps, delegator, start_after, limit, )?, ), QueryMsg::GetDelegationDetails { node_id, delegator, proxy, } => to_json_binary(&crate::delegations::queries::query_node_delegation( deps, node_id, delegator, proxy, )?), QueryMsg::GetAllDelegations { start_after, limit } => to_json_binary( &crate::delegations::queries::query_all_delegations_paged(deps, start_after, limit)?, ), // rewards related QueryMsg::GetPendingOperatorReward { address } => to_json_binary( &crate::rewards::queries::query_pending_operator_reward(deps, address)?, ), QueryMsg::GetPendingNodeOperatorReward { node_id } => to_json_binary( &crate::rewards::queries::query_pending_mixnode_operator_reward(deps, node_id)?, ), QueryMsg::GetPendingDelegatorReward { address, node_id, proxy, } => to_json_binary(&crate::rewards::queries::query_pending_delegator_reward( deps, address, node_id, proxy, )?), QueryMsg::GetEstimatedCurrentEpochOperatorReward { node_id, estimated_performance, estimated_work, } => to_json_binary( &crate::rewards::queries::query_estimated_current_epoch_operator_reward( deps, node_id, estimated_performance, estimated_work, )?, ), QueryMsg::GetEstimatedCurrentEpochDelegatorReward { address, node_id, estimated_performance, estimated_work, } => to_json_binary( &crate::rewards::queries::query_estimated_current_epoch_delegator_reward( deps, address, node_id, estimated_performance, estimated_work, )?, ), // interval-related QueryMsg::GetPendingEpochEvents { limit, start_after } => { to_json_binary(&crate::interval::queries::query_pending_epoch_events_paged( deps, env, start_after, limit, )?) } QueryMsg::GetPendingIntervalEvents { limit, start_after } => to_json_binary( &crate::interval::queries::query_pending_interval_events_paged( deps, env, start_after, limit, )?, ), QueryMsg::GetPendingEpochEvent { event_id } => to_json_binary( &crate::interval::queries::query_pending_epoch_event(deps, event_id)?, ), QueryMsg::GetPendingIntervalEvent { event_id } => to_json_binary( &crate::interval::queries::query_pending_interval_event(deps, event_id)?, ), QueryMsg::GetNumberOfPendingEvents {} => to_json_binary( &crate::interval::queries::query_number_of_pending_events(deps)?, ), // signing-related QueryMsg::GetSigningNonce { address } => to_json_binary( &crate::signing::queries::query_current_signing_nonce(deps, address)?, ), // sphinx key rotation-related QueryMsg::GetKeyRotationState {} => { to_json_binary(&crate::nodes::queries::query_key_rotation_state(deps)?) } QueryMsg::GetKeyRotationId {} => { to_json_binary(&crate::nodes::queries::query_key_rotation_id(deps)?) } }; Ok(query_res?) } #[entry_point] pub fn migrate( mut deps: DepsMut<'_>, _env: Env, msg: MigrateMsg, ) -> Result { set_build_information!(deps.storage)?; cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false); if !skip_state_updates { introduce_key_rotation_id(deps.branch())?; } // due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address // and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh // environment, one of the contracts will HAVE TO go through a migration if let Some(vesting_contract_address) = msg.vesting_contract_address { let mut current_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?; current_state.vesting_contract_address = deps.api.addr_validate(&vesting_contract_address)?; mixnet_params_storage::CONTRACT_STATE.save(deps.storage, ¤t_state)?; } Ok(Default::default()) } #[cfg(test)] mod tests { use super::*; use crate::rewards::storage as rewards_storage; use cosmwasm_std::testing::{message_info, mock_env}; use cosmwasm_std::{Decimal, Uint128}; use mixnet_contract_common::reward_params::{ IntervalRewardParams, RewardedSetParams, RewardingParams, }; use mixnet_contract_common::{ InitialRewardingParams, OperatingCostRange, Percent, ProfitMarginRange, }; use nym_contracts_common_testing::mock_dependencies; use std::time::Duration; #[test] fn initialize_contract() { let mut deps = mock_dependencies(); let env = mock_env(); let init_msg = InstantiateMsg { rewarding_validator_address: deps.api.addr_make("foomp123").to_string(), vesting_contract_address: deps.api.addr_make("bar456").to_string(), rewarding_denom: "uatom".to_string(), epochs_in_interval: 1234, epoch_duration: Duration::from_secs(4321), initial_rewarding_params: InitialRewardingParams { initial_reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(), initial_staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(), staking_supply_scale_factor: Percent::hundred(), sybil_resistance: Percent::from_percentage_value(23).unwrap(), active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(), interval_pool_emission: Percent::from_percentage_value(1).unwrap(), rewarded_set_params: RewardedSetParams { entry_gateways: 123, exit_gateways: 70, mixnodes: 120, standby: 0, }, }, current_nym_node_version: "1.1.10".to_string(), version_score_weights: Default::default(), version_score_params: Default::default(), profit_margin: ProfitMarginRange { minimum: "0.05".parse().unwrap(), maximum: "0.95".parse().unwrap(), }, interval_operating_cost: OperatingCostRange { minimum: "1000".parse().unwrap(), maximum: "10000".parse().unwrap(), }, key_validity_in_epochs: None, }; let sender = message_info(&deps.api.addr_make("sender"), &[]); let res = instantiate(deps.as_mut(), env, sender, init_msg); assert!(res.is_ok()); #[allow(deprecated)] let expected_state = ContractState { owner: Some(deps.api.addr_make("sender")), rewarding_validator_address: deps.api.addr_make("foomp123"), vesting_contract_address: deps.api.addr_make("bar456"), rewarding_denom: "uatom".into(), params: ContractStateParams { delegations_params: DelegationsParams { minimum_delegation: None, }, operators_params: OperatorsParams { minimum_pledge: Coin { denom: "uatom".into(), amount: INITIAL_PLEDGE_AMOUNT, }, profit_margin: ProfitMarginRange { minimum: Percent::from_percentage_value(5).unwrap(), maximum: Percent::from_percentage_value(95).unwrap(), }, interval_operating_cost: OperatingCostRange { minimum: Uint128::new(1000), maximum: Uint128::new(10000), }, }, config_score_params: ConfigScoreParams { version_weights: Default::default(), version_score_formula_params: Default::default(), }, }, }; let expected_epoch_reward_budget = Decimal::from_ratio(100_000_000_000_000u128, 1234u32) * Decimal::percent(1); let expected_stake_saturation_point = Decimal::from_ratio(123_456_000_000_000u128, 313u32); let expected_rewarding_params = RewardingParams { interval: IntervalRewardParams { reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(), staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(), staking_supply_scale_factor: Percent::hundred(), epoch_reward_budget: expected_epoch_reward_budget, stake_saturation_point: expected_stake_saturation_point, sybil_resistance: Percent::from_percentage_value(23).unwrap(), active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(), interval_pool_emission: Percent::from_percentage_value(1).unwrap(), }, rewarded_set: RewardedSetParams { entry_gateways: 123, exit_gateways: 70, mixnodes: 120, standby: 0, }, }; let state = mixnet_params_storage::CONTRACT_STATE .load(deps.as_ref().storage) .unwrap(); assert_eq!(state, expected_state); let rewarding_params = rewards_storage::REWARDING_PARAMS .load(deps.as_ref().storage) .unwrap(); assert_eq!(rewarding_params, expected_rewarding_params); let interval = interval_storage::current_interval(deps.as_ref().storage).unwrap(); assert_eq!(interval.epochs_in_interval(), 1234); assert_eq!(interval.epoch_length(), Duration::from_secs(4321)); assert_eq!(interval.current_interval_id(), 0); assert_eq!(interval.current_epoch_id(), 0); } }