Compare commits

..

4 Commits

Author SHA1 Message Date
Jon Häggblad 67f24b01a5 Make the contract optional 2023-04-19 10:37:59 +02:00
Jon Häggblad 4bde51a367 rustfmt 2023-04-19 09:51:03 +02:00
Jon Häggblad 10cd57e08a Add sp directory contract traits and methods to nyxd client 2023-04-19 09:51:03 +02:00
Jon Häggblad 6e30e6178b Update Cargo.lock files after bumping internal versions during release 2023-04-19 09:37:38 +02:00
106 changed files with 868 additions and 3197 deletions
+1 -2
View File
@@ -30,7 +30,6 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' }}
needs: matrix_prep
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps:
- uses: actions/checkout@v2
@@ -64,4 +63,4 @@ jobs:
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --workspace --all-targets -- -D warnings
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
+1 -1
View File
@@ -64,4 +64,4 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features --all-targets -- -D warnings
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features -- -D warnings
Generated
+1
View File
@@ -4186,6 +4186,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
"nym-service-provider-directory-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"prost 0.10.4",
@@ -18,6 +18,7 @@ nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-c
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" }
nym-vesting-contract = { path = "../../../contracts/vesting" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
@@ -63,6 +64,10 @@ name = "offline_signing"
# (traits would need to be moved around and refactored themselves)
required-features = ["nyxd-client"]
[[example]]
name = "query_service_provider_directory"
required-features = ["nyxd-client"]
[features]
nyxd-client = [
"async-trait",
@@ -0,0 +1,43 @@
use std::str::FromStr;
use cosmrs::AccountId;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use nym_service_provider_directory_common::NymAddress;
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
let client = nym_validator_client::Client::new_query(config).unwrap();
let config = client.nyxd.get_service_config().await.unwrap();
println!("config: {config:?}");
let services_paged = client.nyxd.get_services_paged(None, None).await.unwrap();
println!("services (paged): {services_paged:#?}");
let services = client.nyxd.get_all_services().await.unwrap();
println!("services: {services:#?}");
let announcer = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap();
let services_by_announcer = client
.nyxd
.get_services_by_announcer(announcer)
.await
.unwrap();
println!("services (by announcer): {services_by_announcer:#?}");
let nym_address = NymAddress::new("foo.bar@gateway");
let services_by_nym_address = client
.nyxd
.get_services_by_nym_address(nym_address)
.await
.unwrap();
assert_eq!(services_by_announcer, services_by_nym_address);
let service_info = client.nyxd.get_service_info(1).await;
println!("service info: {service_info:#?}");
}
@@ -117,7 +117,7 @@ async fn test_nyxd_connection(
);
code == 18
}
Ok(Err(error @ NyxdError::NoContractAddressAvailable)) => {
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => {
log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
false
}
@@ -22,7 +22,7 @@ use std::{io, time::Duration};
#[derive(Debug, Error)]
pub enum NyxdError {
#[error("No contract address is available to perform the call")]
NoContractAddressAvailable,
NoContractAddressAvailable(String),
#[error(transparent)]
WalletError(#[from] DirectSecp256k1HdWalletError),
@@ -67,6 +67,7 @@ pub struct Config {
pub(crate) group_contract_address: Option<AccountId>,
pub(crate) multisig_contract_address: Option<AccountId>,
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
pub(crate) service_provider_contract_address: Option<AccountId>,
// TODO: add this in later commits
// pub(crate) gas_price: GasPrice,
}
@@ -131,6 +132,13 @@ impl Config {
details.contracts.coconut_dkg_contract_address.as_ref(),
prefix,
)?,
service_provider_contract_address: Self::parse_optional_account(
details
.contracts
.service_provider_directory_contract_address
.as_ref(),
prefix,
)?,
})
}
}
@@ -246,6 +254,10 @@ impl<C> NyxdClient<C> {
self.config.multisig_contract_address = Some(address);
}
pub fn set_service_provider_contract_address(&mut self, address: AccountId) {
self.config.service_provider_contract_address = Some(address);
}
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
// note: what unwrap is doing here is just moving a failure that would have normally
// occurred in `connect` when attempting to parse an empty address,
@@ -304,6 +316,11 @@ impl<C> NyxdClient<C> {
self.config.coconut_dkg_contract_address.as_ref().unwrap()
}
// The service provider directory contract is optional, so we return an Option not a Result
pub fn service_provider_contract_address(&self) -> Option<&AccountId> {
self.config.service_provider_contract_address.as_ref()
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.simulated_gas_multiplier = multiplier;
}
@@ -24,9 +24,8 @@ use nym_mixnet_contract_common::{
MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse,
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse,
PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse,
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventResponse,
PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse,
QueryMsg as MixnetQueryMsg,
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse,
PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
};
use serde::Deserialize;
@@ -175,16 +174,6 @@ pub trait MixnetQueryClient {
.await
}
async fn get_mixnode_details_by_identity(
&self,
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
async fn get_mixnode_rewarding_details(
&self,
mix_id: MixId,
@@ -385,20 +374,14 @@ pub trait MixnetQueryClient {
.await
}
async fn get_pending_epoch_event(
async fn get_mixnode_details_by_identity(
&self,
event_id: EpochEventId,
) -> Result<PendingEpochEventResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvent { event_id })
.await
}
async fn get_pending_interval_event(
&self,
event_id: IntervalEventId,
) -> Result<PendingIntervalEventResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvent { event_id })
.await
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
async fn get_number_of_pending_events(
@@ -331,38 +331,6 @@ pub trait MixnetSigningClient {
.await
}
async fn decrease_pledge(
&self,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DecreasePledge {
decrease_by: decrease_by.into(),
},
vec![],
)
.await
}
async fn decrease_pledge_on_behalf(
&self,
owner: AccountId,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DecreasePledgeOnBehalf {
owner: owner.to_string(),
decrease_by: decrease_by.into(),
},
vec![],
)
.await
}
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await
@@ -16,15 +16,20 @@ mod mixnet_signing_client;
mod multisig_signing_client;
mod vesting_signing_client;
mod sp_directory_query_client;
mod sp_directory_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use dkg_query_client::DkgQueryClient;
pub use group_query_client::GroupQueryClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use multisig_query_client::MultisigQueryClient;
pub use sp_directory_query_client::SpDirectoryQueryClient;
pub use vesting_query_client::VestingQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use dkg_signing_client::DkgSigningClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use sp_directory_signing_client::SpDirectorySigningClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -0,0 +1,107 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQuery,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, ServiceId, ServiceInfo,
};
use serde::Deserialize;
use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient};
#[async_trait]
pub trait SpDirectoryQueryClient {
async fn query_service_provider_contract<T>(&self, query: SpQuery) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>;
async fn get_service_config(&self) -> Result<ConfigResponse, NyxdError> {
self.query_service_provider_contract(SpQuery::Config {})
.await
}
async fn get_service_info(
&self,
service_id: ServiceId,
) -> Result<ServiceInfoResponse, NyxdError> {
self.query_service_provider_contract(SpQuery::ServiceId { service_id })
.await
}
async fn get_services_paged(
&self,
start_after: Option<ServiceId>,
limit: Option<u32>,
) -> Result<PagedServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQuery::All { limit, start_after })
.await
}
async fn get_services_by_announcer(
&self,
announcer: AccountId,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQuery::ByAnnouncer {
announcer: announcer.to_string(),
})
.await
}
async fn get_services_by_nym_address(
&self,
nym_address: NymAddress,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQuery::ByNymAddress { nym_address })
.await
}
async fn get_sp_contract_version(&self) -> Result<ContractBuildInformation, NyxdError> {
self.query_service_provider_contract(SpQuery::GetContractVersion {})
.await
}
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
let last_id = paged_response.services.last().map(|serv| serv.service_id);
services.append(&mut paged_response.services);
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(services)
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQuery) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&query,
)
.await
}
}
@@ -0,0 +1,111 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_service_provider_directory_common::{
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
};
use crate::nyxd::{
coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient,
SigningCosmWasmClient,
};
#[async_trait]
pub trait SpDirectorySigningClient {
async fn exectute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn announce_service_provider(
&self,
nym_address: NymAddress,
service_type: ServiceType,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
nym_address,
service_type,
},
vec![deposit],
)
.await
}
async fn delete_service_provider_by_id(
&self,
service_id: ServiceId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteId { service_id },
vec![],
)
.await
}
async fn delete_service_provider_by_nym_address(
&self,
nym_address: NymAddress,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteNymAddress { nym_address },
vec![],
)
.await
}
async fn update_deposit_required(
&self,
deposit_required: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::UpdateDepositRequired {
deposit_required: deposit_required.into(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> SpDirectorySigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn exectute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&msg,
fee,
memo,
funds,
)
.await
}
}
@@ -91,21 +91,6 @@ pub trait VestingSigningClient {
.await
}
async fn vesting_decrease_pledge(
&self,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::DecreasePledge {
amount: decrease_by.into(),
},
vec![],
)
.await
}
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError>;
async fn vesting_track_unbond_mixnode(
@@ -6,9 +6,11 @@ use clap::{Args, Subcommand};
pub mod rewards;
pub mod delegate_to_mixnode;
pub mod pledge_more;
pub mod query_for_delegations;
pub mod undelegate_from_mixnode;
pub mod vesting_delegate_to_mixnode;
pub mod vesting_pledge_more;
pub mod vesting_undelegate_from_mixnode;
#[derive(Debug, Args)]
@@ -32,4 +34,8 @@ pub enum MixnetDelegatorsCommands {
DelegateVesting(vesting_delegate_to_mixnode::Args),
/// Undelegate from a mixnode (when originally using locked tokens)
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
/// Pledge more
PledgeMore(pledge_more::Args),
/// Pledge more with locked tokens
PledgeMoreVesting(vesting_pledge_more::Args),
}
@@ -26,7 +26,7 @@ pub struct Args {
pub version: Option<String>,
}
pub async fn vesting_update_config(args: Args, client: SigningClient) {
pub async fn vesting_update_config(client: SigningClient, args: Args) {
info!("Update vesting gateway config!");
let current_details = match client
@@ -45,9 +45,7 @@ pub struct Args {
pub force: bool,
}
pub async fn vesting_bond_gateway(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str) {
info!("Starting vesting gateway bonding!");
// if we're trying to bond less than 1 token
@@ -1,29 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use nym_validator_client::nyxd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub decrease_by: u128,
}
pub async fn decrease_pledge(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting to decrease pledge");
let coin = Coin::new(args.decrease_by, denom);
let res = client
.pledge_more(coin.into(), None)
.await
.expect("failed to decrease pledge!");
info!("decreasing pledge: {:?}", res);
}
@@ -4,17 +4,13 @@
use clap::{Args, Subcommand};
pub mod bond_mixnode;
pub mod decrease_pledge;
pub mod families;
pub mod keys;
pub mod mixnode_bonding_sign_payload;
pub mod pledge_more;
pub mod rewards;
pub mod settings;
pub mod unbond_mixnode;
pub mod vesting_bond_mixnode;
pub mod vesting_decrease_pledge;
pub mod vesting_pledge_more;
pub mod vesting_unbond_mixnode;
#[derive(Debug, Args)]
@@ -44,12 +40,4 @@ pub enum MixnetOperatorsMixnodeCommands {
UnbondVesting(vesting_unbond_mixnode::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateMixnodeBondingSignPayload(mixnode_bonding_sign_payload::Args),
/// Pledge more
PledgeMore(pledge_more::Args),
/// Pledge more with locked tokens
PledgeMoreVesting(vesting_pledge_more::Args),
/// Decrease pledge
DecreasePledge(decrease_pledge::Args),
/// Decrease pledge with locked tokens
DecreasePledgeVesting(vesting_decrease_pledge::Args),
}
@@ -1,29 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use nym_validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub decrease_by: u128,
}
pub async fn vesting_decrease_pledge(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting vesting to decrease pledge");
let coin = Coin::new(args.decrease_by, denom);
let res = client
.vesting_decrease_pledge(coin.into(), None)
.await
.expect("failed to vesting decrease pledge!");
info!("vesting decreasing pledge: {:?}", res);
}
@@ -1,16 +1,13 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{EpochEventId, EpochState, IdentityKey, MixId};
use crate::{EpochState, IdentityKey, MixId};
use contracts_common::signing::verifier::ApiVerifierError;
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use cosmwasm_std::{Addr, Coin, Decimal};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError {
#[error("could not perform contract migration: {comment}")]
FailedMigration { comment: String },
#[error("{source}")]
StdErr {
#[from]
@@ -29,17 +26,6 @@ pub enum MixnetContractError {
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
InsufficientPledge { received: Coin, minimum: Coin },
#[error("Attempted to reduce node pledge ({current}{denom} - {decrease_by}{denom}) below the minimum amount: {minimum}{denom}")]
InvalidPledgeReduction {
current: Uint128,
decrease_by: Uint128,
minimum: Uint128,
denom: String,
},
#[error("A pledge change is already pending in this epoch. The event id: {pending_event_id}")]
PendingPledgeChange { pending_event_id: EpochEventId },
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin },
@@ -204,9 +190,6 @@ pub enum MixnetContractError {
#[error("epoch duration must be > 0")]
EpochDurationZero,
#[error("attempted to perform the operation with 0 coins. This is not allowed")]
ZeroCoinAmount,
#[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")]
RewardingValidatorMismatch {
current_validator: Addr,
@@ -243,11 +226,3 @@ pub enum MixnetContractError {
source: ApiVerifierError,
},
}
impl MixnetContractError {
pub fn inconsistent_state<S: Into<String>>(comment: S) -> Self {
MixnetContractError::InconsistentState {
comment: comment.into(),
}
}
}
@@ -15,8 +15,6 @@ pub enum MixnetEventType {
MixnodeBonding,
PendingPledgeIncrease,
PledgeIncrease,
PendingPledgeDecrease,
PledgeDecrease,
GatewayBonding,
GatewayUnbonding,
PendingMixnodeUnbonding,
@@ -60,8 +58,6 @@ impl ToString for MixnetEventType {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
MixnetEventType::PledgeIncrease => "pledge_increase",
MixnetEventType::PendingPledgeDecrease => "pending_pledge_decrease",
MixnetEventType::PledgeDecrease => "pledge_decrease",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
@@ -358,19 +354,6 @@ pub fn new_pledge_increase_event(created_at: BlockHeight, mix_id: MixId, amount:
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pending_pledge_decrease_event(mix_id: MixId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PendingPledgeDecrease)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pledge_decrease_event(created_at: BlockHeight, mix_id: MixId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PledgeDecrease)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
@@ -3,10 +3,7 @@
use crate::error::MixnetContractError;
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use crate::{
EpochEventId, EpochId, IntervalEventId, IntervalId, MixId, PendingEpochEventData,
PendingIntervalEventData,
};
use crate::{EpochId, IntervalId, MixId};
use cosmwasm_std::{Addr, Env};
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
@@ -531,30 +528,6 @@ impl PendingIntervalEventsResponse {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingEpochEventResponse {
pub event_id: EpochEventId,
pub event: Option<PendingEpochEventData>,
}
impl PendingEpochEventResponse {
pub fn new(event_id: EpochEventId, event: Option<PendingEpochEventData>) -> Self {
PendingEpochEventResponse { event_id, event }
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingIntervalEventResponse {
pub event_id: IntervalEventId,
pub event: Option<PendingIntervalEventData>,
}
impl PendingIntervalEventResponse {
pub fn new(event_id: IntervalEventId, event: Option<PendingIntervalEventData>) -> Self {
PendingIntervalEventResponse { event_id, event }
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct NumberOfPendingEventsResponse {
pub epoch_events: u32,
@@ -32,8 +32,7 @@ pub use gateway::{
};
pub use interval::{
CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse,
PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEventResponse,
PendingIntervalEventsResponse,
PendingEpochEventsResponse, PendingIntervalEventsResponse,
};
pub use mixnode::{
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
@@ -10,7 +10,7 @@ use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -37,20 +37,13 @@ impl RewardedSetNodeStatus {
pub struct MixNodeDetails {
pub bond_information: MixNodeBond,
pub rewarding_details: MixNodeRewarding,
#[serde(default)]
pub pending_changes: PendingMixNodeChanges,
}
impl MixNodeDetails {
pub fn new(
bond_information: MixNodeBond,
rewarding_details: MixNodeRewarding,
pending_changes: PendingMixNodeChanges,
) -> Self {
pub fn new(bond_information: MixNodeBond, rewarding_details: MixNodeRewarding) -> Self {
MixNodeDetails {
bond_information,
rewarding_details,
pending_changes,
}
}
@@ -80,10 +73,6 @@ impl MixNodeDetails {
pub fn total_stake(&self) -> Decimal {
self.rewarding_details.node_bond()
}
pub fn pending_pledge_change(&self) -> Option<EpochEventId> {
self.pending_changes.pledge_change
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
@@ -343,22 +332,6 @@ impl MixNodeRewarding {
Ok(())
}
/// Decreases total pledge of operator by the specified amount.
pub fn decrease_operator_uint128(
&mut self,
amount: Uint128,
) -> Result<(), MixnetContractError> {
let amount_decimal = amount.into_base_decimal()?;
if self.operator < amount_decimal {
return Err(MixnetContractError::OverflowDecimalSubtraction {
minuend: self.operator,
subtrahend: amount_decimal,
});
}
self.operator -= amount_decimal;
Ok(())
}
pub fn increase_delegates_uint128(
&mut self,
amount: Uint128,
@@ -628,25 +601,6 @@ impl From<Layer> for u8 {
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingMixnodeChanges.ts")
)]
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PendingMixNodeChanges {
pub pledge_change: Option<EpochEventId>,
// pub cost_params_change: Option<IntervalEventId>,
}
impl PendingMixNodeChanges {
pub fn new_empty() -> PendingMixNodeChanges {
PendingMixNodeChanges {
pledge_change: None,
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
@@ -10,13 +10,10 @@ use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
};
use crate::{
delegation, ContractStateParams, EpochEventId, IntervalEventId, Layer, LayerAssignment, MixId,
Percent,
};
use crate::{delegation, ContractStateParams, Layer, LayerAssignment, MixId, Percent};
use crate::{Gateway, IdentityKey, MixNode};
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Decimal};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;
@@ -164,13 +161,6 @@ pub enum ExecuteMsg {
PledgeMoreOnBehalf {
owner: String,
},
DecreasePledge {
decrease_by: Coin,
},
DecreasePledgeOnBehalf {
owner: String,
decrease_by: Coin,
},
UnbondMixnode {},
UnbondMixnodeOnBehalf {
owner: String,
@@ -307,10 +297,6 @@ impl ExecuteMsg {
}
ExecuteMsg::PledgeMore {} => "pledging additional tokens".into(),
ExecuteMsg::PledgeMoreOnBehalf { .. } => "pledging additional tokens on behalf".into(),
ExecuteMsg::DecreasePledge { .. } => "decreasing mixnode pledge".into(),
ExecuteMsg::DecreasePledgeOnBehalf { .. } => {
"decreasing mixnode pledge on behalf".into()
}
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
@@ -520,12 +506,6 @@ pub enum QueryMsg {
limit: Option<u32>,
start_after: Option<u32>,
},
GetPendingEpochEvent {
event_id: EpochEventId,
},
GetPendingIntervalEvent {
event_id: IntervalEventId,
},
GetNumberOfPendingEvents {},
// signing-related
@@ -38,10 +38,6 @@ pub enum PendingEpochEventKind {
mix_id: MixId,
amount: Coin,
},
DecreasePledge {
mix_id: MixId,
decrease_by: Coin,
},
UnbondMixnode {
mix_id: MixId,
},
@@ -70,7 +66,7 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingIntervalEvent {
pub id: IntervalEventId,
pub id: EpochEventId,
pub event: PendingIntervalEventData,
}
@@ -40,6 +40,24 @@ impl ExecuteMsg {
pub fn delete_id(service_id: ServiceId) -> Self {
ExecuteMsg::DeleteId { service_id }
}
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Announce {
nym_address,
service_type,
} => format!("announcing {nym_address} as type {service_type}"),
ExecuteMsg::DeleteId { service_id } => {
format!("deleting service with service id {service_id}")
}
ExecuteMsg::DeleteNymAddress { nym_address } => {
format!("deleting service with nym address {nym_address}")
}
ExecuteMsg::UpdateDepositRequired { deposit_required } => {
format!("updating the deposit required to {deposit_required}")
}
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -15,7 +15,6 @@ pub const VESTING_GATEWAY_BONDING_EVENT_TYPE: &str = "vesting_gateway_bonding";
pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding";
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
pub const VESTING_PLEDGE_MORE_EVENT_TYPE: &str = "vesting_pledge_more";
pub const VESTING_DECREASE_PLEDGE_EVENT_TYPE: &str = "vesting_pledge_decrease";
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config";
@@ -23,7 +22,6 @@ pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
"vesting_update_mixnode_cost_params";
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
pub const TRACK_MIXNODE_PLEDGE_DECREASE_EVENT_TYPE: &str = "track_mixnode_pledge_decrease";
pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation";
pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward";
@@ -120,10 +118,6 @@ pub fn new_vesting_pledge_more_event() -> Event {
Event::new(VESTING_PLEDGE_MORE_EVENT_TYPE)
}
pub fn new_vesting_decrease_pledge_event() -> Event {
Event::new(VESTING_DECREASE_PLEDGE_EVENT_TYPE)
}
pub fn new_vesting_update_mixnode_config_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
}
@@ -152,10 +146,6 @@ pub fn new_track_mixnode_unbond_event() -> Event {
Event::new(TRACK_MIXNODE_UNBOND_EVENT_TYPE)
}
pub fn new_track_mixnode_pledge_decrease_event() -> Event {
Event::new(TRACK_MIXNODE_PLEDGE_DECREASE_EVENT_TYPE)
}
pub fn new_track_gateway_unbond_event() -> Event {
Event::new(TRACK_GATEWAY_UNBOND_EVENT_TYPE)
}
@@ -123,18 +123,11 @@ pub enum ExecuteMsg {
PledgeMore {
amount: Coin,
},
DecreasePledge {
amount: Coin,
},
UnbondMixnode {},
TrackUnbondMixnode {
owner: String,
amount: Coin,
},
TrackDecreasePledge {
owner: String,
amount: Coin,
},
BondGateway {
gateway: Gateway,
owner_signature: MessageSignature,
@@ -182,10 +175,8 @@ impl ExecuteMsg {
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore",
ExecuteMsg::DecreasePledge { .. } => "VestingExecuteMsg::DecreasePledge",
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
ExecuteMsg::TrackDecreasePledge { .. } => "VestingExecuteMsg::TrackDecreasePledge",
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
+30 -1
View File
@@ -3,7 +3,12 @@
use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD};
use serde::{Deserialize, Serialize};
use std::{env::var, ops::Not, path::PathBuf};
use std::{
env::{var, VarError},
ffi::OsStr,
ops::Not,
path::PathBuf,
};
use url::Url;
pub mod mainnet;
@@ -28,6 +33,7 @@ pub struct NymContracts {
pub group_contract_address: Option<String>,
pub multisig_contract_address: Option<String>,
pub coconut_dkg_contract_address: Option<String>,
pub service_provider_directory_contract_address: Option<String>,
}
// I wanted to use the simpler `NetworkDetails` name, but there's a clash
@@ -68,6 +74,14 @@ impl NymNetworkDetails {
}
pub fn new_from_env() -> Self {
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
match var(env) {
Ok(var) => Some(var),
Err(VarError::NotPresent) => None,
err => panic!("Unable to set: {:?}", err),
}
}
NymNetworkDetails::new_empty()
.with_bech32_account_prefix(
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
@@ -117,6 +131,9 @@ impl NymNetworkDetails {
.with_coconut_dkg_contract(Some(
var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"),
))
.with_service_provider_directory_contract(get_optional_env(
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
))
}
pub fn new_mainnet() -> Self {
@@ -146,6 +163,9 @@ impl NymNetworkDetails {
coconut_dkg_contract_address: parse_optional_str(
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
),
service_provider_directory_contract_address: parse_optional_str(
mainnet::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
),
},
}
}
@@ -227,6 +247,15 @@ impl NymNetworkDetails {
self.contracts.coconut_dkg_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_service_provider_directory_contract<S: Into<String>>(
mut self,
contract: Option<S>,
) -> Self {
self.contracts.service_provider_directory_contract_address = contract.map(Into::into);
self
}
}
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
+2
View File
@@ -20,6 +20,8 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
+2
View File
@@ -19,6 +19,8 @@ pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
-11
View File
@@ -61,10 +61,6 @@ pub enum PendingEpochEventData {
mix_id: MixId,
amount: DecCoin,
},
DecreasePledge {
mix_id: MixId,
decrease_by: DecCoin,
},
UnbondMixnode {
mix_id: MixId,
},
@@ -105,13 +101,6 @@ impl PendingEpochEventData {
amount: reg.attempt_convert_to_display_dec_coin(amount.into())?,
})
}
MixnetContractPendingEpochEventKind::DecreasePledge {
mix_id,
decrease_by,
} => Ok(PendingEpochEventData::DecreasePledge {
mix_id,
decrease_by: reg.attempt_convert_to_display_dec_coin(decrease_by.into())?,
}),
MixnetContractPendingEpochEventKind::UnbondMixnode { mix_id } => {
Ok(PendingEpochEventData::UnbondMixnode { mix_id })
}
-6
View File
@@ -1,6 +0,0 @@
[alias]
wasm = "build --target wasm32-unknown-unknown"
[build]
rustflags = ["-C", "link-arg=-s"]
#target = "wasm32-unknown-unknown"
-16
View File
@@ -949,22 +949,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "mixnet-vesting-integration-tests"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cosmwasm-storage",
"cw-multi-test",
"nym-contracts-common",
"nym-crypto",
"nym-mixnet-contract",
"nym-mixnet-contract-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"rand_chacha 0.2.2",
]
[[package]]
name = "num-traits"
version = "0.2.15"
-1
View File
@@ -4,7 +4,6 @@ members = [
"coconut-dkg",
"coconut-test",
"mixnet",
"mixnet-vesting-integration-tests",
"multisig/cw3-flex-multisig",
"multisig/cw4-group",
"service-provider-directory",
+2 -2
View File
@@ -86,9 +86,9 @@ mod tests {
assert!(res.is_none());
let mut spend_credential = SpendCredential::new(
funds,
funds.clone(),
blind_serial_number.to_string(),
gateway_cosmos_address,
gateway_cosmos_address.clone(),
);
spend_credential.mark_as_spent();
@@ -20,6 +20,6 @@ pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env, info, msg).unwrap();
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
@@ -173,7 +173,7 @@ mod tests {
);
let info = mock_info("requester", &[coin]);
let tx = deposit_funds(deps.as_mut(), env, info, data).unwrap();
let tx = deposit_funds(deps.as_mut(), env.clone(), info, data).unwrap();
let events: Vec<_> = tx
.events
@@ -246,8 +246,13 @@ mod tests {
deps.querier
.update_balance(env.contract.address.clone(), vec![funds.clone()]);
let err =
release_funds(deps.as_mut(), env, mock_info(invalid_admin, &[]), funds).unwrap_err();
let err = release_funds(
deps.as_mut(),
env.clone(),
mock_info(invalid_admin, &[]),
funds.clone(),
)
.unwrap_err();
assert_eq!(err, ContractError::Admin(AdminError::NotAdmin {}));
}
@@ -289,7 +294,7 @@ mod tests {
{
assert_eq!(contract_addr, MULTISIG_CONTRACT);
assert!(funds.is_empty());
let multisig_msg: MultisigExecuteMsg = from_binary(msg).unwrap();
let multisig_msg: MultisigExecuteMsg = from_binary(&msg).unwrap();
if let MultisigExecuteMsg::Propose {
title: _,
description,
@@ -307,7 +312,7 @@ mod tests {
{
assert_eq!(*contract_addr, env.contract.address.into_string());
assert!(funds.is_empty());
let release_funds_req: ExecuteMsg = from_binary(msg).unwrap();
let release_funds_req: ExecuteMsg = from_binary(&msg).unwrap();
if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req {
assert_eq!(funds, *data.funds());
} else {
+5 -5
View File
@@ -216,7 +216,7 @@ mod tests {
};
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env, info, msg);
let res = instantiate(deps.as_mut(), env.clone(), info, msg);
assert!(res.is_ok())
}
@@ -245,7 +245,7 @@ mod tests {
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
&[],
&vec![],
)
.unwrap();
assert_eq!(parse_node_index(res), (idx + 1) as u64);
@@ -259,7 +259,7 @@ mod tests {
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
&[],
&vec![],
)
.unwrap_err();
assert_eq!(ContractError::AlreadyADealer, err.downcast().unwrap());
@@ -269,13 +269,13 @@ mod tests {
let err = app
.execute_contract(
unauthorized_member,
coconut_dkg_contract_addr,
coconut_dkg_contract_addr.clone(),
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
&[],
&vec![],
)
.unwrap_err();
assert_eq!(ContractError::Unauthorized, err.downcast().unwrap());
@@ -153,9 +153,9 @@ pub(crate) mod tests {
let ret = try_add_dealer(
deps.as_mut(),
info,
bte_key_with_proof,
announce_address,
info.clone(),
bte_key_with_proof.clone(),
announce_address.clone(),
false,
)
.unwrap_err();
@@ -56,11 +56,11 @@ pub(crate) mod tests {
for n in 0..size {
let dealing_share = dealing_bytes_fixture();
let sender = Addr::unchecked(format!("owner{}", n));
(0..TOTAL_DEALINGS).for_each(|idx| {
for idx in 0..TOTAL_DEALINGS {
DEALINGS_BYTES[idx]
.save(deps.storage, &sender, &dealing_share)
.unwrap();
});
}
}
}
@@ -131,7 +131,8 @@ pub(crate) mod tests {
assert!(ret.is_ok());
assert!(dealings.has(deps.as_mut().storage, &owner));
}
let ret = try_commit_dealings(deps.as_mut(), info, dealing_bytes, true).unwrap_err();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true)
.unwrap_err();
assert_eq!(
ret,
ContractError::AlreadyCommitted {
@@ -236,10 +236,10 @@ pub(crate) mod tests {
let limit = *limits.next().unwrap();
{
let mut group_members = GROUP_MEMBERS.lock().unwrap();
for dealer in dealers.iter() {
for i in 0..n as usize {
group_members.push((
Member {
addr: dealer.address.to_string(),
addr: dealers[i].address.to_string(),
weight: 10,
},
1,
@@ -339,7 +339,7 @@ pub(crate) mod tests {
)
.unwrap()
);
for i in 0..3_u64 {
for i in 0..3 as u64 {
let details = dealer_details_fixture(i + 1);
current_dealers()
.save(deps.as_mut().storage, &details.address, &details)
@@ -90,6 +90,6 @@ pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>
};
let env = mock_env();
let info = mock_info(ADMIN_ADDRESS, &[]);
instantiate(deps.as_mut(), env, info, msg).unwrap();
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
@@ -129,8 +129,14 @@ mod tests {
.save(deps.as_mut().storage, &dealer, &dealer_details)
.unwrap();
try_commit_verification_key_share(deps.as_mut(), env, info.clone(), share.clone(), false)
.unwrap();
try_commit_verification_key_share(
deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap();
assert_eq!(
vk_share,
@@ -209,8 +215,14 @@ mod tests {
)
.unwrap();
let ret =
try_commit_verification_key_share(deps.as_mut(), env, info, share, false).unwrap_err();
let ret = try_commit_verification_key_share(
deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap_err();
assert_eq!(
ret,
ContractError::AlreadyCommitted {
@@ -306,7 +318,14 @@ mod tests {
dealers_storage::current_dealers()
.save(deps.as_mut().storage, &owner, &dealer_details)
.unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info, share, false).unwrap();
try_commit_verification_key_share(
deps.as_mut(),
env.clone(),
info.clone(),
share.clone(),
false,
)
.unwrap();
env.block.time = env
.block
@@ -319,6 +338,7 @@ mod tests {
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
advance_epoch_state(deps.as_mut(), env).unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner, false).unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
.unwrap();
}
}
@@ -89,8 +89,13 @@ fn deposit_and_release() {
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
app.execute_contract(Addr::unchecked(multisig_addr), contract_addr, &msg, &[])
.unwrap();
app.execute_contract(
Addr::unchecked(multisig_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap();
let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap();
assert_eq!(pool_bal, deposit_funds[0]);
}
@@ -101,7 +101,7 @@ fn spend_credential_creates_proposal() {
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&[],
&vec![],
)
.unwrap();
let proposal_id = res
@@ -124,7 +124,7 @@ fn spend_credential_creates_proposal() {
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&[],
&vec![],
)
.unwrap_err();
assert_eq!(
@@ -142,9 +142,9 @@ fn spend_credential_creates_proposal() {
let res = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr,
coconut_bandwidth_contract_addr.clone(),
&msg,
&[],
&vec![],
)
.unwrap();
let proposal_id = res
@@ -105,7 +105,7 @@ fn dkg_proposal() {
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
&[],
&vec![],
)
.unwrap();
@@ -115,7 +115,7 @@ fn dkg_proposal() {
Addr::unchecked(OWNER),
coconut_dkg_contract_addr.clone(),
&AdvanceEpochState {},
&[],
&vec![],
)
.unwrap();
}
@@ -132,7 +132,7 @@ fn dkg_proposal() {
Addr::unchecked(MEMBER1),
coconut_dkg_contract_addr.clone(),
&msg,
&[],
&vec![],
)
.unwrap();
@@ -174,7 +174,7 @@ fn dkg_proposal() {
proposal_id,
vote: cw3::Vote::Yes,
},
&[],
&vec![],
)
.unwrap();
@@ -184,16 +184,16 @@ fn dkg_proposal() {
Addr::unchecked(OWNER),
coconut_dkg_contract_addr.clone(),
&AdvanceEpochState {},
&[],
&vec![],
)
.unwrap();
}
app.execute_contract(
Addr::unchecked(MEMBER1),
multisig_contract_addr,
multisig_contract_addr.clone(),
&Execute { proposal_id },
&[],
&vec![],
)
.unwrap();
@@ -1,32 +0,0 @@
[package]
name = "mixnet-vesting-integration-tests"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# cosmwasm dependencies
cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
cw-multi-test = { workspace = true }
# contracts dependencies
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract = { path = "../mixnet" }
nym-vesting-contract = { path = "../vesting" }
# other local dependencies
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
# external dependencies
rand_chacha = "0.2"
[[test]]
name = "mixnet-vesting-test"
path = "src/tests.rs"
@@ -1,240 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::helpers::{mix_coin, mix_coins, vesting_owner};
use crate::support::setup::{TestSetup, MIX_DENOM};
use cosmwasm_std::Addr;
use cw_multi_test::Executor;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::error::MixnetContractError;
use nym_mixnet_contract_common::{ContractStateParams, MixNodeCostParams};
use nym_mixnet_contract_common::{MixOwnershipResponse, QueryMsg as MixnetQueryMsg};
use nym_vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
use vesting_contract::errors::ContractError as VestingContractError;
#[test]
fn decrease_mixnode_pledge_from_vesting_account_with_minimum_pledge() {
let mut test = TestSetup::new_simple();
let vesting_account = "vesting-account";
// 0. get the minimum pledge amount
let state_params: ContractStateParams = test
.app
.wrap()
.query_wasm_smart(test.mixnet_contract(), &MixnetQueryMsg::GetStateParams {})
.unwrap();
let minimum_pledge = state_params.minimum_mixnode_pledge;
// 1. create vesting account
let create_msg = VestingExecuteMsg::CreateAccount {
owner_address: vesting_account.to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
test.app
.execute_contract(
vesting_owner(),
test.vesting_contract(),
&create_msg,
&mix_coins(1_000_000_000),
)
.unwrap();
// 2. bond mixnode with the vesting account
let pledge = minimum_pledge.clone();
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: mix_coin(40_000_000),
};
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
vesting_account,
Some(test.vesting_contract()),
cost_params.clone(),
pledge.clone(),
);
let bond_msg = VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount: pledge.clone(),
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&bond_msg,
&[],
)
.unwrap();
// 3. try to decrease the pledge
// trying to decrease by a zero amount - not valid
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: mix_coin(0),
};
let res_zero = test
.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap_err();
assert_eq!(
VestingContractError::EmptyFunds,
res_zero.downcast().unwrap()
);
// trying to go below the cap - also not valid
let amount = mix_coin(50_000);
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: amount.clone(),
};
let res_below = test
.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap_err();
assert_eq!(
MixnetContractError::InvalidPledgeReduction {
current: pledge.amount,
decrease_by: amount.amount,
minimum: minimum_pledge.amount,
denom: minimum_pledge.denom
},
res_below.downcast().unwrap()
)
}
#[test]
fn decrease_mixnode_pledge_from_vesting_account_with_sufficient_pledge() {
let mut test = TestSetup::new_simple();
let vesting_account = "vesting-account";
// 1. create vesting account
let create_msg = VestingExecuteMsg::CreateAccount {
owner_address: vesting_account.to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
test.app
.execute_contract(
vesting_owner(),
test.vesting_contract(),
&create_msg,
&mix_coins(10_000_000_000),
)
.unwrap();
// 2. bond mixnode with the vesting account
let pledge = mix_coin(150_000_000);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: mix_coin(40_000_000),
};
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
vesting_account,
Some(test.vesting_contract()),
cost_params.clone(),
pledge.clone(),
);
let bond_msg = VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount: pledge,
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&bond_msg,
&[],
)
.unwrap();
// 3. try to decrease the pledge
let before: MixOwnershipResponse = test
.app
.wrap()
.query_wasm_smart(
test.mixnet_contract(),
&MixnetQueryMsg::GetOwnedMixnode {
address: vesting_account.to_string(),
},
)
.unwrap();
let balance_before = test
.app
.wrap()
.query_balance(test.vesting_contract(), MIX_DENOM)
.unwrap();
assert_eq!(balance_before.amount.u128(), 9_850_000_000);
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: mix_coin(50_000_000),
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap();
let after_decrease: MixOwnershipResponse = test
.app
.wrap()
.query_wasm_smart(
test.mixnet_contract(),
&MixnetQueryMsg::GetOwnedMixnode {
address: vesting_account.to_string(),
},
)
.unwrap();
// note: nothing has changed with the pledge because the event hasn't been resolved yet!
assert_eq!(before.address, after_decrease.address);
let before_details = before.mixnode_details.unwrap();
let after_details = after_decrease.mixnode_details.unwrap();
assert_eq!(
before_details.rewarding_details,
after_details.rewarding_details
);
assert_eq!(
before_details.bond_information,
after_details.bond_information
);
// but we have the pending change saved now!
assert!(before_details.pending_changes.pledge_change.is_none());
assert_eq!(Some(1), after_details.pending_changes.pledge_change);
// 4. resolve events
test.advance_mixnet_epoch();
let balance_after = test
.app
.wrap()
.query_balance(test.vesting_contract(), MIX_DENOM)
.unwrap();
assert_eq!(balance_after.amount.u128(), 9_900_000_000);
}
@@ -1,28 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::setup::{MIX_DENOM, REWARDING_VALIDATOR};
use cosmwasm_std::Decimal;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::InitialRewardingParams;
use std::time::Duration;
pub fn default_mixnet_init_msg() -> nym_mixnet_contract_common::InstantiateMsg {
nym_mixnet_contract_common::InstantiateMsg {
rewarding_validator_address: REWARDING_VALIDATOR.to_string(),
vesting_contract_address: "placeholder".to_string(),
rewarding_denom: MIX_DENOM.to_string(),
epochs_in_interval: 720,
epoch_duration: Duration::from_secs(60 * 60),
initial_rewarding_params: InitialRewardingParams {
initial_reward_pool: Decimal::from_atomics(250_000_000_000_000u128, 0).unwrap(),
initial_staking_supply: Decimal::from_atomics(223_000_000_000_000u128, 0).unwrap(),
staking_supply_scale_factor: Percent::hundred(),
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
interval_pool_emission: Percent::from_percentage_value(2).unwrap(),
rewarded_set_size: 240,
active_set_size: 100,
},
}
}
@@ -1,56 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::setup::{MIXNET_OWNER, MIX_DENOM, REWARDING_VALIDATOR, VESTING_OWNER};
use cosmwasm_std::{coin, coins, Addr, Coin, Empty};
use cw_multi_test::{Contract, ContractWrapper};
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha20Rng;
#[allow(unused)]
pub fn mixnet_owner() -> Addr {
Addr::unchecked(MIXNET_OWNER)
}
pub fn vesting_owner() -> Addr {
Addr::unchecked(VESTING_OWNER)
}
pub fn rewarding_validator() -> Addr {
Addr::unchecked(REWARDING_VALIDATOR)
}
pub fn mix_coins(amount: u128) -> Vec<Coin> {
coins(amount, MIX_DENOM)
}
pub fn mix_coin(amount: u128) -> Coin {
coin(amount, MIX_DENOM)
}
pub fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
ChaCha20Rng::from_seed(dummy_seed)
}
pub fn mixnet_contract_wrapper() -> Box<dyn Contract<Empty>> {
Box::new(
ContractWrapper::new(
mixnet_contract::contract::execute,
mixnet_contract::contract::instantiate,
mixnet_contract::contract::query,
)
.with_migrate(mixnet_contract::contract::migrate),
)
}
pub fn vesting_contract_wrapper() -> Box<dyn Contract<Empty>> {
Box::new(
ContractWrapper::new(
vesting_contract::contract::execute,
vesting_contract::contract::instantiate,
vesting_contract::contract::query,
)
.with_migrate(vesting_contract::contract::migrate),
)
}
@@ -1,6 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod fixtures;
pub mod helpers;
pub mod setup;
@@ -1,328 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::fixtures;
use crate::support::helpers::{
mixnet_contract_wrapper, rewarding_validator, test_rng, vesting_contract_wrapper,
};
use cosmwasm_std::{coins, Addr, Coin, Timestamp};
use cw_multi_test::{App, AppBuilder, Executor};
use nym_contracts_common::signing::{ContractMessageContent, MessageSignature, Nonce};
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{
CurrentIntervalResponse, LayerAssignment, MixNodeCostParams, MixnodeBondingPayload,
PagedRewardedSetResponse, RewardingParams, SignableMixNodeBondingMsg,
};
use nym_mixnet_contract_common::{
ExecuteMsg as MixnetExecuteMsg, MixNode, QueryMsg as MixnetQueryMsg,
};
use rand_chacha::ChaCha20Rng;
use std::collections::HashMap;
// our global accounts that should always get some coins at the start
pub const MIXNET_OWNER: &str = "mixnet-owner";
pub const VESTING_OWNER: &str = "vesting-owner";
pub const REWARDING_VALIDATOR: &str = "rewarding-validator";
pub const MIX_DENOM: &str = "unym";
pub struct ContractInstantiationResult {
mixnet_contract_address: Addr,
vesting_contract_address: Addr,
}
#[allow(unused)]
pub struct TestSetupBuilder {
mixnet_init_msg: nym_mixnet_contract_common::InstantiateMsg,
initial_balances: HashMap<Addr, Vec<Coin>>,
}
#[allow(unused)]
impl TestSetupBuilder {
pub fn new() -> Self {
TestSetupBuilder {
mixnet_init_msg: fixtures::default_mixnet_init_msg(),
initial_balances: Default::default(),
}
}
pub fn with_mixnet_init_msg(
mut self,
mixnet_init_msg: nym_mixnet_contract_common::InstantiateMsg,
) -> Self {
self.mixnet_init_msg = mixnet_init_msg;
self
}
pub fn with_initial_balances(mut self, initial_balances: HashMap<Addr, Vec<Coin>>) -> Self {
self.initial_balances = initial_balances;
self
}
pub fn with_initial_balance(mut self, addr: impl Into<String>, balance: Vec<Coin>) -> Self {
self.initial_balances.insert(Addr::unchecked(addr), balance);
self
}
pub fn build(self) -> TestSetup {
TestSetup::new(self.initial_balances, self.mixnet_init_msg)
}
}
pub struct TestSetup {
pub app: App,
pub rng: ChaCha20Rng,
pub mixnet_contract: Addr,
pub vesting_contract: Addr,
}
impl TestSetup {
pub fn new_simple() -> Self {
TestSetup::new(Default::default(), fixtures::default_mixnet_init_msg())
}
pub fn new(
initial_balances: HashMap<Addr, Vec<Coin>>,
custom_mixnet_init: nym_mixnet_contract_common::InstantiateMsg,
) -> Self {
let (app, contracts) = instantiate_contracts(initial_balances, Some(custom_mixnet_init));
TestSetup {
app,
rng: test_rng(),
mixnet_contract: contracts.mixnet_contract_address,
vesting_contract: contracts.vesting_contract_address,
}
}
pub fn mixnet_contract(&self) -> Addr {
self.mixnet_contract.clone()
}
pub fn vesting_contract(&self) -> Addr {
self.vesting_contract.clone()
}
pub fn skip_to_current_epoch_end(&mut self) {
let current_interval: CurrentIntervalResponse = self
.app
.wrap()
.query_wasm_smart(
self.mixnet_contract(),
&MixnetQueryMsg::GetCurrentIntervalDetails {},
)
.unwrap();
let epoch_end = current_interval.interval.current_epoch_end_unix_timestamp();
self.app.update_block(|current_block| {
// skip few blocks just in case
current_block.height += 10;
current_block.time = Timestamp::from_seconds(epoch_end as u64)
})
}
pub fn full_mixnet_epoch_operations(&mut self) {
let current_rewarded_set: PagedRewardedSetResponse = self
.app
.wrap()
.query_wasm_smart(
self.mixnet_contract(),
&MixnetQueryMsg::GetRewardedSet {
limit: Some(9999),
start_after: None,
},
)
.unwrap();
let current_params: RewardingParams = self
.app
.wrap()
.query_wasm_smart(
self.mixnet_contract(),
&MixnetQueryMsg::GetRewardingParams {},
)
.unwrap();
// TODO: handle paging
// begin epoch transition
self.app
.execute_contract(
rewarding_validator(),
self.mixnet_contract(),
&MixnetExecuteMsg::BeginEpochTransition {},
&[],
)
.unwrap();
// reward
for (mix_id, _status) in &current_rewarded_set.nodes {
self.app
.execute_contract(
rewarding_validator(),
self.mixnet_contract(),
&MixnetExecuteMsg::RewardMixnode {
mix_id: *mix_id,
performance: Performance::hundred(),
},
&[],
)
.unwrap();
}
// events
self.app
.execute_contract(
rewarding_validator(),
self.mixnet_contract(),
&MixnetExecuteMsg::ReconcileEpochEvents { limit: None },
&[],
)
.unwrap();
// don't bother changing the active set, use the same node for update and advance
let new_rewarded_set = current_rewarded_set
.nodes
.into_iter()
.enumerate()
.map(|(i, (node, _))| {
LayerAssignment::new(node, ((i as u8 % 3) + 1).try_into().unwrap())
})
.collect();
self.app
.execute_contract(
rewarding_validator(),
self.mixnet_contract(),
&MixnetExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size: current_params.active_set_size,
},
&[],
)
.unwrap();
}
pub fn advance_mixnet_epoch(&mut self) {
self.skip_to_current_epoch_end();
self.full_mixnet_epoch_operations();
}
pub fn valid_mixnode_with_sig(
&mut self,
owner: &str,
proxy: Option<Addr>,
cost_params: MixNodeCostParams,
stake: Coin,
) -> (MixNode, MessageSignature) {
let signing_nonce: Nonce = self
.app
.wrap()
.query_wasm_smart(
self.mixnet_contract(),
&MixnetQueryMsg::GetSigningNonce {
address: owner.to_string(),
},
)
.unwrap();
let keypair = identity::KeyPair::new(&mut self.rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let mixnode = MixNode {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
host: "mix.node.org".to_string(),
mix_port: 1789,
verloc_port: 1790,
http_api_port: 8000,
version: "1.1.14".to_string(),
};
let payload = MixnodeBondingPayload::new(mixnode.clone(), cost_params);
let content =
ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload);
let sign_payload = SignableMixNodeBondingMsg::new(signing_nonce, content);
let plaintext = sign_payload.to_plaintext().unwrap();
let signature = keypair.private_key().sign(&plaintext);
let msg_signature = MessageSignature::from(signature.to_bytes().as_ref());
(mixnode, msg_signature)
}
}
pub fn instantiate_contracts(
mut initial_funds: HashMap<Addr, Vec<Coin>>,
custom_mixnet_init: Option<nym_mixnet_contract_common::InstantiateMsg>,
) -> (App, ContractInstantiationResult) {
// add our global addresses to the map
initial_funds.insert(
Addr::unchecked(MIXNET_OWNER),
coins(100_000_000_000, MIX_DENOM),
);
initial_funds.insert(
Addr::unchecked(VESTING_OWNER),
coins(100_000_000_000, MIX_DENOM),
);
initial_funds.insert(
Addr::unchecked(REWARDING_VALIDATOR),
coins(1_000_000_000_000, MIX_DENOM),
);
let mut app = AppBuilder::new().build(|router, _api, storage| {
for (addr, funds) in initial_funds {
router
.bank
.init_balance(storage, &addr, funds.clone())
.unwrap()
}
});
let mixnet_code_id = app.store_code(mixnet_contract_wrapper());
let vesting_code_id = app.store_code(vesting_contract_wrapper());
let mixnet_contract_address = app
.instantiate_contract(
mixnet_code_id,
Addr::unchecked(MIXNET_OWNER),
&custom_mixnet_init.unwrap_or(fixtures::default_mixnet_init_msg()),
&[],
"mixnet-contract",
Some(MIXNET_OWNER.to_string()),
)
.unwrap();
let vesting_contract_address = app
.instantiate_contract(
vesting_code_id,
Addr::unchecked(VESTING_OWNER),
&nym_vesting_contract_common::InitMsg {
mixnet_contract_address: mixnet_contract_address.to_string(),
mix_denom: MIX_DENOM.to_string(),
},
&[],
"vesting-contract",
Some(VESTING_OWNER.to_string()),
)
.unwrap();
// now fix up vesting contract address...
app.migrate_contract(
Addr::unchecked(MIXNET_OWNER),
mixnet_contract_address.clone(),
&nym_mixnet_contract_common::MigrateMsg {
vesting_contract_address: Some(vesting_contract_address.to_string()),
},
mixnet_code_id,
)
.unwrap();
(
app,
ContractInstantiationResult {
mixnet_contract_address,
vesting_contract_address,
},
)
}
@@ -1,5 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod decrease_mixnode_pledge;
mod support;
-2
View File
@@ -1,2 +0,0 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
+1 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Uint128;
@@ -61,7 +61,6 @@ pub const CONTRACT_STATE_KEY: &str = "state";
pub const LAYER_DISTRIBUTION_KEY: &str = "layers";
pub const NODE_ID_COUNTER_KEY: &str = "nic";
pub const PENDING_MIXNODE_CHANGES_NAMESPACE: &str = "pmc";
pub const MIXNODES_PK_NAMESPACE: &str = "mnn";
pub const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
pub const MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "mni";
+1 -20
View File
@@ -251,18 +251,6 @@ pub fn execute(
ExecuteMsg::PledgeMoreOnBehalf { owner } => {
crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, owner)
}
ExecuteMsg::DecreasePledge { decrease_by } => {
crate::mixnodes::transactions::try_decrease_pledge(deps, env, info, decrease_by)
}
ExecuteMsg::DecreasePledgeOnBehalf { owner, decrease_by } => {
crate::mixnodes::transactions::try_decrease_pledge_on_behalf(
deps,
env,
info,
decrease_by,
owner,
)
}
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
}
@@ -585,12 +573,6 @@ pub fn query(
limit,
)?,
),
QueryMsg::GetPendingEpochEvent { event_id } => to_binary(
&crate::interval::queries::query_pending_epoch_event(deps, event_id)?,
),
QueryMsg::GetPendingIntervalEvent { event_id } => to_binary(
&crate::interval::queries::query_pending_interval_event(deps, event_id)?,
),
QueryMsg::GetNumberOfPendingEvents {} => to_binary(
&crate::interval::queries::query_number_of_pending_events(deps)?,
),
@@ -604,7 +586,7 @@ pub fn query(
#[entry_point]
pub fn migrate(
mut deps: DepsMut<'_>,
deps: DepsMut<'_>,
_env: Env,
msg: MigrateMsg,
) -> Result<Response, MixnetContractError> {
@@ -630,7 +612,6 @@ pub fn migrate(
// If state structure changed in any contract version in the way migration is needed, it
// should occur here, for example anything from `crate::queued_migrations::`
crate::queued_migrations::insert_pending_pledge_changes(deps.branch())?;
}
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
@@ -489,7 +489,7 @@ mod test {
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
new_member.to_string(),
family_head,
family_head.clone(),
)
.unwrap_err();
@@ -362,7 +362,7 @@ pub mod tests {
let res = try_add_gateway(
test.deps_mut(),
env.clone(),
info,
info.clone(),
gateway.clone(),
signature.clone(),
);
+53 -600
View File
@@ -1,22 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Addr, Coin, DepsMut, Env, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_active_set_update_event, new_delegation_event, new_delegation_on_unbonded_node_event,
new_mixnode_cost_params_update_event, new_mixnode_unbonding_event, new_pledge_decrease_event,
new_pledge_increase_event, new_rewarding_params_update_event, new_undelegation_event,
};
use mixnet_contract_common::mixnode::MixNodeCostParams;
use mixnet_contract_common::pending_events::{
PendingEpochEventData, PendingEpochEventKind, PendingIntervalEventData,
PendingIntervalEventKind,
};
use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate;
use mixnet_contract_common::{BlockHeight, Delegation, MixId};
use crate::delegations;
use crate::delegations::storage as delegations_storage;
use crate::interval::helpers::change_interval_config;
@@ -25,6 +9,20 @@ use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::storage as rewards_storage;
use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking};
use cosmwasm_std::{Addr, Coin, DepsMut, Env, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_active_set_update_event, new_delegation_event, new_delegation_on_unbonded_node_event,
new_mixnode_cost_params_update_event, new_mixnode_unbonding_event, new_pledge_increase_event,
new_rewarding_params_update_event, new_undelegation_event,
};
use mixnet_contract_common::mixnode::MixNodeCostParams;
use mixnet_contract_common::pending_events::{
PendingEpochEventData, PendingEpochEventKind, PendingIntervalEventData,
PendingIntervalEventKind,
};
use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate;
use mixnet_contract_common::{BlockHeight, Delegation, MixId};
pub(crate) trait ContractExecutableEvent {
// note: the error only means a HARD error like we failed to read from storage.
@@ -148,9 +146,10 @@ pub(crate) fn undelegate(
Some(delegation) => delegation,
};
let mix_rewarding =
rewards_storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::inconsistent_state(
"mixnode rewarding got removed from the storage whilst there's still an existing delegation",
))?;
rewards_storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::InconsistentState {
comment: "mixnode rewarding got removed from the storage whilst there's still an existing delegation"
.into(),
})?;
// this also appropriately adjusts the storage
let tokens_to_return =
delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?;
@@ -181,15 +180,11 @@ pub(crate) fn unbond_mixnode(
// in unbonding state and thus nothing could have been done to it (such as attempting to double unbond it)
// thus the node with all its associated information MUST exist in the storage.
let node_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
MixnetContractError::inconsistent_state(
"mixnode getting processed to get unbonded doesn't exist in the storage",
),
MixnetContractError::InconsistentState {
comment: "mixnode getting processed to get unbonded doesn't exist in the storage"
.into(),
},
)?;
if node_details.pending_changes.pledge_change.is_some() {
return Err(MixnetContractError::inconsistent_state(
"attempted to unbond mixnode while there are associated pending pledge changes",
));
}
// the denom on the original pledge was validated at the time of bonding so we can safely reuse it here
let rewarding_denom = &node_details.bond_information.original_pledge.denom;
@@ -249,15 +244,12 @@ pub(crate) fn increase_pledge(
// the target node MUST exist - we have checked it at the time of putting this event onto the queue
// we have also verified there were no preceding unbond events
let mix_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
MixnetContractError::inconsistent_state(
"mixnode getting processed to increase its pledge doesn't exist in the storage",
),
MixnetContractError::InconsistentState {
comment:
"mixnode getting processed to increase its pledge doesn't exist in the storage"
.into(),
},
)?;
if mix_details.pending_changes.pledge_change.is_none() {
return Err(MixnetContractError::inconsistent_state(
"attempted to increase mixnode pledge while there are no associated pending changes",
));
}
let mut updated_bond = mix_details.bond_information.clone();
let mut updated_rewarding = mix_details.rewarding_details;
@@ -265,10 +257,7 @@ pub(crate) fn increase_pledge(
updated_bond.original_pledge.amount += increase.amount;
updated_rewarding.increase_operator_uint128(increase.amount)?;
let mut pending_changes = mix_details.pending_changes;
pending_changes.pledge_change = None;
// update all: bond information, rewarding details and pending pledge changes
// update both, bond information and rewarding details
mixnodes_storage::mixnode_bonds().replace(
deps.storage,
mix_id,
@@ -276,70 +265,10 @@ pub(crate) fn increase_pledge(
Some(&mix_details.bond_information),
)?;
rewards_storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &updated_rewarding)?;
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
Ok(Response::new().add_event(new_pledge_increase_event(created_at, mix_id, &increase)))
}
pub(crate) fn decrease_pledge(
deps: DepsMut<'_>,
created_at: BlockHeight,
mix_id: MixId,
decrease_by: Coin,
) -> Result<Response, MixnetContractError> {
// the target node MUST exist - we have checked it at the time of putting this event onto the queue
// we have also verified there were no preceding unbond events
let mix_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
MixnetContractError::inconsistent_state(
"mixnode getting processed to increase its pledge doesn't exist in the storage",
),
)?;
if mix_details.pending_changes.pledge_change.is_none() {
return Err(MixnetContractError::inconsistent_state(
"attempted to decrease mixnode pledge while there are no associated pending changes",
));
}
let mut updated_bond = mix_details.bond_information.clone();
let mut updated_rewarding = mix_details.rewarding_details;
let mut pending_changes = mix_details.pending_changes;
pending_changes.pledge_change = None;
// SAFETY: the subtraction here can't overflow as before the event was pushed into the queue,
// we checked that the new value will be higher than minimum pledge (which is also strictly positive)
updated_bond.original_pledge.amount -= decrease_by.amount;
updated_rewarding.decrease_operator_uint128(decrease_by.amount)?;
let proxy = &mix_details.bond_information.proxy;
let owner = &mix_details.bond_information.owner;
// send the removed tokens back to the operator
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![decrease_by.clone()]);
// update all: bond information, rewarding details and pending pledge changes
mixnodes_storage::mixnode_bonds().replace(
deps.storage,
mix_id,
Some(&updated_bond),
Some(&mix_details.bond_information),
)?;
rewards_storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &updated_rewarding)?;
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
let response = Response::new()
.add_message(return_tokens)
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by))
.maybe_add_track_vesting_decrease_mixnode_pledge(
deps.storage,
proxy.clone(),
owner.clone().to_string(),
decrease_by,
)?;
Ok(response)
}
impl ContractExecutableEvent for PendingEpochEventData {
fn execute(self, deps: DepsMut<'_>, env: &Env) -> Result<Response, MixnetContractError> {
// note that the basic validation on all those events was already performed before
@@ -359,10 +288,6 @@ impl ContractExecutableEvent for PendingEpochEventData {
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
increase_pledge(deps, self.created_at, mix_id, amount)
}
PendingEpochEventKind::DecreasePledge {
mix_id,
decrease_by,
} => decrease_pledge(deps, self.created_at, mix_id, decrease_by),
PendingEpochEventKind::UnbondMixnode { mix_id } => {
unbond_mixnode(deps, env, self.created_at, mix_id)
}
@@ -472,33 +397,26 @@ impl ContractExecutableEvent for PendingIntervalEventData {
#[cfg(test)]
mod tests {
use std::time::Duration;
use cosmwasm_std::Decimal;
use mixnet_contract_common::Percent;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use super::*;
use crate::support::tests::test_helpers;
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
use super::*;
use cosmwasm_std::Decimal;
use mixnet_contract_common::Percent;
use std::time::Duration;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
// note that authorization and basic validation has already been performed for all of those
// before being pushed onto the event queues
#[cfg(test)]
mod delegating {
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, to_binary, CosmosMsg, Decimal, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
use crate::mixnodes::transactions::try_remove_mixnode;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use super::*;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, to_binary, CosmosMsg, Decimal, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn returns_the_tokens_if_mixnode_has_unbonded() {
@@ -911,7 +829,7 @@ mod tests {
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
vesting_contract
}
);
}
@@ -919,14 +837,11 @@ mod tests {
#[cfg(test)]
mod undelegating {
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use super::*;
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn doesnt_return_any_tokens_if_it_doesnt_exist() {
@@ -1106,7 +1021,7 @@ mod tests {
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
vesting_contract
}
);
}
@@ -1114,17 +1029,13 @@ mod tests {
#[cfg(test)]
mod mixnode_unbonding {
use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::transactions::{_try_decrease_pledge, _try_increase_pledge};
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use super::*;
use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::mixnode::UnbondedMixnode;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn returns_hard_error_if_mixnode_doesnt_exist() {
@@ -1139,71 +1050,6 @@ mod tests {
));
}
#[test]
fn returns_hard_error_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
let env = test.env();
let change = test.coins(1234);
// increase
let owner = "mix-owner1";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
_try_increase_pledge(
test.deps_mut(),
env.clone(),
change.clone(),
Addr::unchecked(owner),
None,
)
.unwrap();
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
// decrease
let owner = "mix-owner2";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
_try_decrease_pledge(
test.deps_mut(),
env.clone(),
change[0].clone(),
Addr::unchecked(owner),
None,
)
.unwrap();
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
// artificial
let owner = "mix-owner3";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
let changes = PendingMixNodeChanges {
pledge_change: Some(1234),
};
mixnodes_storage::PENDING_MIXNODE_CHANGES
.save(test.deps_mut().storage, mix_id, &changes)
.unwrap();
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
}
#[test]
fn returns_original_pledge_alongside_any_earned_rewards() {
let mut test = TestSetup::new();
@@ -1332,7 +1178,7 @@ mod tests {
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
vesting_contract
}
);
}
@@ -1340,11 +1186,9 @@ mod tests {
#[cfg(test)]
mod increasing_pledge {
use cosmwasm_std::Uint128;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
use cosmwasm_std::Uint128;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn returns_hard_error_if_mixnode_doesnt_exist() {
@@ -1359,27 +1203,10 @@ mod tests {
));
}
#[test]
fn returns_hard_error_if_there_are_no_pending_pledge_changes() {
let mut test = TestSetup::new();
let change = test.coin(1234);
let owner = "mix-owner";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
let res = increase_pledge(test.deps_mut(), 123, mix_id, change);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
}
#[test]
fn updates_stored_bond_information_and_rewarding_details() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.set_pending_pledge_change(mix_id, None);
let old_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
.unwrap()
@@ -1412,8 +1239,6 @@ mod tests {
let pledge3 = Uint128::new(200_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
let increase = test.coin(pledge2.u128());
increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap();
@@ -1449,7 +1274,6 @@ mod tests {
let pledge2 = Uint128::new(50_000_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
@@ -1522,7 +1346,6 @@ mod tests {
let pledge2 = Uint128::new(50_000_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
@@ -1597,373 +1420,6 @@ mod tests {
assert_eq!(dist1, dist2)
}
}
#[test]
fn updates_the_pending_pledge_changes_field() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.set_pending_pledge_change(mix_id, None);
let amount = test.coin(12345);
increase_pledge(test.deps_mut(), 123, mix_id, amount).unwrap();
let pending = mixnodes_storage::PENDING_MIXNODE_CHANGES
.load(test.deps().storage, mix_id)
.unwrap();
assert!(pending.pledge_change.is_none())
}
}
#[cfg(test)]
mod decreasing_pledge {
use cosmwasm_std::{to_binary, BankMsg, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
#[test]
fn returns_hard_error_if_mixnode_doesnt_exist() {
// this should have never happened so hard error MUST be thrown here
let mut test = TestSetup::new();
let amount = test.coin(123);
let res = decrease_pledge(test.deps_mut(), 123, 1, amount);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
}
#[test]
fn returns_hard_error_if_there_are_no_pending_pledge_changes() {
let mut test = TestSetup::new();
let change = test.coin(1234);
let owner = "mix-owner";
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
let res = decrease_pledge(test.deps_mut(), 123, mix_id, change);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
));
}
#[test]
fn updates_stored_bond_information_and_rewarding_details() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.set_pending_pledge_change(mix_id, None);
let old_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
.unwrap()
.unwrap();
let amount = test.coin(12345);
decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
let updated_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
.unwrap()
.unwrap();
assert_eq!(
updated_details.bond_information.original_pledge.amount,
old_details.bond_information.original_pledge.amount - amount.amount
);
assert_eq!(
updated_details.rewarding_details.operator,
old_details.rewarding_details.operator
- Decimal::from_atomics(amount.amount, 0).unwrap()
);
}
#[test]
fn returns_tokens_back_to_the_owner() {
let mut test = TestSetup::new();
let owner = "mix-owner";
let mix_id = test.add_dummy_mixnode(owner, None);
test.set_pending_pledge_change(mix_id, None);
let amount = test.coin(12345);
let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
assert_eq!(res.messages.len(), 1);
assert_eq!(
res.messages[0].msg,
CosmosMsg::Bank(BankMsg::Send {
to_address: owner.to_string(),
amount: vec![amount],
})
)
}
#[test]
fn returns_tokens_back_to_the_proxy_if_bonded_with_vesting() {
let mut test = TestSetup::new();
let owner = "mix-owner";
let mix_id = test.add_dummy_mixnode_with_legal_proxy(owner, None);
test.set_pending_pledge_change(mix_id, None);
let vesting_contract = test.vesting_contract();
let amount = test.coin(12345);
let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
assert_eq!(res.messages.len(), 2);
assert_eq!(
res.messages[0].msg,
CosmosMsg::Bank(BankMsg::Send {
to_address: vesting_contract.to_string(),
amount: vec![amount],
})
)
}
#[test]
fn attaches_vesting_track_message() {
let mut test = TestSetup::new();
let mix_id_no_proxy = test.add_dummy_mixnode("mix-owner1", None);
test.set_pending_pledge_change(mix_id_no_proxy, None);
let mix_id_proxy = test.add_dummy_mixnode_with_legal_proxy("mix-owner2", None);
test.set_pending_pledge_change(mix_id_proxy, None);
let vesting_contract = test.vesting_contract();
let amount = test.coin(12345);
let res_no_proxy =
decrease_pledge(test.deps_mut(), 123, mix_id_no_proxy, amount.clone()).unwrap();
// nothing was attached (apart from bank message tested in `returns_tokens_back_to_the_owner`)
// because it wasn't done with proxy!
assert_eq!(res_no_proxy.messages.len(), 1);
let res_proxy =
decrease_pledge(test.deps_mut(), 123, mix_id_proxy, amount.clone()).unwrap();
assert_eq!(res_proxy.messages.len(), 2);
assert_eq!(
res_proxy.messages[1].msg,
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: vesting_contract.to_string(),
msg: to_binary(&VestingContractExecuteMsg::TrackDecreasePledge {
owner: "mix-owner2".to_string(),
amount,
})
.unwrap(),
funds: vec![],
})
);
}
#[test]
fn without_any_events_in_between_is_equivalent_to_pledging_the_same_amount_immediately() {
let mut test = TestSetup::new();
let pledge1 = Uint128::new(200_000_000);
let pledge_change = Uint128::new(50_000_000);
let pledge3 = Uint128::new(150_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
let decrease = test.coin(pledge_change.u128());
decrease_pledge(test.deps_mut(), 123, mix_id_repledge, decrease).unwrap();
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
test.add_immediate_delegation("alice", 123_456_789u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000u128, mix_id_repledge);
test.add_immediate_delegation("carol", 111_111_111u128, mix_id_repledge);
test.add_immediate_delegation("alice", 123_456_789u128, mix_id_full_pledge);
test.add_immediate_delegation("bob", 500_000_000u128, mix_id_full_pledge);
test.add_immediate_delegation("carol", 111_111_111u128, mix_id_full_pledge);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
let dist1 = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
let dist2 = test.reward_with_distribution_with_state_bypass(
mix_id_full_pledge,
test_helpers::performance(100.0),
);
assert_eq!(dist1, dist2)
}
#[test]
fn correctly_decreases_future_rewards() {
let mut test = TestSetup::new();
let pledge1 = Uint128::new(200_000_000_000);
let pledge_change = Uint128::new(50_000_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge]);
let dist = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
let decrease = test.coin(pledge_change.u128());
decrease_pledge(test.deps_mut(), 123, mix_id_repledge, decrease).unwrap();
let pledge3 = Uint128::new(150_000_000_000) + truncate_reward_amount(dist.operator);
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_full_pledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_full_pledge);
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_full_pledge);
let lost_operator = dist.operator
- Decimal::from_atomics(truncate_reward_amount(dist.operator), 0).unwrap();
let lost_delegates = dist.delegates
- Decimal::from_atomics(truncate_reward_amount(dist.delegates), 0).unwrap();
// add the tiny bit of lost precision manually
let mut mix_rewarding_full = test.mix_rewarding(mix_id_full_pledge);
mix_rewarding_full.delegates += lost_delegates;
mix_rewarding_full.operator += lost_operator;
rewards_storage::MIXNODE_REWARDING
.save(
test.deps_mut().storage,
mix_id_full_pledge,
&mix_rewarding_full,
)
.unwrap();
test.add_immediate_delegation(
"dave",
truncate_reward_amount(dist.delegates).u128(),
mix_id_full_pledge,
);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
// go through few epochs of rewarding
for _ in 0..500 {
test.skip_to_next_epoch_end();
let dist1 = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
let dist2 = test.reward_with_distribution_with_state_bypass(
mix_id_full_pledge,
test_helpers::performance(100.0),
);
assert_eq!(dist1, dist2)
}
}
#[test]
fn correctly_decreases_future_rewards_with_more_passed_epochs() {
let mut test = TestSetup::new();
let pledge1 = Uint128::new(200_000_000_000);
let pledge_change = Uint128::new(50_000_000_000);
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
test.set_pending_pledge_change(mix_id_repledge, None);
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge]);
let mut cumulative_op_reward = Decimal::zero();
let mut cumulative_del_reward = Decimal::zero();
// go few epochs of rewarding before decreasing pledge
for _ in 0..500 {
test.skip_to_next_epoch_end();
let dist = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
cumulative_op_reward += dist.operator;
cumulative_del_reward += dist.delegates;
}
let decrease = test.coin(pledge_change.u128());
decrease_pledge(test.deps_mut(), 123, mix_id_repledge, decrease).unwrap();
let pledge3 =
Uint128::new(150_000_000_000) + truncate_reward_amount(cumulative_op_reward);
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_full_pledge);
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_full_pledge);
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_full_pledge);
let lost_operator = cumulative_op_reward
- Decimal::from_atomics(truncate_reward_amount(cumulative_op_reward), 0).unwrap();
let lost_delegates = cumulative_del_reward
- Decimal::from_atomics(truncate_reward_amount(cumulative_del_reward), 0).unwrap();
// add the tiny bit of lost precision manually
let mut mix_rewarding_full = test.mix_rewarding(mix_id_full_pledge);
mix_rewarding_full.delegates += lost_delegates;
mix_rewarding_full.operator += lost_operator;
rewards_storage::MIXNODE_REWARDING
.save(
test.deps_mut().storage,
mix_id_full_pledge,
&mix_rewarding_full,
)
.unwrap();
test.add_immediate_delegation(
"dave",
truncate_reward_amount(cumulative_del_reward).u128(),
mix_id_full_pledge,
);
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
// go through few more epochs of rewarding
for _ in 0..500 {
test.skip_to_next_epoch_end();
let dist1 = test.reward_with_distribution_with_state_bypass(
mix_id_repledge,
test_helpers::performance(100.0),
);
let dist2 = test.reward_with_distribution_with_state_bypass(
mix_id_full_pledge,
test_helpers::performance(100.0),
);
assert_eq!(dist1, dist2)
}
}
#[test]
fn updates_the_pending_pledge_changes_field() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.set_pending_pledge_change(mix_id, None);
let amount = test.coin(12345);
decrease_pledge(test.deps_mut(), 123, mix_id, amount).unwrap();
let pending = mixnodes_storage::PENDING_MIXNODE_CHANGES
.load(test.deps().storage, mix_id)
.unwrap();
assert!(pending.pledge_change.is_none())
}
}
#[test]
@@ -1983,13 +1439,10 @@ mod tests {
#[cfg(test)]
mod changing_mix_cost_params {
use cosmwasm_std::coin;
use mixnet_contract_common::Percent;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use super::*;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use cosmwasm_std::coin;
use mixnet_contract_common::Percent;
#[test]
fn doesnt_do_anything_if_mixnode_has_unbonded() {
@@ -2027,7 +1480,7 @@ mod tests {
Response::new().add_event(new_mixnode_cost_params_update_event(
123,
mix_id,
&new_params,
&new_params
))
)
);
+2 -100
View File
@@ -13,8 +13,8 @@ use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use mixnet_contract_common::{
CurrentIntervalResponse, EpochEventId, EpochStatus, IntervalEventId, MixId,
NumberOfPendingEventsResponse, PagedRewardedSetResponse, PendingEpochEventResponse,
PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse,
NumberOfPendingEventsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse,
PendingIntervalEventsResponse,
};
pub fn query_epoch_status(deps: Deps<'_>) -> StdResult<EpochStatus> {
@@ -112,22 +112,6 @@ pub fn query_pending_interval_events_paged(
})
}
pub fn query_pending_epoch_event(
deps: Deps<'_>,
event_id: EpochEventId,
) -> Result<PendingEpochEventResponse, MixnetContractError> {
let event = storage::PENDING_EPOCH_EVENTS.may_load(deps.storage, event_id)?;
Ok(PendingEpochEventResponse { event_id, event })
}
pub fn query_pending_interval_event(
deps: Deps<'_>,
event_id: IntervalEventId,
) -> Result<PendingIntervalEventResponse, MixnetContractError> {
let event = storage::PENDING_INTERVAL_EVENTS.may_load(deps.storage, event_id)?;
Ok(PendingIntervalEventResponse { event_id, event })
}
pub fn query_number_of_pending_events(
deps: Deps<'_>,
) -> Result<NumberOfPendingEventsResponse, MixnetContractError> {
@@ -556,88 +540,6 @@ mod tests {
}
}
#[test]
fn query_for_pending_epoch_event() {
let mut test = TestSetup::new();
// it doesn't exist
let expected = PendingEpochEventResponse {
event_id: 123,
event: None,
};
assert_eq!(
expected,
query_pending_epoch_event(test.deps(), 123).unwrap()
);
// it exists
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let env = test.env();
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action.clone()).unwrap();
let expected = PendingEpochEventResponse {
event_id: 1,
event: Some(dummy_action.attach_source_height(env.block.height)),
};
assert_eq!(expected, query_pending_epoch_event(test.deps(), 1).unwrap());
// it no longer exist (but used to)
test.execute_all_pending_events();
let expected = PendingEpochEventResponse {
event_id: 1,
event: None,
};
assert_eq!(expected, query_pending_epoch_event(test.deps(), 1).unwrap());
}
#[test]
fn query_for_pending_interval_event() {
let mut test = TestSetup::new();
// it doesn't exist
let expected = PendingIntervalEventResponse {
event_id: 123,
event: None,
};
assert_eq!(
expected,
query_pending_interval_event(test.deps(), 123).unwrap()
);
// it exists
let dummy_action = PendingIntervalEventKind::ChangeMixCostParams {
mix_id: test.rng.next_u32(),
new_costs: fixtures::mix_node_cost_params_fixture(),
};
let env = test.env();
storage::push_new_interval_event(test.deps_mut().storage, &env, dummy_action.clone())
.unwrap();
let expected = PendingIntervalEventResponse {
event_id: 1,
event: Some(dummy_action.attach_source_height(env.block.height)),
};
assert_eq!(
expected,
query_pending_interval_event(test.deps(), 1).unwrap()
);
// it no longer exist (but used to)
test.execute_all_pending_events();
let expected = PendingIntervalEventResponse {
event_id: 1,
event: None,
};
assert_eq!(
expected,
query_pending_interval_event(test.deps(), 1).unwrap()
);
}
#[test]
fn querying_for_number_of_pending_events() {
let mut test = TestSetup::new();
+4 -67
View File
@@ -81,7 +81,7 @@ pub(crate) fn push_new_epoch_event(
storage: &mut dyn Storage,
env: &Env,
event: PendingEpochEventKind,
) -> StdResult<EpochEventId> {
) -> StdResult<()> {
// not included in non-test code as it messes with our return types as we expected `StdResult`
// from all storage-related operations.
// However, the callers MUST HAVE ensured the below invariant
@@ -90,15 +90,14 @@ pub(crate) fn push_new_epoch_event(
let event_id = next_epoch_event_id_counter(storage)?;
let event_data = event.attach_source_height(env.block.height);
PENDING_EPOCH_EVENTS.save(storage, event_id, &event_data)?;
Ok(event_id)
PENDING_EPOCH_EVENTS.save(storage, event_id, &event_data)
}
pub(crate) fn push_new_interval_event(
storage: &mut dyn Storage,
env: &Env,
event: PendingIntervalEventKind,
) -> StdResult<IntervalEventId> {
) -> StdResult<()> {
// not included in non-test code as it messes with our return types as we expected `StdResult`
// from all storage-related operations.
// However, the callers MUST HAVE ensured the below invariant
@@ -107,8 +106,7 @@ pub(crate) fn push_new_interval_event(
let event_id = next_interval_event_id_counter(storage)?;
let event_data = event.attach_source_height(env.block.height);
PENDING_INTERVAL_EVENTS.save(storage, event_id, &event_data)?;
Ok(event_id)
PENDING_INTERVAL_EVENTS.save(storage, event_id, &event_data)
}
pub(crate) fn update_rewarded_set(
@@ -170,11 +168,8 @@ pub(crate) fn initialise_storage(
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::fixtures;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::mock_dependencies;
use cosmwasm_std::Order;
use rand_chacha::rand_core::RngCore;
fn read_entire_set(storage: &mut dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> {
REWARDED_SET
@@ -216,62 +211,4 @@ mod tests {
assert!(current_set.get(&7).is_none());
assert!(current_set.get(&1).is_none());
}
#[test]
fn pushing_new_epoch_event_returns_its_id() {
let mut test = TestSetup::new();
let env = test.env();
for _ in 0..500 {
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
}
test.execute_all_pending_events();
for _ in 0..10 {
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
}
}
#[test]
fn pushing_new_interval_event_returns_its_id() {
let mut test = TestSetup::new();
let env = test.env();
for _ in 0..500 {
let dummy_action = PendingIntervalEventKind::ChangeMixCostParams {
mix_id: test.rng.next_u32(),
new_costs: fixtures::mix_node_cost_params_fixture(),
};
let id = push_new_interval_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = INTERVAL_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
}
test.execute_all_pending_events();
for _ in 0..10 {
let dummy_action = PendingIntervalEventKind::ChangeMixCostParams {
mix_id: test.rng.next_u32(),
new_costs: fixtures::mix_node_cost_params_fixture(),
};
let id = push_new_interval_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = INTERVAL_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
}
}
}
+71 -164
View File
@@ -10,7 +10,7 @@ use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::{
MixNodeCostParams, MixNodeDetails, MixNodeRewarding, UnbondedMixnode,
};
use mixnet_contract_common::{IdentityKey, Layer, MixId, MixNode, MixNodeBond};
use mixnet_contract_common::{Layer, MixId, MixNode, MixNodeBond};
pub(crate) fn must_get_mixnode_bond_by_owner(
store: &dyn Storage,
@@ -26,34 +26,18 @@ pub(crate) fn must_get_mixnode_bond_by_owner(
.1)
}
pub(crate) fn attach_mix_details(
store: &dyn Storage,
bond_information: MixNodeBond,
) -> StdResult<MixNodeDetails> {
// if bond exists, rewarding details MUST also exist
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.mix_id)?;
// since this `Map` hasn't existed when contract was instantiated, some mixnodes might not
// have an entry here. But that's fine, because it means they have no pending changes
// (if there were supposed to be any changes, they would have been added during migration)
let pending_changes = storage::PENDING_MIXNODE_CHANGES
.may_load(store, bond_information.mix_id)?
.unwrap_or_default();
Ok(MixNodeDetails::new(
bond_information,
rewarding_details,
pending_changes,
))
}
pub(crate) fn get_mixnode_details_by_id(
store: &dyn Storage,
mix_id: MixId,
) -> StdResult<Option<MixNodeDetails>> {
if let Some(bond_information) = storage::mixnode_bonds().may_load(store, mix_id)? {
attach_mix_details(store, bond_information).map(Some)
// if bond exists, rewarding details MUST also exist
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.mix_id)?;
Ok(Some(MixNodeDetails::new(
bond_information,
rewarding_details,
)))
} else {
Ok(None)
}
@@ -69,23 +53,13 @@ pub(crate) fn get_mixnode_details_by_owner(
.item(store, address)?
.map(|record| record.1)
{
attach_mix_details(store, bond_information).map(Some)
} else {
Ok(None)
}
}
pub(crate) fn get_mixnode_details_by_identity(
store: &dyn Storage,
mix_identity: IdentityKey,
) -> StdResult<Option<MixNodeDetails>> {
if let Some(bond_information) = storage::mixnode_bonds()
.idx
.identity_key
.item(store, mix_identity)?
.map(|record| record.1)
{
attach_mix_details(store, bond_information).map(Some)
// if bond exists, rewarding details MUST also exist
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.mix_id)?;
Ok(Some(MixNodeDetails::new(
bond_information,
rewarding_details,
)))
} else {
Ok(None)
}
@@ -179,13 +153,7 @@ pub(crate) mod tests {
mix_node_cost_params_fixture, mix_node_fixture, TEST_COIN_DENOM,
};
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::{coin, Uint128};
pub(crate) struct DummyMixnode {
pub mix_id: MixId,
pub owner: Addr,
pub identity: IdentityKey,
}
use cosmwasm_std::coin;
pub(crate) const OWNER_EXISTS: &str = "mix-owner-existing";
pub(crate) const OWNER_UNBONDING: &str = "mix-owner-unbonding";
@@ -193,59 +161,33 @@ pub(crate) mod tests {
pub(crate) const OWNER_UNBONDED_LEFTOVER: &str = "mix-owner-unbonded-leftover";
// create a mixnode that is bonded, unbonded, in the process of unbonding and unbonded with leftover mix rewarding details
pub(crate) fn setup_mix_combinations(
test: &mut TestSetup,
stake: Option<Uint128>,
) -> Vec<DummyMixnode> {
let (mix_id, keypair) = test.add_dummy_mixnode_with_keypair(OWNER_EXISTS, stake);
let mix_exists = DummyMixnode {
mix_id,
owner: Addr::unchecked(OWNER_EXISTS),
identity: keypair.public_key().to_base58_string(),
};
let (mix_id, keypair) = test.add_dummy_mixnode_with_keypair(OWNER_UNBONDING, stake);
let mix_unbonding = DummyMixnode {
mix_id,
owner: Addr::unchecked(OWNER_UNBONDING),
identity: keypair.public_key().to_base58_string(),
};
let (mix_id, keypair) = test.add_dummy_mixnode_with_keypair(OWNER_UNBONDED, stake);
let mix_unbonded = DummyMixnode {
mix_id,
owner: Addr::unchecked(OWNER_UNBONDED),
identity: keypair.public_key().to_base58_string(),
};
let (mix_id, keypair) = test.add_dummy_mixnode_with_keypair(OWNER_UNBONDED_LEFTOVER, stake);
let mix_unbonded_leftover = DummyMixnode {
mix_id,
owner: Addr::unchecked(OWNER_UNBONDED_LEFTOVER),
identity: keypair.public_key().to_base58_string(),
};
pub(crate) fn setup_mix_combinations(test: &mut TestSetup) -> Vec<MixId> {
let mix_id_exists = test.add_dummy_mixnode(OWNER_EXISTS, None);
let mix_id_unbonding = test.add_dummy_mixnode(OWNER_UNBONDING, None);
let mix_id_unbonded = test.add_dummy_mixnode(OWNER_UNBONDED, None);
let mix_id_unbonded_leftover = test.add_dummy_mixnode(OWNER_UNBONDED_LEFTOVER, None);
// manually adjust delegation info as to indicate the rewarding information shouldnt get removed
let mut rewarding_details = test.mix_rewarding(mix_unbonded_leftover.mix_id);
let mut rewarding_details = test.mix_rewarding(mix_id_unbonded_leftover);
rewarding_details.delegates = Decimal::raw(12345);
rewarding_details.unique_delegations = 1;
rewards_storage::MIXNODE_REWARDING
.save(
test.deps_mut().storage,
mix_unbonded_leftover.mix_id,
mix_id_unbonded_leftover,
&rewarding_details,
)
.unwrap();
test.immediately_unbond_mixnode(mix_unbonded.mix_id);
test.immediately_unbond_mixnode(mix_unbonded_leftover.mix_id);
test.start_unbonding_mixnode(mix_unbonding.mix_id);
test.immediately_unbond_mixnode(mix_id_unbonded);
test.immediately_unbond_mixnode(mix_id_unbonded_leftover);
test.start_unbonding_mixnode(mix_id_unbonding);
vec![
mix_exists,
mix_unbonding,
mix_unbonded,
mix_unbonded_leftover,
mix_id_exists,
mix_id_unbonding,
mix_id_unbonded,
mix_id_unbonded_leftover,
]
}
@@ -253,35 +195,37 @@ pub(crate) mod tests {
fn getting_mixnode_bond_by_owner() {
let mut test = TestSetup::new();
let nodes = setup_mix_combinations(&mut test, None);
let mix_exists = &nodes[0];
let mix_unbonding = &nodes[1];
let mix_unbonded = &nodes[2];
let mix_unbonded_leftover = &nodes[3];
let owner_exists = Addr::unchecked(OWNER_EXISTS);
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
let ids = setup_mix_combinations(&mut test);
let mix_id_exists = ids[0];
let mix_id_unbonding = ids[1];
// if this is a normally bonded mixnode, all should be fine
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_exists.owner).unwrap();
assert_eq!(res.mix_id, mix_exists.mix_id);
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_exists).unwrap();
assert_eq!(res.mix_id, mix_id_exists);
// if node is in the process of unbonding, we still should be capable of retrieving its details
let res =
must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonding.owner).unwrap();
assert_eq!(res.mix_id, mix_unbonding.mix_id);
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_unbonding).unwrap();
assert_eq!(res.mix_id, mix_id_unbonding);
// but if node has unbonded, the information is purged and query fails
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonded.owner);
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_unbonded);
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: mix_unbonded.owner.clone()
owner: owner_unbonded
})
);
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonded_leftover.owner);
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_unbonded_leftover);
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: mix_unbonded_leftover.owner.clone()
owner: owner_unbonded_leftover
})
);
}
@@ -290,27 +234,26 @@ pub(crate) mod tests {
fn getting_mixnode_details_by_id() {
let mut test = TestSetup::new();
let nodes = setup_mix_combinations(&mut test, None);
let mix_exists = &nodes[0];
let mix_unbonding = &nodes[1];
let mix_unbonded = &nodes[2];
let mix_unbonded_leftover = &nodes[3];
let ids = setup_mix_combinations(&mut test);
let mix_id_exists = ids[0];
let mix_id_unbonding = ids[1];
let mix_id_unbonded = ids[2];
let mix_id_unbonded_leftover = ids[3];
let res = get_mixnode_details_by_id(test.deps().storage, mix_exists.mix_id)
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_exists)
.unwrap()
.unwrap();
assert_eq!(res.bond_information.mix_id, mix_exists.mix_id);
assert_eq!(res.bond_information.mix_id, mix_id_exists);
let res = get_mixnode_details_by_id(test.deps().storage, mix_unbonding.mix_id)
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonding)
.unwrap()
.unwrap();
assert_eq!(res.bond_information.mix_id, mix_unbonding.mix_id);
assert_eq!(res.bond_information.mix_id, mix_id_unbonding);
let res = get_mixnode_details_by_id(test.deps().storage, mix_unbonded.mix_id).unwrap();
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonded).unwrap();
assert!(res.is_none());
let res =
get_mixnode_details_by_id(test.deps().storage, mix_unbonded_leftover.mix_id).unwrap();
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonded_leftover).unwrap();
assert!(res.is_none())
}
@@ -318,69 +261,33 @@ pub(crate) mod tests {
fn getting_mixnode_details_by_owner() {
let mut test = TestSetup::new();
let nodes = setup_mix_combinations(&mut test, None);
let mix_exists = &nodes[0];
let mix_unbonding = &nodes[1];
let mix_unbonded = &nodes[2];
let mix_unbonded_leftover = &nodes[3];
let owner_exists = Addr::unchecked(OWNER_EXISTS);
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
let ids = setup_mix_combinations(&mut test);
let mix_id_exists = ids[0];
let mix_id_unbonding = ids[1];
// if this is a normally bonded mixnode, all should be fine
let res = get_mixnode_details_by_owner(test.deps().storage, mix_exists.owner.clone())
let res = get_mixnode_details_by_owner(test.deps().storage, owner_exists)
.unwrap()
.unwrap();
assert_eq!(res.bond_information.mix_id, mix_exists.mix_id);
assert_eq!(res.bond_information.mix_id, mix_id_exists);
// if node is in the process of unbonding, we still should be capable of retrieving its details
let res = get_mixnode_details_by_owner(test.deps().storage, mix_unbonding.owner.clone())
let res = get_mixnode_details_by_owner(test.deps().storage, owner_unbonding)
.unwrap()
.unwrap();
assert_eq!(res.bond_information.mix_id, mix_unbonding.mix_id);
assert_eq!(res.bond_information.mix_id, mix_id_unbonding);
// but if node has unbonded, the information is purged and query fails
let res =
get_mixnode_details_by_owner(test.deps().storage, mix_unbonded.owner.clone()).unwrap();
let res = get_mixnode_details_by_owner(test.deps().storage, owner_unbonded).unwrap();
assert!(res.is_none());
let res =
get_mixnode_details_by_owner(test.deps().storage, mix_unbonded_leftover.owner.clone())
.unwrap();
assert!(res.is_none());
}
#[test]
fn getting_mixnode_details_by_identity() {
let mut test = TestSetup::new();
let nodes = setup_mix_combinations(&mut test, None);
let mix_exists = &nodes[0];
let mix_unbonding = &nodes[1];
let mix_unbonded = &nodes[2];
let mix_unbonded_leftover = &nodes[3];
// if this is a normally bonded mixnode, all should be fine
let res = get_mixnode_details_by_identity(test.deps().storage, mix_exists.identity.clone())
.unwrap()
.unwrap();
assert_eq!(res.bond_information.mix_id, mix_exists.mix_id);
// if node is in the process of unbonding, we still should be capable of retrieving its details
let res =
get_mixnode_details_by_identity(test.deps().storage, mix_unbonding.identity.clone())
.unwrap()
.unwrap();
assert_eq!(res.bond_information.mix_id, mix_unbonding.mix_id);
// but if node has unbonded, the information is purged and query fails
let res =
get_mixnode_details_by_identity(test.deps().storage, mix_unbonded.identity.clone())
.unwrap();
assert!(res.is_none());
let res = get_mixnode_details_by_identity(
test.deps().storage,
mix_unbonded_leftover.identity.clone(),
)
.unwrap();
get_mixnode_details_by_owner(test.deps().storage, owner_unbonded_leftover).unwrap();
assert!(res.is_none());
}
+59 -27
View File
@@ -7,10 +7,7 @@ use crate::constants::{
MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT, MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT,
UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT, UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT,
};
use crate::mixnodes::helpers::{
attach_mix_details, get_mixnode_details_by_id, get_mixnode_details_by_identity,
get_mixnode_details_by_owner,
};
use crate::mixnodes::helpers::{get_mixnode_details_by_id, get_mixnode_details_by_owner};
use crate::rewards::storage as rewards_storage;
use cosmwasm_std::{Deps, Order, StdResult, Storage};
use cw_storage_plus::Bound;
@@ -49,12 +46,18 @@ pub fn query_mixnode_bonds_paged(
))
}
fn attach_node_details(
fn attach_rewarding_info(
storage: &dyn Storage,
read_bond: StdResult<(MixId, MixNodeBond)>,
) -> StdResult<MixNodeDetails> {
match read_bond {
Ok((_, bond)) => attach_mix_details(storage, bond),
Ok((_, bond)) => {
// if we managed to read the bond we MUST be able to also read rewarding information.
// if we fail, this is a hard error and the query should definitely fail and we should investigate
// the reasons for that.
let mix_rewarding = rewards_storage::MIXNODE_REWARDING.load(storage, bond.mix_id)?;
Ok(MixNodeDetails::new(bond, mix_rewarding))
}
Err(err) => Err(err),
}
}
@@ -73,7 +76,7 @@ pub fn query_mixnodes_details_paged(
let nodes = storage::mixnode_bonds()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| attach_node_details(deps.storage, res))
.map(|res| attach_rewarding_info(deps.storage, res))
.collect::<StdResult<Vec<MixNodeDetails>>>()?;
let start_next_after = nodes.last().map(|details| details.mix_id());
@@ -189,12 +192,26 @@ pub fn query_mixnode_details(deps: Deps<'_>, mix_id: MixId) -> StdResult<Mixnode
})
}
// TODO: change the return type to be consistent with the other details queries
pub fn query_mixnode_details_by_identity(
deps: Deps<'_>,
mix_identity: IdentityKey,
) -> StdResult<Option<MixNodeDetails>> {
get_mixnode_details_by_identity(deps.storage, mix_identity)
if let Some(bond_information) = storage::mixnode_bonds()
.idx
.identity_key
.item(deps.storage, mix_identity)?
.map(|record| record.1)
{
// if bond exists, rewarding details MUST also exist
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(deps.storage, bond_information.mix_id)?;
Ok(Some(MixNodeDetails::new(
bond_information,
rewarding_details,
)))
} else {
Ok(None)
}
}
pub fn query_mixnode_rewarding_details(
@@ -442,10 +459,10 @@ pub(crate) mod tests {
fn obeys_limits() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
let mut rng = test_helpers::test_rng();
let limit = 2;
test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000);
let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32);
}
@@ -454,8 +471,8 @@ pub(crate) mod tests {
fn has_default_limit() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
let mut rng = test_helpers::test_rng();
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000);
// query without explicitly setting a limit
let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, None).unwrap();
@@ -470,8 +487,8 @@ pub(crate) mod tests {
fn has_max_limit() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
let mut rng = test_helpers::test_rng();
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000;
@@ -572,11 +589,16 @@ pub(crate) mod tests {
fn obeys_limits() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
let mut rng = test_helpers::test_rng();
let limit = 2;
let owner = "owner";
test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
test_helpers::add_dummy_unbonded_mixnodes_with_owner(
&mut rng,
deps.as_mut(),
owner,
1000,
);
let page1 = query_unbonded_mixnodes_by_owner_paged(
deps.as_ref(),
owner.into(),
@@ -591,10 +613,15 @@ pub(crate) mod tests {
fn has_default_limit() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
let mut rng = test_helpers::test_rng();
let owner = "owner";
test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
test_helpers::add_dummy_unbonded_mixnodes_with_owner(
&mut rng,
deps.as_mut(),
owner,
1000,
);
// query without explicitly setting a limit
let page1 =
@@ -611,10 +638,15 @@ pub(crate) mod tests {
fn has_max_limit() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
let mut rng = test_helpers::test_rng();
let owner = "owner";
test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
test_helpers::add_dummy_unbonded_mixnodes_with_owner(
&mut rng,
deps.as_mut(),
owner,
1000,
);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000;
@@ -804,12 +836,12 @@ pub(crate) mod tests {
fn obeys_limits() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
let mut rng = test_helpers::test_rng();
let limit = 2;
let identity = "foomp123";
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
rng,
&mut rng,
deps.as_mut(),
identity,
1000,
@@ -828,10 +860,10 @@ pub(crate) mod tests {
fn has_default_limit() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
let mut rng = test_helpers::test_rng();
let identity = "foomp123";
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
rng,
&mut rng,
deps.as_mut(),
identity,
1000,
@@ -856,10 +888,10 @@ pub(crate) mod tests {
fn has_max_limit() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let rng = test_helpers::test_rng();
let mut rng = test_helpers::test_rng();
let identity = "foomp123";
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
rng,
&mut rng,
deps.as_mut(),
identity,
1000,
+9 -10
View File
@@ -1,27 +1,23 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{
LAYER_DISTRIBUTION_KEY, MIXNODES_IDENTITY_IDX_NAMESPACE, MIXNODES_OWNER_IDX_NAMESPACE,
MIXNODES_PK_NAMESPACE, MIXNODES_SPHINX_IDX_NAMESPACE, NODE_ID_COUNTER_KEY,
PENDING_MIXNODE_CHANGES_NAMESPACE, UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE,
UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE, UNBONDED_MIXNODES_PK_NAMESPACE,
UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE, UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE,
UNBONDED_MIXNODES_PK_NAMESPACE,
};
use cosmwasm_std::{StdResult, Storage};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex, UniqueIndex};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, MultiIndex, UniqueIndex};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
use mixnet_contract_common::mixnode::UnbondedMixnode;
use mixnet_contract_common::SphinxKey;
use mixnet_contract_common::{Addr, IdentityKey, Layer, LayerDistribution, MixId, MixNodeBond};
pub const LAYERS: Item<'_, LayerDistribution> = Item::new(LAYER_DISTRIBUTION_KEY);
pub const MIXNODE_ID_COUNTER: Item<MixId> = Item::new(NODE_ID_COUNTER_KEY);
pub const PENDING_MIXNODE_CHANGES: Map<MixId, PendingMixNodeChanges> =
Map::new(PENDING_MIXNODE_CHANGES_NAMESPACE);
// keeps track of `node_id -> IdentityKey, Owner, unbonding_height` so we'd known a bit more about past mixnodes
// if we ever decide it's too bloaty, we can deprecate it and start removing all data in
// subsequent migrations
pub(crate) struct UnbondedMixnodeIndex<'a> {
pub(crate) owner: MultiIndex<'a, Addr, UnbondedMixnode, MixId>,
@@ -52,6 +48,9 @@ pub(crate) fn unbonded_mixnodes<'a>(
IndexedMap::new(UNBONDED_MIXNODES_PK_NAMESPACE, indexes)
}
pub(crate) const LAYERS: Item<'_, LayerDistribution> = Item::new(LAYER_DISTRIBUTION_KEY);
pub const MIXNODE_ID_COUNTER: Item<MixId> = Item::new(NODE_ID_COUNTER_KEY);
pub(crate) struct MixnodeBondIndex<'a> {
pub(crate) owner: UniqueIndex<'a, Addr, MixNodeBond>,
+60 -637
View File
@@ -1,19 +1,7 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_mixnode_bonding_event, new_mixnode_config_update_event,
new_mixnode_pending_cost_params_update_event, new_pending_mixnode_unbonding_event,
new_pending_pledge_decrease_event, new_pending_pledge_increase_event,
};
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingIntervalEventKind};
use mixnet_contract_common::{Layer, MixId, MixNode};
use nym_contracts_common::signing::MessageSignature;
use super::storage;
use crate::interval::storage as interval_storage;
use crate::interval::storage::push_new_interval_event;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
@@ -25,11 +13,19 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
validate_pledge,
ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
};
use super::storage;
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_mixnode_bonding_event, new_mixnode_config_update_event,
new_mixnode_pending_cost_params_update_event, new_pending_mixnode_unbonding_event,
new_pending_pledge_increase_event,
};
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingIntervalEventKind};
use mixnet_contract_common::{Layer, MixId, MixNode};
use nym_contracts_common::signing::MessageSignature;
pub(crate) fn update_mixnode_layer(
mix_id: MixId,
@@ -199,7 +195,6 @@ pub fn _try_increase_pledge(
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
let mut pending_changes = mix_details.pending_changes;
let mix_id = mix_details.mix_id();
// increasing pledge is only allowed if the epoch is currently not in the process of being advanced
@@ -207,7 +202,6 @@ pub fn _try_increase_pledge(
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
ensure_no_pending_pledge_changes(&pending_changes)?;
let rewarding_denom = rewarding_denom(deps.storage)?;
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
@@ -219,95 +213,7 @@ pub fn _try_increase_pledge(
mix_id,
amount: pledge_increase,
};
let epoch_event_id = interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
pending_changes.pledge_change = Some(epoch_event_id);
storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
Ok(Response::new().add_event(cosmos_event))
}
pub fn try_decrease_pledge(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
decrease_by: Coin,
) -> Result<Response, MixnetContractError> {
_try_decrease_pledge(deps, env, decrease_by, info.sender, None)
}
pub fn try_decrease_pledge_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
decrease_by: Coin,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_decrease_pledge(deps, env, decrease_by, owner, Some(proxy))
}
pub fn _try_decrease_pledge(
deps: DepsMut<'_>,
env: Env,
decrease_by: Coin,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
let mut pending_changes = mix_details.pending_changes;
let mix_id = mix_details.mix_id();
// decreasing pledge is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
ensure_no_pending_pledge_changes(&pending_changes)?;
let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
// check that the denomination is correct
if decrease_by.denom != minimum_pledge.denom {
return Err(MixnetContractError::WrongDenom {
received: decrease_by.denom,
expected: minimum_pledge.denom,
});
}
// also check if the request contains non-zero amount
// (otherwise it's a no-op and we should we waste gas when resolving events?)
if decrease_by.amount.is_zero() {
return Err(MixnetContractError::ZeroCoinAmount);
}
// decreasing pledge can't result in the new pledge being lower than the minimum amount
let new_pledge_amount = mix_details
.original_pledge()
.amount
.saturating_sub(decrease_by.amount);
if new_pledge_amount < minimum_pledge.amount {
return Err(MixnetContractError::InvalidPledgeReduction {
current: mix_details.original_pledge().amount,
decrease_by: decrease_by.amount,
minimum: minimum_pledge.amount,
denom: minimum_pledge.denom,
});
}
let cosmos_event = new_pending_pledge_decrease_event(mix_id, &decrease_by);
// push the event to execute it at the end of the epoch
let epoch_event = PendingEpochEventKind::DecreasePledge {
mix_id,
decrease_by,
};
let epoch_event_id = interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
pending_changes.pledge_change = Some(epoch_event_id);
storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
Ok(Response::new().add_event(cosmos_event))
}
@@ -340,9 +246,6 @@ pub(crate) fn _try_remove_mixnode(
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
let pending_changes = storage::PENDING_MIXNODE_CHANGES
.may_load(deps.storage, existing_bond.mix_id)?
.unwrap_or_default();
// unbonding is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
@@ -351,9 +254,6 @@ pub(crate) fn _try_remove_mixnode(
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
ensure_bonded(&existing_bond)?;
// if there are any pending requests to change the pledge, wait for them to resolve before allowing the unbonding
ensure_no_pending_pledge_changes(&pending_changes)?;
// set `is_unbonding` field
let mut updated_bond = existing_bond.clone();
updated_bond.is_unbonding = true;
@@ -492,22 +392,18 @@ pub(crate) fn _try_update_mixnode_cost_params(
#[cfg(test)]
pub mod tests {
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Order, StdResult, Uint128};
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{
EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent,
};
use super::*;
use crate::contract::execute;
use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge;
use crate::mixnodes::helpers::get_mixnode_details_by_id;
use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM};
use crate::support::tests::test_helpers::TestSetup;
use crate::support::tests::{fixtures, test_helpers};
use super::*;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Order, StdResult, Uint128};
use mixnet_contract_common::{
EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent,
};
#[test]
fn mixnode_add() {
@@ -635,7 +531,7 @@ pub mod tests {
let res = try_add_mixnode(
test.deps_mut(),
env.clone(),
info,
info.clone(),
mixnode.clone(),
cost_params.clone(),
signature.clone(),
@@ -705,7 +601,7 @@ pub mod tests {
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
vesting_contract
}
)
}
@@ -773,7 +669,7 @@ pub mod tests {
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
incoming: vesting_contract.into_string()
})
);
@@ -821,66 +717,11 @@ pub mod tests {
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
vesting_contract
}
)
}
#[test]
fn mixnode_remove_is_not_allowed_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
let env = test.env();
// prior increase
let owner = "mix-owner1";
test.add_dummy_mixnode(owner, None);
let sender = mock_info(owner, &[test.coin(1000)]);
try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
let res = try_remove_mixnode(test.deps_mut(), env.clone(), sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1
})
);
// prior decrease
let owner = "mix-owner2";
test.add_dummy_mixnode(owner, Some(Uint128::new(10000000000)));
let sender = mock_info(owner, &[]);
let amount = test.coin(1000);
try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_remove_mixnode(test.deps_mut(), env.clone(), sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 2
})
);
// artificial event
let owner = "mix-owner3";
let mix_id = test.add_dummy_mixnode(owner, None);
let pending_change = PendingMixNodeChanges {
pledge_change: Some(1234),
};
storage::PENDING_MIXNODE_CHANGES
.save(test.deps_mut().storage, mix_id, &pending_change)
.unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_remove_mixnode(test.deps_mut(), env, sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1234
})
);
}
#[test]
fn updating_mixnode_config() {
let mut test = TestSetup::new();
@@ -919,7 +760,7 @@ pub mod tests {
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
incoming: vesting_contract.into_string()
})
);
// "normal" update succeeds
@@ -971,7 +812,7 @@ pub mod tests {
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
vesting_contract
}
)
}
@@ -1054,7 +895,7 @@ pub mod tests {
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
incoming: vesting_contract.into_string()
})
);
// "normal" update succeeds
@@ -1077,7 +918,7 @@ pub mod tests {
assert_eq!(
PendingIntervalEventKind::ChangeMixCostParams {
mix_id,
new_costs: update.clone(),
new_costs: update.clone()
},
event.1.kind
);
@@ -1126,7 +967,7 @@ pub mod tests {
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
vesting_contract
}
)
}
@@ -1172,7 +1013,7 @@ pub mod tests {
info_alice,
mixnode1,
cost_params.clone(),
sig1,
sig1
)
.is_ok());
@@ -1184,15 +1025,12 @@ pub mod tests {
#[cfg(test)]
mod increasing_mixnode_pledge {
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{EpochState, EpochStatus};
use super::*;
use crate::mixnodes::helpers::tests::{
setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
};
use crate::support::tests::test_helpers::TestSetup;
use super::*;
use mixnet_contract_common::{EpochState, EpochStatus};
#[test]
fn cant_be_performed_if_epoch_transition_is_in_progress() {
@@ -1270,7 +1108,7 @@ pub mod tests {
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: "proxy".to_string(),
incoming: "proxy".to_string()
})
);
@@ -1285,7 +1123,7 @@ pub mod tests {
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "None".to_string(),
incoming: "None".to_string()
})
);
@@ -1300,7 +1138,7 @@ pub mod tests {
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "unrelated-proxy".to_string(),
incoming: "unrelated-proxy".to_string()
})
)
}
@@ -1316,8 +1154,8 @@ pub mod tests {
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
let ids = setup_mix_combinations(&mut test, None);
let mix_id_unbonding = ids[1].mix_id;
let ids = setup_mix_combinations(&mut test);
let mix_id_unbonding = ids[1];
let res = try_increase_pledge(
test.deps_mut(),
@@ -1376,66 +1214,11 @@ pub mod tests {
res,
Err(MixnetContractError::InsufficientPledge {
received: test.coin(0),
minimum: test.coin(1),
minimum: test.coin(1)
})
)
}
#[test]
fn is_not_allowed_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
let env = test.env();
// prior increase
let owner = "mix-owner1";
test.add_dummy_mixnode(owner, None);
let sender = mock_info(owner, &[test.coin(1000)]);
try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
let res = try_increase_pledge(test.deps_mut(), env.clone(), sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1
})
);
// prior decrease
let owner = "mix-owner2";
test.add_dummy_mixnode(owner, Some(Uint128::new(10000000000)));
let sender = mock_info(owner, &[]);
let amount = test.coin(1000);
try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_increase_pledge(test.deps_mut(), env.clone(), sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 2
})
);
// artificial event
let owner = "mix-owner3";
let mix_id = test.add_dummy_mixnode(owner, None);
let pending_change = PendingMixNodeChanges {
pledge_change: Some(1234),
};
storage::PENDING_MIXNODE_CHANGES
.save(test.deps_mut().storage, mix_id, &pending_change)
.unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_increase_pledge(test.deps_mut(), env, sender);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1234
})
);
}
#[test]
fn with_valid_information_creates_pending_event() {
let mut test = TestSetup::new();
@@ -1455,398 +1238,38 @@ pub mod tests {
events[0].kind,
PendingEpochEventKind::PledgeMore {
mix_id,
amount: test.coin(1000),
amount: test.coin(1000)
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let res = try_increase_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
#[cfg(test)]
mod decreasing_mixnode_pledge {
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{EpochState, EpochStatus};
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
use crate::mixnodes::helpers::tests::{
setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
};
use crate::support::tests::test_helpers::TestSetup;
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
use super::*;
let owner = "alice";
#[test]
fn cant_be_performed_if_epoch_transition_is_in_progress() {
let bad_states = vec![
EpochState::Rewarding {
last_rewarded: 0,
final_node_id: 0,
},
EpochState::ReconcilingEvents,
EpochState::AdvancingEpoch,
];
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
for bad_state in bad_states {
let mut test = TestSetup::new();
let env = test.env();
let owner = "mix-owner";
let decrease = test.coin(1000);
let res = try_increase_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
owner.to_string(),
)
.unwrap_err();
test.add_dummy_mixnode(owner, Some(Uint128::new(100_000_000_000)));
let mut status = EpochStatus::new(test.rewarding_validator().sender);
status.state = bad_state;
interval_storage::save_current_epoch_status(test.deps_mut().storage, &status)
.unwrap();
let sender = mock_info(owner, &[]);
let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
assert!(matches!(
res,
Err(MixnetContractError::EpochAdvancementInProgress { .. })
));
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
}
#[test]
fn is_not_allowed_if_account_doesnt_own_mixnode() {
let mut test = TestSetup::new();
let env = test.env();
let sender = mock_info("not-mix-owner", &[]);
let decrease = test.coin(1000);
let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: Addr::unchecked("not-mix-owner")
})
)
}
#[test]
fn is_not_allowed_if_theres_proxy_mismatch() {
let mut test = TestSetup::new();
let env = test.env();
let owner_without_proxy = Addr::unchecked("no-proxy");
let owner_with_proxy = Addr::unchecked("with-proxy");
let proxy = Addr::unchecked("proxy");
let wrong_proxy = Addr::unchecked("unrelated-proxy");
// just to make sure that after decrease the value would still be above the minimum
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
test.add_dummy_mixnode(owner_without_proxy.as_str(), Some(stake));
test.add_dummy_mixnode_with_illegal_proxy(
owner_with_proxy.as_str(),
Some(stake),
proxy.clone(),
);
let res = _try_decrease_pledge(
test.deps_mut(),
env.clone(),
decrease.clone(),
owner_without_proxy.clone(),
Some(proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: "proxy".to_string(),
})
);
let res = _try_decrease_pledge(
test.deps_mut(),
env.clone(),
decrease.clone(),
owner_with_proxy.clone(),
None,
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "None".to_string(),
})
);
let res = _try_decrease_pledge(
test.deps_mut(),
env,
decrease,
owner_with_proxy.clone(),
Some(wrong_proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "unrelated-proxy".to_string(),
})
)
}
#[test]
fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
let mut test = TestSetup::new();
let env = test.env();
// just to make sure that after decrease the value would still be above the minimum
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
// TODO: I dislike this cross-test access, but it provides us with exactly what we need
// perhaps it should be refactored a bit?
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
let ids = setup_mix_combinations(&mut test, Some(stake));
let mix_id_unbonding = ids[1].mix_id;
let res = try_decrease_pledge(
test.deps_mut(),
env.clone(),
mock_info(owner_unbonding.as_str(), &[]),
decrease.clone(),
);
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding {
mix_id: mix_id_unbonding
})
);
// if the nodes are gone we treat them as tey never existed in the first place
// (regardless of if there's some leftover data)
let res = try_decrease_pledge(
test.deps_mut(),
env.clone(),
mock_info(owner_unbonded_leftover.as_str(), &[]),
decrease.clone(),
);
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner_unbonded_leftover
})
);
let res = try_decrease_pledge(
test.deps_mut(),
env,
mock_info(owner_unbonded.as_str(), &[]),
decrease,
);
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner_unbonded
})
)
}
#[test]
fn is_not_allowed_if_it_would_result_going_below_minimum_pledge() {
let mut test = TestSetup::new();
let env = test.env();
let owner = "mix-owner";
let minimum_pledge = minimum_mixnode_pledge(test.deps().storage).unwrap();
let pledge_amount = minimum_pledge.amount + Uint128::new(100);
let pledged = test.coin(pledge_amount.u128());
test.add_dummy_mixnode(owner, Some(pledge_amount));
let invalid_decrease = test.coin(150);
let valid_decrease = test.coin(50);
let sender = mock_info(owner, &[]);
let res = try_decrease_pledge(
test.deps_mut(),
env.clone(),
sender.clone(),
invalid_decrease.clone(),
);
assert_eq!(
res,
Err(MixnetContractError::InvalidPledgeReduction {
current: pledged.amount,
decrease_by: invalid_decrease.amount,
minimum: minimum_pledge.amount,
denom: minimum_pledge.denom,
})
);
let res = try_decrease_pledge(test.deps_mut(), env, sender, valid_decrease);
assert!(res.is_ok())
}
#[test]
fn provided_amount_has_to_be_nonzero() {
let mut test = TestSetup::new();
let env = test.env();
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(0);
let owner = "mix-owner";
test.add_dummy_mixnode(owner, Some(stake));
let sender = mock_info(owner, &[]);
let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
assert_eq!(res, Err(MixnetContractError::ZeroCoinAmount))
}
#[test]
fn is_not_allowed_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
let env = test.env();
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
// prior increase
let owner = "mix-owner1";
test.add_dummy_mixnode(owner, Some(stake));
let sender = mock_info(owner, &[test.coin(1000)]);
try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
let res = try_decrease_pledge(test.deps_mut(), env.clone(), sender, decrease.clone());
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1
})
);
// prior decrease
let owner = "mix-owner2";
test.add_dummy_mixnode(owner, Some(stake));
let sender = mock_info(owner, &[]);
let amount = test.coin(1000);
try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_decrease_pledge(test.deps_mut(), env.clone(), sender, decrease.clone());
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 2
})
);
// artificial event
let owner = "mix-owner3";
let mix_id = test.add_dummy_mixnode(owner, Some(stake));
let pending_change = PendingMixNodeChanges {
pledge_change: Some(1234),
};
storage::PENDING_MIXNODE_CHANGES
.save(test.deps_mut().storage, mix_id, &pending_change)
.unwrap();
let sender = mock_info(owner, &[test.coin(1000)]);
let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
assert_eq!(
res,
Err(MixnetContractError::PendingPledgeChange {
pending_event_id: 1234
})
);
}
#[test]
fn with_valid_information_creates_pending_event() {
let mut test = TestSetup::new();
let env = test.env();
// just to make sure that after decrease the value would still be above the minimum
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
let owner = "mix-owner";
let mix_id = test.add_dummy_mixnode(owner, Some(stake));
let events = test.pending_epoch_events();
assert!(events.is_empty());
let sender = mock_info(owner, &[]);
try_decrease_pledge(test.deps_mut(), env, sender, decrease.clone()).unwrap();
let events = test.pending_epoch_events();
assert_eq!(
events[0].kind,
PendingEpochEventKind::DecreasePledge {
mix_id,
decrease_by: decrease,
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(stake), illegal_proxy.clone());
let res = try_decrease_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
decrease,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
)
}
}
-40
View File
@@ -1,42 +1,2 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::interval::storage as interval_storage;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::DepsMut;
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::PendingEpochEventKind;
use std::collections::BTreeMap;
pub fn insert_pending_pledge_changes(deps: DepsMut<'_>) -> Result<(), MixnetContractError> {
let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(deps.storage)?;
let last_inserted = interval_storage::EPOCH_EVENT_ID_COUNTER.load(deps.storage)?;
let mut new_pending = BTreeMap::new();
for event_id in last_executed + 1..=last_inserted {
let event = interval_storage::PENDING_EPOCH_EVENTS.load(deps.storage, event_id)?;
match event.kind {
PendingEpochEventKind::PledgeMore { mix_id, .. }
| PendingEpochEventKind::DecreasePledge { mix_id, .. } => {
if new_pending.insert(mix_id, event_id).is_some() {
return Err(MixnetContractError::FailedMigration { comment: format!("mixnode {mix_id} has more than a single pledge change pending for this epoch. Run this migration again after the epoch has finished.") });
}
}
_ => (),
}
}
for (mix_id, event_id) in new_pending {
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(
deps.storage,
mix_id,
&PendingMixNodeChanges {
pledge_change: Some(event_id),
},
)?;
}
Ok(())
}
+2 -2
View File
@@ -253,8 +253,8 @@ mod tests {
fn withdrawing_delegator_reward() {
let mut test = TestSetup::new();
let delegation_amount = Uint128::new(2_500_000_000);
let delegation_dec = 2_500_000_000_u32.into_base_decimal().unwrap();
let delegation_amount = Uint128::new(2500_000_000);
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);
+53 -65
View File
@@ -1,8 +1,20 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_storage;
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::helpers;
use crate::rewards::helpers::update_and_save_last_rewarded;
use crate::support::helpers::{
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
ensure_proxy_match, ensure_sent_by_vesting_contract, send_to_proxy_or_owner,
};
use cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_active_set_update_event, new_mix_rewarding_event,
@@ -18,21 +30,6 @@ use mixnet_contract_common::reward_params::{
use mixnet_contract_common::{Delegation, EpochState, MixId};
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_storage;
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::helpers;
use crate::rewards::helpers::update_and_save_last_rewarded;
use crate::support::helpers::{
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
ensure_proxy_match, ensure_sent_by_vesting_contract, send_to_proxy_or_owner,
};
use super::storage;
pub(crate) fn try_reward_mixnode(
deps: DepsMut<'_>,
env: Env,
@@ -236,22 +233,23 @@ pub(crate) fn _try_withdraw_delegator_reward(
mix_id,
address: owner.into_string(),
proxy: proxy.map(Addr::into_string),
});
})
}
Some(delegation) => delegation,
};
// grab associated mixnode rewarding details
let mix_rewarding =
storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::inconsistent_state(
"mixnode rewarding got removed from the storage whilst there's still an existing delegation"
))?;
storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::InconsistentState {
comment: "mixnode rewarding got removed from the storage whilst there's still an existing delegation"
.into(),
})?;
// see if the mixnode is not in the process of unbonding or whether it has already unbonded
// (in that case the expected path of getting your tokens back is via undelegation)
match mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)? {
Some(mix_bond) if mix_bond.is_unbonding => {
return Err(MixnetContractError::MixnodeIsUnbonding { mix_id });
return Err(MixnetContractError::MixnodeIsUnbonding { mix_id })
}
None => return Err(MixnetContractError::MixnodeHasUnbonded { mix_id }),
_ => (),
@@ -378,17 +376,17 @@ pub(crate) fn try_update_rewarding_params(
#[cfg(test)]
pub mod tests {
use cosmwasm_std::testing::mock_info;
use super::*;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::tests::test_helpers;
use super::*;
use cosmwasm_std::testing::mock_info;
#[cfg(test)]
mod mixnode_rewarding {
use super::*;
use crate::interval::pending_events;
use crate::support::tests::test_helpers::{find_attribute, TestSetup};
use cosmwasm_std::{Decimal, Uint128};
use mixnet_contract_common::events::{
MixnetEventType, BOND_NOT_FOUND_VALUE, DELEGATES_REWARD_KEY, NO_REWARD_REASON_KEY,
OPERATOR_REWARD_KEY, PRIOR_DELEGATES_KEY, PRIOR_UNIT_REWARD_KEY,
@@ -397,16 +395,10 @@ pub mod tests {
use mixnet_contract_common::helpers::compare_decimals;
use mixnet_contract_common::{EpochStatus, RewardedSetNodeStatus};
use crate::interval::pending_events;
use crate::support::tests::test_helpers::{find_attribute, TestSetup};
use super::*;
#[cfg(test)]
mod epoch_state_is_correctly_updated {
use mixnet_contract_common::EpochState;
use super::*;
use mixnet_contract_common::EpochState;
#[test]
fn when_target_mixnode_unbonded() {
@@ -467,7 +459,7 @@ pub mod tests {
assert_eq!(
EpochState::Rewarding {
last_rewarded: mix_id_unbonded,
final_node_id: mix_id_never_existed,
final_node_id: mix_id_never_existed
},
interval_storage::current_epoch_status(test.deps().storage)
.unwrap()
@@ -485,7 +477,7 @@ pub mod tests {
assert_eq!(
EpochState::Rewarding {
last_rewarded: mix_id_unbonded_leftover,
final_node_id: mix_id_never_existed,
final_node_id: mix_id_never_existed
},
interval_storage::current_epoch_status(test.deps().storage)
.unwrap()
@@ -494,8 +486,8 @@ pub mod tests {
try_reward_mixnode(
test.deps_mut(),
env,
sender,
env.clone(),
sender.clone(),
mix_id_never_existed,
performance,
)
@@ -586,7 +578,7 @@ pub mod tests {
assert_eq!(
EpochState::Rewarding {
last_rewarded: mix_id,
final_node_id: 100,
final_node_id: 100
},
current_state
)
@@ -1407,15 +1399,12 @@ pub mod tests {
#[cfg(test)]
mod withdrawing_delegator_reward {
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
use crate::interval::pending_events;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
use super::*;
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn can_only_be_done_if_delegation_exists() {
@@ -1783,7 +1772,7 @@ pub mod tests {
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
vesting_contract
}
)
}
@@ -1791,13 +1780,11 @@ pub mod tests {
#[cfg(test)]
mod withdrawing_operator_reward {
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128};
use super::*;
use crate::interval::pending_events;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::TestSetup;
use super::*;
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128};
#[test]
fn can_only_be_done_if_bond_exists() {
@@ -1942,7 +1929,7 @@ pub mod tests {
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
vesting_contract
}
)
}
@@ -1950,11 +1937,9 @@ pub mod tests {
#[cfg(test)]
mod updating_active_set {
use mixnet_contract_common::{EpochState, EpochStatus};
use crate::support::tests::test_helpers::TestSetup;
use super::*;
use crate::support::tests::test_helpers::TestSetup;
use mixnet_contract_common::{EpochState, EpochStatus};
#[test]
fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() {
@@ -2109,7 +2094,7 @@ pub mod tests {
.unwrap()
.active_set_size;
let env = test.env();
let res = try_update_active_set_size(test.deps_mut(), env, owner, 42, true);
let res = try_update_active_set_size(test.deps_mut(), env, owner.clone(), 42, true);
assert!(res.is_ok());
let new = storage::REWARDING_PARAMS
.load(test.deps().storage)
@@ -2155,13 +2140,10 @@ pub mod tests {
#[cfg(test)]
mod updating_rewarding_params {
use cosmwasm_std::Decimal;
use mixnet_contract_common::{EpochState, EpochStatus};
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
use super::*;
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
use cosmwasm_std::Decimal;
use mixnet_contract_common::{EpochState, EpochStatus};
#[test]
fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() {
@@ -2199,7 +2181,7 @@ pub mod tests {
test.deps_mut(),
env.clone(),
owner.clone(),
update,
update.clone(),
false,
);
assert!(matches!(
@@ -2207,8 +2189,13 @@ pub mod tests {
Err(MixnetContractError::EpochAdvancementInProgress { .. })
));
let res_forced =
try_update_rewarding_params(test.deps_mut(), env.clone(), owner, update, true);
let res_forced = try_update_rewarding_params(
test.deps_mut(),
env.clone(),
owner,
update.clone(),
true,
);
assert!(res_forced.is_ok())
}
}
@@ -2321,7 +2308,8 @@ pub mod tests {
let old = storage::REWARDING_PARAMS.load(test.deps().storage).unwrap();
let env = test.env();
let res = try_update_rewarding_params(test.deps_mut(), env, owner, update, true);
let res =
try_update_rewarding_params(test.deps_mut(), env, owner.clone(), update, true);
assert!(res.is_ok());
let new = storage::REWARDING_PARAMS.load(test.deps().storage).unwrap();
assert_ne!(old, new);
-45
View File
@@ -6,7 +6,6 @@ use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
@@ -47,14 +46,6 @@ where
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError>;
fn maybe_add_track_vesting_decrease_mixnode_pledge(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError>;
}
impl VestingTracking for Response {
@@ -124,33 +115,6 @@ impl VestingTracking for Response {
Ok(self)
}
}
fn maybe_add_track_vesting_decrease_mixnode_pledge(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError> {
if let Some(proxy) = proxy {
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
// exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`.
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy,
vesting_contract,
});
}
let msg = VestingContractExecuteMsg::TrackDecreasePledge { owner, amount };
let track_decrease_pledge_message = wasm_execute(proxy, &msg, vec![])?;
Ok(self.add_message(track_decrease_pledge_message))
} else {
// there's no proxy so nothing to do
Ok(self)
}
}
}
// pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
@@ -378,15 +342,6 @@ pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractErro
Ok(())
}
pub(crate) fn ensure_no_pending_pledge_changes(
pending_changes: &PendingMixNodeChanges,
) -> Result<(), MixnetContractError> {
if let Some(pending_event_id) = pending_changes.pledge_change {
return Err(MixnetContractError::PendingPledgeChange { pending_event_id });
}
Ok(())
}
// check if the target address has already bonded a mixnode or gateway,
// in either case, return an appropriate error
pub(crate) fn ensure_no_existing_bond(
+19 -23
View File
@@ -45,7 +45,7 @@ pub mod test_helpers {
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::testing::MockApi;
use cosmwasm_std::testing::MockQuerier;
use cosmwasm_std::{coin, coins, Addr, Api, BankMsg, CosmosMsg, Storage};
use cosmwasm_std::{coin, Addr, Api, BankMsg, CosmosMsg, Storage};
use cosmwasm_std::{Coin, Order};
use cosmwasm_std::{Decimal, Empty, MemoryStorage};
use cosmwasm_std::{Deps, OwnedDeps};
@@ -62,7 +62,7 @@ pub mod test_helpers {
use mixnet_contract_common::rewarding::simulator::Simulator;
use mixnet_contract_common::rewarding::RewardDistribution;
use mixnet_contract_common::{
construct_family_join_permit, Delegation, EpochEventId, EpochState, EpochStatus, Gateway,
construct_family_join_permit, Delegation, EpochState, EpochStatus, Gateway,
GatewayBondingPayload, IdentityKey, IdentityKeyRef, InitialRewardingParams, InstantiateMsg,
Interval, MixId, MixNode, MixNodeBond, MixnodeBondingPayload, Percent,
RewardedSetNodeStatus, SignableGatewayBondingMsg, SignableMixNodeBondingMsg,
@@ -159,10 +159,6 @@ pub mod test_helpers {
coin(amount, rewarding_denom(self.deps().storage).unwrap())
}
pub fn coins(&self, amount: u128) -> Vec<Coin> {
coins(amount, rewarding_denom(self.deps().storage).unwrap())
}
pub fn current_interval(&self) -> Interval {
interval_storage::current_interval(self.deps().storage).unwrap()
}
@@ -193,7 +189,8 @@ pub mod test_helpers {
let family_head = FamilyHead::new(&identity);
let owner = head_mixnode.owner;
let nonce = signing_storage::get_signing_nonce(self.deps().storage, owner).unwrap();
let nonce =
signing_storage::get_signing_nonce(self.deps().storage, owner.clone()).unwrap();
let proxy = if vesting {
Some(self.vesting_contract())
@@ -246,17 +243,6 @@ pub mod test_helpers {
(mix_id, keys)
}
pub fn set_pending_pledge_change(&mut self, mix_id: MixId, event_id: Option<EpochEventId>) {
let mut changes = mixnodes_storage::PENDING_MIXNODE_CHANGES
.load(self.deps().storage, mix_id)
.unwrap_or_default();
changes.pledge_change = Some(event_id.unwrap_or(12345));
mixnodes_storage::PENDING_MIXNODE_CHANGES
.save(self.deps_mut().storage, mix_id, &changes)
.unwrap();
}
pub fn add_dummy_mixnode(&mut self, owner: &str, stake: Option<Uint128>) -> MixId {
let stake = self.make_mix_pledge(stake);
let (mixnode, owner_signature, _) =
@@ -555,8 +541,13 @@ pub mod test_helpers {
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::mix_node_fixture()
};
let msg =
mixnode_bonding_sign_payload(self.deps(), sender, None, mixnode.clone(), stake);
let msg = mixnode_bonding_sign_payload(
self.deps(),
sender,
None,
mixnode.clone(),
stake.clone(),
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
(mixnode, owner_signature, keypair)
@@ -579,8 +570,13 @@ pub mod test_helpers {
..tests::fixtures::gateway_fixture()
};
let msg =
gateway_bonding_sign_payload(self.deps(), sender, None, gateway.clone(), stake);
let msg = gateway_bonding_sign_payload(
self.deps(),
sender,
None,
gateway.clone(),
stake.clone(),
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
(gateway, owner_signature)
@@ -1106,7 +1102,7 @@ pub mod test_helpers {
deps.branch(),
&env,
env.block.height,
Addr::unchecked(format!("owner{}", i)),
Addr::unchecked(&format!("owner{}", i)),
mix_id,
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
None,
@@ -780,7 +780,7 @@ mod tests {
let err = app
.execute_contract(
Addr::unchecked(TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string()),
flex_addr,
flex_addr.clone(),
&proposal_wrong_exp,
&[],
)
@@ -856,7 +856,12 @@ mod tests {
let proposer = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string();
let res = app
.execute_contract(Addr::unchecked(&proposer), flex_addr, &proposal, &[])
.execute_contract(
Addr::unchecked(&proposer),
flex_addr.clone(),
&proposal,
&[],
)
.unwrap();
assert_eq!(
res.custom_attrs(1),
@@ -262,7 +262,7 @@ fn paging_works() {
services: vec![
service_info(1, nym_address1.clone(), announcer1.clone()),
service_info(2, nym_address1.clone(), announcer1.clone()),
service_info(3, nym_address2.clone(), announcer1),
service_info(3, nym_address2.clone(), announcer1.clone()),
],
per_page: 3,
start_next_after: Some(3),
@@ -272,8 +272,8 @@ fn paging_works() {
setup.query_all_with_limit(Some(3), Some(3)),
PagedServicesListResponse {
services: vec![
service_info(4, nym_address1, announcer2.clone()),
service_info(5, nym_address2, announcer2),
service_info(4, nym_address1.clone(), announcer2.clone()),
service_info(5, nym_address2.clone(), announcer2.clone()),
],
per_page: 3,
start_next_after: Some(5),
-4
View File
@@ -160,14 +160,10 @@ pub fn execute(
deps,
),
ExecuteMsg::PledgeMore { amount } => try_pledge_more(deps, env, info, amount),
ExecuteMsg::DecreasePledge { amount } => try_decrease_pledge(deps, info, amount),
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
try_track_unbond_mixnode(&owner, amount, info, deps)
}
ExecuteMsg::TrackDecreasePledge { owner, amount } => {
try_track_decrease_mixnode_pledge(&owner, amount, info, deps)
}
ExecuteMsg::BondGateway {
gateway,
owner_signature,
+1 -4
View File
@@ -1,5 +1,5 @@
use crate::storage::AccountStorageKey;
use cosmwasm_std::{Addr, Coin, OverflowError, StdError, Uint128};
use cosmwasm_std::{Addr, OverflowError, StdError, Uint128};
use mixnet_contract_common::MixId;
use thiserror::Error;
@@ -58,9 +58,6 @@ pub enum ContractError {
#[error("VESTING ({}): No bond found for account {0}", line!())]
NoBondFound(String),
#[error("VESTING: Attempted to reduce mixnode bond pledge below zero! The current pledge is {current} and we attempted to reduce it by {decrease_by}.")]
InvalidBondPledgeReduction { current: Coin, decrease_by: Coin },
#[error("VESTING ({}): Action can only be executed by account owner -> {0}", line!())]
NotOwner(String),
+1 -1
View File
@@ -5,7 +5,7 @@
#![warn(clippy::unwrap_used)]
pub mod contract;
pub mod errors;
mod errors;
mod queries;
mod queued_migrations;
mod storage;
+1 -19
View File
@@ -1,7 +1,7 @@
use crate::errors::ContractError;
use crate::vesting::Account;
use cosmwasm_std::Order;
use cosmwasm_std::{Addr, Api, Storage, Uint128};
use cosmwasm_std::{Coin, Order};
use cw_storage_plus::{Item, Map};
use mixnet_contract_common::{IdentityKey, MixId};
use vesting_contract_common::PledgeData;
@@ -157,24 +157,6 @@ pub fn save_bond_pledge(
Ok(())
}
pub fn decrease_bond_pledge(
key: AccountStorageKey,
amount: Coin,
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
let mut existing = BOND_PLEDGES.load(storage, key)?;
if existing.amount.amount <= amount.amount {
// this shouldn't be possible!
// (but check for it anyway... just in case)
return Err(ContractError::InvalidBondPledgeReduction {
current: existing.amount,
decrease_by: amount,
});
}
existing.amount.amount -= amount.amount;
save_bond_pledge(key, &existing, storage)
}
pub fn load_gateway_pledge(
key: AccountStorageKey,
storage: &dyn Storage,
+2 -2
View File
@@ -325,12 +325,12 @@ pub mod helpers {
};
let env = mock_env();
let info = mock_info("admin", &[]);
instantiate(deps.as_mut(), env, info, msg).unwrap();
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
pub fn vesting_account_mid_fixture(storage: &mut dyn Storage, env: &Env) -> Account {
let start_time_ts = env.block.time;
let start_time_ts = env.block.time.clone();
let start_time = env.block.time.seconds() - 7200;
let periods = populate_vesting_periods(
start_time,
@@ -27,12 +27,6 @@ pub trait MixnodeBondingAccount {
storage: &mut dyn Storage,
) -> Result<Response, ContractError>;
fn try_decrease_mixnode_pledge(
&self,
amount: Coin,
storage: &mut dyn Storage,
) -> Result<Response, ContractError>;
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
fn try_track_unbond_mixnode(
@@ -41,12 +35,6 @@ pub trait MixnodeBondingAccount {
storage: &mut dyn Storage,
) -> Result<(), ContractError>;
fn try_track_decrease_mixnode_pledge(
&self,
amount: Coin,
storage: &mut dyn Storage,
) -> Result<(), ContractError>;
fn try_update_mixnode_config(
&self,
new_config: MixNodeConfigUpdate,
+2 -31
View File
@@ -19,8 +19,8 @@ use mixnet_contract_common::{
use vesting_contract_common::events::{
new_ownership_transfer_event, new_periodic_vesting_account_event,
new_staking_address_update_event, new_track_gateway_unbond_event,
new_track_mixnode_pledge_decrease_event, new_track_mixnode_unbond_event,
new_track_reward_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
new_vested_coins_withdraw_event,
};
use vesting_contract_common::messages::VestingSpecification;
use vesting_contract_common::PledgeCap;
@@ -278,19 +278,6 @@ pub fn try_pledge_more(
account.try_pledge_additional_tokens(additional_pledge, &env, deps.storage)
}
pub fn try_decrease_pledge(
deps: DepsMut<'_>,
info: MessageInfo,
amount: Coin,
) -> Result<Response, ContractError> {
let mix_denom = MIX_DENOM.load(deps.storage)?;
// perform basic validation - is it correct demon, is it non-zero, etc.
let decrease = validate_funds(&[amount], mix_denom)?;
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_decrease_mixnode_pledge(decrease, deps.storage)
}
/// Unbond a mixnode, sends [mixnet_contract_common::ExecuteMsg::UnbondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
@@ -312,22 +299,6 @@ pub fn try_track_unbond_mixnode(
Ok(Response::new().add_event(new_track_mixnode_unbond_event()))
}
/// Tracks decreasing mixnode pledge. Invoked by the mixnet contract after successful event reconciliation.
/// A separate BankMsg containing the specified amount was sent in the same transaction.
pub fn try_track_decrease_mixnode_pledge(
owner: &str,
amount: Coin,
info: MessageInfo,
deps: DepsMut<'_>,
) -> Result<Response, ContractError> {
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
return Err(ContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(owner, deps.storage, deps.api)?;
account.try_track_decrease_mixnode_pledge(amount, deps.storage)?;
Ok(Response::new().add_event(new_track_mixnode_pledge_decrease_event()))
}
/// Track reward collection, invoked by the mixnert contract after sucessful reward compounding or claiming
pub fn try_track_reward(
deps: DepsMut<'_>,
@@ -11,9 +11,9 @@ use mixnet_contract_common::mixnode::MixNodeConfigUpdate;
use mixnet_contract_common::mixnode::MixNodeCostParams;
use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, MixNode};
use vesting_contract_common::events::{
new_vesting_decrease_pledge_event, new_vesting_mixnode_bonding_event,
new_vesting_mixnode_unbonding_event, new_vesting_pledge_more_event,
new_vesting_update_mixnode_config_event, new_vesting_update_mixnode_cost_params_event,
new_vesting_mixnode_bonding_event, new_vesting_mixnode_unbonding_event,
new_vesting_pledge_more_event, new_vesting_update_mixnode_config_event,
new_vesting_update_mixnode_cost_params_event,
};
use vesting_contract_common::PledgeData;
@@ -109,51 +109,6 @@ impl MixnodeBondingAccount for Account {
.add_event(new_vesting_pledge_more_event()))
}
fn try_decrease_mixnode_pledge(
&self,
amount: Coin,
storage: &mut dyn Storage,
) -> Result<Response, ContractError> {
match self.load_mixnode_pledge(storage)? {
Some(pledge) => {
if pledge.amount.amount <= amount.amount {
return Err(ContractError::InvalidBondPledgeReduction {
current: pledge.amount,
decrease_by: amount,
});
}
}
None => {
return Err(ContractError::NoBondFound(
self.owner_address().as_str().to_string(),
));
}
}
let msg = MixnetExecuteMsg::DecreasePledgeOnBehalf {
owner: self.owner_address().into_string(),
decrease_by: amount,
};
let decrease_pledge_message =
wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?;
Ok(Response::new()
.add_message(decrease_pledge_message)
.add_event(new_vesting_decrease_pledge_event()))
}
fn try_track_decrease_mixnode_pledge(
&self,
amount: Coin,
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
self.save_balance(new_balance, storage)?;
self.decrease_mixnode_pledge(amount, storage)
}
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf {
owner: self.owner_address().into_string(),
@@ -180,7 +135,8 @@ impl MixnodeBondingAccount for Account {
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
self.save_balance(new_balance, storage)?;
self.remove_mixnode_pledge(storage)
self.remove_mixnode_pledge(storage)?;
Ok(())
}
fn try_update_mixnode_config(
+4 -12
View File
@@ -1,10 +1,10 @@
use super::VestingPeriod;
use crate::errors::ContractError;
use crate::storage::{
count_subdelegations_for_mix, decrease_bond_pledge, load_balance, load_bond_pledge,
load_delegation_timestamps, load_gateway_pledge, load_withdrawn, remove_bond_pledge,
remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge,
save_gateway_pledge, save_withdrawn, AccountStorageKey, BlockTimestampSecs, DELEGATIONS, KEY,
count_subdelegations_for_mix, load_balance, load_bond_pledge, load_delegation_timestamps,
load_gateway_pledge, load_withdrawn, remove_bond_pledge, remove_delegation,
remove_gateway_pledge, save_account, save_balance, save_bond_pledge, save_gateway_pledge,
save_withdrawn, AccountStorageKey, BlockTimestampSecs, DELEGATIONS, KEY,
};
use crate::traits::VestingAccount;
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
@@ -247,14 +247,6 @@ impl Account {
remove_bond_pledge(self.storage_key(), storage)
}
pub fn decrease_mixnode_pledge(
&self,
amount: Coin,
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
decrease_bond_pledge(self.storage_key, amount, storage)
}
pub fn load_gateway_pledge(
&self,
storage: &dyn Storage,
+8 -8
View File
@@ -68,7 +68,7 @@ mod tests {
cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
};
// Try creating an account when not admin
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
assert!(response.is_err());
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
@@ -142,7 +142,7 @@ mod tests {
assert!(old_owner_account.is_none());
// Not the owner
let response = execute(deps.as_mut(), env.clone(), info, msg);
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg);
assert!(response.is_err());
// can't stake on behalf of the original owner anymore, but we can do it for the new one!
@@ -190,7 +190,7 @@ mod tests {
assert!(response.is_ok());
let info = mock_info("owner", &[]);
let response = execute(deps.as_mut(), env.clone(), info, msg);
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
assert!(response.is_err());
}
@@ -202,7 +202,7 @@ mod tests {
let msg = ExecuteMsg::TransferOwnership {
to_address: "new_owner".to_string(),
};
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg);
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
// Only owner can transfer
assert!(response.is_err());
@@ -213,7 +213,7 @@ mod tests {
},
};
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
let response = execute(deps.as_mut(), env, info, msg);
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
// Only owner can withdraw
assert!(response.is_err());
}
@@ -510,7 +510,7 @@ mod tests {
assert_eq!(
account.load_balance(&deps.storage).unwrap(),
Uint128::new(1_000_000_000_000)
Uint128::new(1000_000_000_000)
);
account
@@ -588,7 +588,7 @@ mod tests {
};
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
let _response = execute(deps.as_mut(), env.clone(), info, msg);
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
let account = load_account(Addr::unchecked("owner"), &deps.storage)
.unwrap()
.unwrap();
@@ -837,7 +837,7 @@ mod tests {
// but the additional one is going to fail
let res = vesting_account
.try_delegate_to_mixnode(mix_id, delegation, &env, &mut deps.storage)
.try_delegate_to_mixnode(mix_id, delegation.clone(), &env, &mut deps.storage)
.unwrap_err();
assert_eq!(
@@ -196,8 +196,6 @@ Follow these steps to update the information about your mix node which is public
You can either do this graphically via the Desktop Wallet, or the CLI.
#### Updating node information via the Desktop Wallet
Node operators can update node information and change node settings such as the amount of self-bond (increase or decrease) via the desktop wallet.
* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner:
![Bonding page](../images/wallet-screenshots/bonding.png)
+1
View File
@@ -17,6 +17,7 @@ GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm2
MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy
COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYXD="https://qwerty-validator.qa.nymte.ch/"
NYM_API="https://qwerty-validator-api.qa.nymte.ch/api"
+6 -24
View File
@@ -1,11 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::Stream;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio_stream::StreamMap;
pub(crate) type GatewayMessages = Vec<Vec<u8>>;
@@ -23,7 +20,11 @@ impl GatewaysReader {
}
}
pub fn add_receivers(
pub fn stream_map(&mut self) -> &mut StreamMap<String, MixnetMessageReceiver> {
&mut self.stream_map
}
pub fn add_recievers(
&mut self,
id: identity::PublicKey,
message_receiver: MixnetMessageReceiver,
@@ -34,27 +35,8 @@ impl GatewaysReader {
self.ack_map.insert(channel_id, ack_receiver);
}
pub fn remove_receivers(&mut self, id: &str) {
pub fn remove_recievers(&mut self, id: &str) {
self.stream_map.remove(id);
self.ack_map.remove(id);
}
}
impl Stream for GatewaysReader {
// just return whatever is returned by our main `stream_map`
type Item = <StreamMap<String, MixnetMessageReceiver> as Stream>::Item;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// exhaust the ack map if possible
match Pin::new(&mut self.ack_map).poll_next(cx) {
Poll::Ready(None) => {
// this should have never happened!
return Poll::Ready(None);
}
Poll::Ready(Some(_item)) => (),
Poll::Pending => (),
}
Pin::new(&mut self.stream_map).poll_next(cx)
}
}
@@ -42,10 +42,10 @@ impl PacketReceiver {
match update {
GatewayClientUpdate::New(id, (message_receiver, ack_receiver)) => {
self.gateways_reader
.add_receivers(id, message_receiver, ack_receiver);
.add_recievers(id, message_receiver, ack_receiver);
}
GatewayClientUpdate::Failure(id) => {
self.gateways_reader.remove_receivers(&id.to_string());
self.gateways_reader.remove_recievers(&id.to_string());
}
}
}
@@ -66,12 +66,11 @@ impl PacketReceiver {
// unwrap here is fine as it can only return a `None` if the PacketSender has died
// and if that was the case, then the entire monitor is already in an undefined state
update = self.clients_updater.next() => self.process_gateway_update(update.unwrap()),
gateway_message = self.gateways_reader.next() => {
let Some((_gateway_id, message)) = gateway_message else {
log::error!("the gateways reader stream has terminated!");
continue
};
self.process_gateway_messages(message)
// similarly gateway reader will never return a `None` as it's implemented
// as an infinite stream that returns Poll::Pending if it doesn't have anything
// to return
Some((_gateway_id, message)) = self.gateways_reader.stream_map().next() => {
self.process_gateway_messages(message)
}
}
}
+4 -1
View File
@@ -27,7 +27,10 @@ async fn get_gateway_bond_annotated(
let gateways = cache
.gateways_annotated_filtered()
.await
.ok_or_else(|| ErrorResponse::new("no data available", Status::ServiceUnavailable))?;
.ok_or(ErrorResponse::new(
"no data available",
Status::ServiceUnavailable,
))?;
gateways
.into_iter()
+9
View File
@@ -3578,6 +3578,14 @@ dependencies = [
"pem",
]
[[package]]
name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"serde",
]
[[package]]
name = "nym-service-providers-common"
version = "0.1.0"
@@ -3826,6 +3834,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
"nym-service-provider-directory-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"prost",
+9
View File
@@ -2966,6 +2966,14 @@ dependencies = [
"pem",
]
[[package]]
name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"serde",
]
[[package]]
name = "nym-sphinx-types"
version = "0.2.0"
@@ -3025,6 +3033,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
"nym-service-provider-directory-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"prost",
@@ -114,6 +114,8 @@ mod sandbox {
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
@@ -149,6 +151,9 @@ mod sandbox {
group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS),
service_provider_directory_contract_address: parse_optional_str(
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
),
},
}
}
@@ -176,6 +181,8 @@ mod qa {
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
"n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
@@ -210,6 +217,9 @@ mod qa {
group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS),
service_provider_directory_contract_address: parse_optional_str(
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
),
},
}
}
-2
View File
@@ -59,7 +59,6 @@ fn main() {
mixnet::bond::bond_gateway,
mixnet::bond::bond_mixnode,
mixnet::bond::pledge_more,
mixnet::bond::decrease_pledge,
mixnet::bond::gateway_bond_details,
mixnet::bond::get_pending_operator_rewards,
mixnet::bond::mixnode_bond_details,
@@ -115,7 +114,6 @@ fn main() {
vesting::bond::vesting_bond_gateway,
vesting::bond::vesting_bond_mixnode,
vesting::bond::vesting_pledge_more,
vesting::bond::vesting_decrease_pledge,
vesting::bond::vesting_unbond_gateway,
vesting::bond::vesting_unbond_mixnode,
vesting::bond::vesting_update_mixnode_cost_params,
@@ -160,33 +160,6 @@ pub async fn pledge_more(
)?)
}
#[tauri::command]
pub async fn decrease_pledge(
fee: Option<Fee>,
decrease_by: DecCoin,
state: tauri::State<'_, WalletState>,
) -> Result<TransactionExecuteResult, BackendError> {
let guard = state.read().await;
let decrease_by_base = guard.attempt_convert_to_base_coin(decrease_by.clone())?;
let fee_amount = guard.convert_tx_fee(fee.as_ref());
log::info!(
">>> Decrease pledge, pledge_decrease_display = {}, pledge_decrease_base = {}, fee = {:?}",
decrease_by,
decrease_by_base,
fee,
);
let res = guard
.current_client()?
.nyxd
.decrease_pledge(decrease_by_base, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res, fee_amount,
)?)
}
#[tauri::command]
pub async fn unbond_mixnode(
fee: Option<Fee>,
@@ -151,33 +151,6 @@ pub async fn vesting_pledge_more(
)?)
}
#[tauri::command]
pub async fn vesting_decrease_pledge(
fee: Option<Fee>,
decrease_by: DecCoin,
state: tauri::State<'_, WalletState>,
) -> Result<TransactionExecuteResult, BackendError> {
let guard = state.read().await;
let decrease_by_base = guard.attempt_convert_to_base_coin(decrease_by.clone())?;
let fee_amount = guard.convert_tx_fee(fee.as_ref());
log::info!(
">>> Decrease pledge with locked tokens, pledge_decrease_display = {}, pledge_decrease_base = {}, fee = {:?}",
decrease_by,
decrease_by_base,
fee,
);
let res = guard
.current_client()?
.nyxd
.vesting_decrease_pledge(decrease_by_base, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res, fee_amount,
)?)
}
#[tauri::command]
pub async fn vesting_unbond_mixnode(
fee: Option<Fee>,
-4
View File
@@ -14,7 +14,6 @@ import {
TBondMixNodeArgs,
TBondMixnodeSignatureArgs,
TBondMoreArgs,
TDecreaseBondArgs,
} from '../types';
import { invokeWrapper } from './wrapper';
@@ -52,6 +51,3 @@ export const unbond = async (type: EnumNodeType) => {
};
export const bondMore = async (args: TBondMoreArgs) => invokeWrapper<TransactionExecuteResult>('pledge_more', args);
export const decreaseBond = async (args: TDecreaseBondArgs) =>
invokeWrapper<TransactionExecuteResult>('decrease_pledge', args);

Some files were not shown because too many files have changed in this diff Show More