Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e7200a7c8 | |||
| 3f0d4846df | |||
| 9bfcdbe8e2 | |||
| 8c4885ce2c | |||
| 926389df89 | |||
| b55db00408 | |||
| cfcb64f7e5 | |||
| 9c6c5f5170 | |||
| f28888e3e7 | |||
| 9549bed8bb | |||
| 7a50f0c3b2 | |||
| 2da6a2fbfa | |||
| d910a4e0ee | |||
| 672ab79421 |
@@ -30,6 +30,7 @@ jobs:
|
|||||||
continue-on-error: ${{ matrix.rust == 'nightly' }}
|
continue-on-error: ${{ matrix.rust == 'nightly' }}
|
||||||
needs: matrix_prep
|
needs: matrix_prep
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -63,4 +64,4 @@ jobs:
|
|||||||
if: ${{ matrix.rust != 'nightly' }}
|
if: ${{ matrix.rust != 'nightly' }}
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
|
args: --manifest-path contracts/Cargo.toml --workspace --all-targets -- -D warnings
|
||||||
|
|||||||
@@ -64,4 +64,4 @@ jobs:
|
|||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features -- -D warnings
|
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features --all-targets -- -D warnings
|
||||||
|
|||||||
Generated
+11
-10
@@ -3912,8 +3912,19 @@ name = "nym-socks5-client-core"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
|
"nym-bandwidth-controller",
|
||||||
"nym-client-core",
|
"nym-client-core",
|
||||||
|
"nym-config",
|
||||||
|
"nym-credential-storage",
|
||||||
|
"nym-network-defaults",
|
||||||
|
"nym-service-providers-common",
|
||||||
|
"nym-socks5-proxy-helpers",
|
||||||
|
"nym-socks5-requests",
|
||||||
|
"nym-sphinx",
|
||||||
|
"nym-task",
|
||||||
|
"nym-validator-client",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4307,15 +4318,6 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-src"
|
|
||||||
version = "111.25.2+1.1.1t"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "320708a054ad9b3bf314688b5db87cf4d6683d64cfc835e2337924ae62bf4431"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.82"
|
version = "0.9.82"
|
||||||
@@ -4325,7 +4327,6 @@ dependencies = [
|
|||||||
"autocfg 1.1.0",
|
"autocfg 1.1.0",
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
"openssl-src",
|
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ features = ["time"]
|
|||||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
||||||
version = "0.14"
|
version = "0.14"
|
||||||
|
|
||||||
[target."cfg(all(not(target_arch = \"wasm32\"),not(target_os = \"android\")))".dependencies.sqlx]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|||||||
@@ -50,10 +50,6 @@ itertools = { version = "0.10", optional = true }
|
|||||||
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
|
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
|
||||||
cosmwasm-std = { workspace = true, optional = true }
|
cosmwasm-std = { workspace = true, optional = true }
|
||||||
|
|
||||||
[target.'cfg(android)'.dependencies.reqwest]
|
|
||||||
version = "0.11"
|
|
||||||
features = ["json", "native-tls-vendored"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bip39 = { workspace = true }
|
bip39 = { workspace = true }
|
||||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32"] }
|
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32"] }
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ use nym_mixnet_contract_common::{
|
|||||||
MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse,
|
MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse,
|
||||||
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse,
|
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse,
|
||||||
PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse,
|
PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse,
|
||||||
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse,
|
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventResponse,
|
||||||
PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
|
PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse,
|
||||||
|
QueryMsg as MixnetQueryMsg,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
@@ -174,6 +175,16 @@ pub trait MixnetQueryClient {
|
|||||||
.await
|
.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(
|
async fn get_mixnode_rewarding_details(
|
||||||
&self,
|
&self,
|
||||||
mix_id: MixId,
|
mix_id: MixId,
|
||||||
@@ -374,13 +385,19 @@ pub trait MixnetQueryClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_mixnode_details_by_identity(
|
async fn get_pending_epoch_event(
|
||||||
&self,
|
&self,
|
||||||
mix_identity: IdentityKey,
|
event_id: EpochEventId,
|
||||||
) -> Result<Option<MixNodeDetails>, NyxdError> {
|
) -> Result<PendingEpochEventResponse, NyxdError> {
|
||||||
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
|
self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvent { event_id })
|
||||||
mix_identity,
|
.await
|
||||||
})
|
}
|
||||||
|
|
||||||
|
async fn get_pending_interval_event(
|
||||||
|
&self,
|
||||||
|
event_id: IntervalEventId,
|
||||||
|
) -> Result<PendingIntervalEventResponse, NyxdError> {
|
||||||
|
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvent { event_id })
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -331,6 +331,38 @@ pub trait MixnetSigningClient {
|
|||||||
.await
|
.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> {
|
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||||
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
|
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -91,6 +91,21 @@ pub trait VestingSigningClient {
|
|||||||
.await
|
.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_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError>;
|
||||||
|
|
||||||
async fn vesting_track_unbond_mixnode(
|
async fn vesting_track_unbond_mixnode(
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ use clap::{Args, Subcommand};
|
|||||||
pub mod rewards;
|
pub mod rewards;
|
||||||
|
|
||||||
pub mod delegate_to_mixnode;
|
pub mod delegate_to_mixnode;
|
||||||
pub mod pledge_more;
|
|
||||||
pub mod query_for_delegations;
|
pub mod query_for_delegations;
|
||||||
pub mod undelegate_from_mixnode;
|
pub mod undelegate_from_mixnode;
|
||||||
pub mod vesting_delegate_to_mixnode;
|
pub mod vesting_delegate_to_mixnode;
|
||||||
pub mod vesting_pledge_more;
|
|
||||||
pub mod vesting_undelegate_from_mixnode;
|
pub mod vesting_undelegate_from_mixnode;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
@@ -34,8 +32,4 @@ pub enum MixnetDelegatorsCommands {
|
|||||||
DelegateVesting(vesting_delegate_to_mixnode::Args),
|
DelegateVesting(vesting_delegate_to_mixnode::Args),
|
||||||
/// Undelegate from a mixnode (when originally using locked tokens)
|
/// Undelegate from a mixnode (when originally using locked tokens)
|
||||||
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
|
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
|
||||||
/// Pledge more
|
|
||||||
PledgeMore(pledge_more::Args),
|
|
||||||
/// Pledge more with locked tokens
|
|
||||||
PledgeMoreVesting(vesting_pledge_more::Args),
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ pub struct Args {
|
|||||||
pub version: Option<String>,
|
pub version: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn vesting_update_config(client: SigningClient, args: Args) {
|
pub async fn vesting_update_config(args: Args, client: SigningClient) {
|
||||||
info!("Update vesting gateway config!");
|
info!("Update vesting gateway config!");
|
||||||
|
|
||||||
let current_details = match client
|
let current_details = match client
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ pub struct Args {
|
|||||||
pub force: bool,
|
pub force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str) {
|
pub async fn vesting_bond_gateway(args: Args, client: SigningClient) {
|
||||||
|
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||||
|
|
||||||
info!("Starting vesting gateway bonding!");
|
info!("Starting vesting gateway bonding!");
|
||||||
|
|
||||||
// if we're trying to bond less than 1 token
|
// if we're trying to bond less than 1 token
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// 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,13 +4,17 @@
|
|||||||
use clap::{Args, Subcommand};
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
pub mod bond_mixnode;
|
pub mod bond_mixnode;
|
||||||
|
pub mod decrease_pledge;
|
||||||
pub mod families;
|
pub mod families;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
pub mod mixnode_bonding_sign_payload;
|
pub mod mixnode_bonding_sign_payload;
|
||||||
|
pub mod pledge_more;
|
||||||
pub mod rewards;
|
pub mod rewards;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod unbond_mixnode;
|
pub mod unbond_mixnode;
|
||||||
pub mod vesting_bond_mixnode;
|
pub mod vesting_bond_mixnode;
|
||||||
|
pub mod vesting_decrease_pledge;
|
||||||
|
pub mod vesting_pledge_more;
|
||||||
pub mod vesting_unbond_mixnode;
|
pub mod vesting_unbond_mixnode;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
@@ -40,4 +44,12 @@ pub enum MixnetOperatorsMixnodeCommands {
|
|||||||
UnbondVesting(vesting_unbond_mixnode::Args),
|
UnbondVesting(vesting_unbond_mixnode::Args),
|
||||||
/// Create base58-encoded payload required for producing valid bonding signature.
|
/// Create base58-encoded payload required for producing valid bonding signature.
|
||||||
CreateMixnodeBondingSignPayload(mixnode_bonding_sign_payload::Args),
|
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),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// 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,13 +1,16 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::{EpochState, IdentityKey, MixId};
|
use crate::{EpochEventId, EpochState, IdentityKey, MixId};
|
||||||
use contracts_common::signing::verifier::ApiVerifierError;
|
use contracts_common::signing::verifier::ApiVerifierError;
|
||||||
use cosmwasm_std::{Addr, Coin, Decimal};
|
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq)]
|
#[derive(Error, Debug, PartialEq)]
|
||||||
pub enum MixnetContractError {
|
pub enum MixnetContractError {
|
||||||
|
#[error("could not perform contract migration: {comment}")]
|
||||||
|
FailedMigration { comment: String },
|
||||||
|
|
||||||
#[error("{source}")]
|
#[error("{source}")]
|
||||||
StdErr {
|
StdErr {
|
||||||
#[from]
|
#[from]
|
||||||
@@ -26,6 +29,17 @@ pub enum MixnetContractError {
|
|||||||
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
|
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
|
||||||
InsufficientPledge { received: Coin, minimum: Coin },
|
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})")]
|
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
|
||||||
InsufficientDelegation { received: Coin, minimum: Coin },
|
InsufficientDelegation { received: Coin, minimum: Coin },
|
||||||
|
|
||||||
@@ -190,6 +204,9 @@ pub enum MixnetContractError {
|
|||||||
#[error("epoch duration must be > 0")]
|
#[error("epoch duration must be > 0")]
|
||||||
EpochDurationZero,
|
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}.")]
|
#[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")]
|
||||||
RewardingValidatorMismatch {
|
RewardingValidatorMismatch {
|
||||||
current_validator: Addr,
|
current_validator: Addr,
|
||||||
@@ -226,3 +243,11 @@ pub enum MixnetContractError {
|
|||||||
source: ApiVerifierError,
|
source: ApiVerifierError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MixnetContractError {
|
||||||
|
pub fn inconsistent_state<S: Into<String>>(comment: S) -> Self {
|
||||||
|
MixnetContractError::InconsistentState {
|
||||||
|
comment: comment.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ pub enum MixnetEventType {
|
|||||||
MixnodeBonding,
|
MixnodeBonding,
|
||||||
PendingPledgeIncrease,
|
PendingPledgeIncrease,
|
||||||
PledgeIncrease,
|
PledgeIncrease,
|
||||||
|
PendingPledgeDecrease,
|
||||||
|
PledgeDecrease,
|
||||||
GatewayBonding,
|
GatewayBonding,
|
||||||
GatewayUnbonding,
|
GatewayUnbonding,
|
||||||
PendingMixnodeUnbonding,
|
PendingMixnodeUnbonding,
|
||||||
@@ -58,6 +60,8 @@ impl ToString for MixnetEventType {
|
|||||||
MixnetEventType::MixnodeBonding => "mixnode_bonding",
|
MixnetEventType::MixnodeBonding => "mixnode_bonding",
|
||||||
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
|
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
|
||||||
MixnetEventType::PledgeIncrease => "pledge_increase",
|
MixnetEventType::PledgeIncrease => "pledge_increase",
|
||||||
|
MixnetEventType::PendingPledgeDecrease => "pending_pledge_decrease",
|
||||||
|
MixnetEventType::PledgeDecrease => "pledge_decrease",
|
||||||
MixnetEventType::GatewayBonding => "gateway_bonding",
|
MixnetEventType::GatewayBonding => "gateway_bonding",
|
||||||
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
|
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
|
||||||
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
|
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
|
||||||
@@ -354,6 +358,19 @@ pub fn new_pledge_increase_event(created_at: BlockHeight, mix_id: MixId, amount:
|
|||||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
.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 {
|
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event {
|
||||||
Event::new(MixnetEventType::MixnodeUnbonding)
|
Event::new(MixnetEventType::MixnodeUnbonding)
|
||||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
use crate::error::MixnetContractError;
|
use crate::error::MixnetContractError;
|
||||||
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
|
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
|
||||||
use crate::{EpochId, IntervalId, MixId};
|
use crate::{
|
||||||
|
EpochEventId, EpochId, IntervalEventId, IntervalId, MixId, PendingEpochEventData,
|
||||||
|
PendingIntervalEventData,
|
||||||
|
};
|
||||||
use cosmwasm_std::{Addr, Env};
|
use cosmwasm_std::{Addr, Env};
|
||||||
use schemars::gen::SchemaGenerator;
|
use schemars::gen::SchemaGenerator;
|
||||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||||
@@ -528,6 +531,30 @@ 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)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct NumberOfPendingEventsResponse {
|
pub struct NumberOfPendingEventsResponse {
|
||||||
pub epoch_events: u32,
|
pub epoch_events: u32,
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ pub use gateway::{
|
|||||||
};
|
};
|
||||||
pub use interval::{
|
pub use interval::{
|
||||||
CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse,
|
CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse,
|
||||||
PendingEpochEventsResponse, PendingIntervalEventsResponse,
|
PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEventResponse,
|
||||||
|
PendingIntervalEventsResponse,
|
||||||
};
|
};
|
||||||
pub use mixnode::{
|
pub use mixnode::{
|
||||||
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
|
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::helpers::IntoBaseDecimal;
|
|||||||
use crate::reward_params::{NodeRewardParams, RewardingParams};
|
use crate::reward_params::{NodeRewardParams, RewardingParams};
|
||||||
use crate::rewarding::helpers::truncate_reward;
|
use crate::rewarding::helpers::truncate_reward;
|
||||||
use crate::rewarding::RewardDistribution;
|
use crate::rewarding::RewardDistribution;
|
||||||
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
|
use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey};
|
||||||
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
|
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -37,13 +37,20 @@ impl RewardedSetNodeStatus {
|
|||||||
pub struct MixNodeDetails {
|
pub struct MixNodeDetails {
|
||||||
pub bond_information: MixNodeBond,
|
pub bond_information: MixNodeBond,
|
||||||
pub rewarding_details: MixNodeRewarding,
|
pub rewarding_details: MixNodeRewarding,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pending_changes: PendingMixNodeChanges,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MixNodeDetails {
|
impl MixNodeDetails {
|
||||||
pub fn new(bond_information: MixNodeBond, rewarding_details: MixNodeRewarding) -> Self {
|
pub fn new(
|
||||||
|
bond_information: MixNodeBond,
|
||||||
|
rewarding_details: MixNodeRewarding,
|
||||||
|
pending_changes: PendingMixNodeChanges,
|
||||||
|
) -> Self {
|
||||||
MixNodeDetails {
|
MixNodeDetails {
|
||||||
bond_information,
|
bond_information,
|
||||||
rewarding_details,
|
rewarding_details,
|
||||||
|
pending_changes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +80,10 @@ impl MixNodeDetails {
|
|||||||
pub fn total_stake(&self) -> Decimal {
|
pub fn total_stake(&self) -> Decimal {
|
||||||
self.rewarding_details.node_bond()
|
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)]
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||||
@@ -332,6 +343,22 @@ impl MixNodeRewarding {
|
|||||||
Ok(())
|
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(
|
pub fn increase_delegates_uint128(
|
||||||
&mut self,
|
&mut self,
|
||||||
amount: Uint128,
|
amount: Uint128,
|
||||||
@@ -601,6 +628,25 @@ 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", derive(ts_rs::TS))]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "generate-ts",
|
feature = "generate-ts",
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
|||||||
use crate::reward_params::{
|
use crate::reward_params::{
|
||||||
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
|
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
|
||||||
};
|
};
|
||||||
use crate::{delegation, ContractStateParams, Layer, LayerAssignment, MixId, Percent};
|
use crate::{
|
||||||
|
delegation, ContractStateParams, EpochEventId, IntervalEventId, Layer, LayerAssignment, MixId,
|
||||||
|
Percent,
|
||||||
|
};
|
||||||
use crate::{Gateway, IdentityKey, MixNode};
|
use crate::{Gateway, IdentityKey, MixNode};
|
||||||
use contracts_common::signing::MessageSignature;
|
use contracts_common::signing::MessageSignature;
|
||||||
use cosmwasm_std::Decimal;
|
use cosmwasm_std::{Coin, Decimal};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -161,6 +164,13 @@ pub enum ExecuteMsg {
|
|||||||
PledgeMoreOnBehalf {
|
PledgeMoreOnBehalf {
|
||||||
owner: String,
|
owner: String,
|
||||||
},
|
},
|
||||||
|
DecreasePledge {
|
||||||
|
decrease_by: Coin,
|
||||||
|
},
|
||||||
|
DecreasePledgeOnBehalf {
|
||||||
|
owner: String,
|
||||||
|
decrease_by: Coin,
|
||||||
|
},
|
||||||
UnbondMixnode {},
|
UnbondMixnode {},
|
||||||
UnbondMixnodeOnBehalf {
|
UnbondMixnodeOnBehalf {
|
||||||
owner: String,
|
owner: String,
|
||||||
@@ -297,6 +307,10 @@ impl ExecuteMsg {
|
|||||||
}
|
}
|
||||||
ExecuteMsg::PledgeMore {} => "pledging additional tokens".into(),
|
ExecuteMsg::PledgeMore {} => "pledging additional tokens".into(),
|
||||||
ExecuteMsg::PledgeMoreOnBehalf { .. } => "pledging additional tokens on behalf".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::UnbondMixnode { .. } => "unbonding mixnode".into(),
|
||||||
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
|
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
|
||||||
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
|
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
|
||||||
@@ -506,6 +520,12 @@ pub enum QueryMsg {
|
|||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
start_after: Option<u32>,
|
start_after: Option<u32>,
|
||||||
},
|
},
|
||||||
|
GetPendingEpochEvent {
|
||||||
|
event_id: EpochEventId,
|
||||||
|
},
|
||||||
|
GetPendingIntervalEvent {
|
||||||
|
event_id: IntervalEventId,
|
||||||
|
},
|
||||||
GetNumberOfPendingEvents {},
|
GetNumberOfPendingEvents {},
|
||||||
|
|
||||||
// signing-related
|
// signing-related
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ pub enum PendingEpochEventKind {
|
|||||||
mix_id: MixId,
|
mix_id: MixId,
|
||||||
amount: Coin,
|
amount: Coin,
|
||||||
},
|
},
|
||||||
|
DecreasePledge {
|
||||||
|
mix_id: MixId,
|
||||||
|
decrease_by: Coin,
|
||||||
|
},
|
||||||
UnbondMixnode {
|
UnbondMixnode {
|
||||||
mix_id: MixId,
|
mix_id: MixId,
|
||||||
},
|
},
|
||||||
@@ -66,7 +70,7 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct PendingIntervalEvent {
|
pub struct PendingIntervalEvent {
|
||||||
pub id: EpochEventId,
|
pub id: IntervalEventId,
|
||||||
pub event: PendingIntervalEventData,
|
pub event: PendingIntervalEventData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding";
|
||||||
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
|
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_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_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_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
|
||||||
pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config";
|
pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config";
|
||||||
@@ -22,6 +23,7 @@ pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
|
|||||||
"vesting_update_mixnode_cost_params";
|
"vesting_update_mixnode_cost_params";
|
||||||
|
|
||||||
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
|
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_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
|
||||||
pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation";
|
pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation";
|
||||||
pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward";
|
pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward";
|
||||||
@@ -118,6 +120,10 @@ pub fn new_vesting_pledge_more_event() -> Event {
|
|||||||
Event::new(VESTING_PLEDGE_MORE_EVENT_TYPE)
|
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 {
|
pub fn new_vesting_update_mixnode_config_event() -> Event {
|
||||||
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
|
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
|
||||||
}
|
}
|
||||||
@@ -146,6 +152,10 @@ pub fn new_track_mixnode_unbond_event() -> Event {
|
|||||||
Event::new(TRACK_MIXNODE_UNBOND_EVENT_TYPE)
|
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 {
|
pub fn new_track_gateway_unbond_event() -> Event {
|
||||||
Event::new(TRACK_GATEWAY_UNBOND_EVENT_TYPE)
|
Event::new(TRACK_GATEWAY_UNBOND_EVENT_TYPE)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,11 +123,18 @@ pub enum ExecuteMsg {
|
|||||||
PledgeMore {
|
PledgeMore {
|
||||||
amount: Coin,
|
amount: Coin,
|
||||||
},
|
},
|
||||||
|
DecreasePledge {
|
||||||
|
amount: Coin,
|
||||||
|
},
|
||||||
UnbondMixnode {},
|
UnbondMixnode {},
|
||||||
TrackUnbondMixnode {
|
TrackUnbondMixnode {
|
||||||
owner: String,
|
owner: String,
|
||||||
amount: Coin,
|
amount: Coin,
|
||||||
},
|
},
|
||||||
|
TrackDecreasePledge {
|
||||||
|
owner: String,
|
||||||
|
amount: Coin,
|
||||||
|
},
|
||||||
BondGateway {
|
BondGateway {
|
||||||
gateway: Gateway,
|
gateway: Gateway,
|
||||||
owner_signature: MessageSignature,
|
owner_signature: MessageSignature,
|
||||||
@@ -175,8 +182,10 @@ impl ExecuteMsg {
|
|||||||
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
|
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
|
||||||
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
|
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
|
||||||
ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore",
|
ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore",
|
||||||
|
ExecuteMsg::DecreasePledge { .. } => "VestingExecuteMsg::DecreasePledge",
|
||||||
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
|
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
|
||||||
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
|
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
|
||||||
|
ExecuteMsg::TrackDecreasePledge { .. } => "VestingExecuteMsg::TrackDecreasePledge",
|
||||||
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
|
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
|
||||||
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
|
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
|
||||||
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
|
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ tap = "1.0.1"
|
|||||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
|
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
|
||||||
|
|
||||||
nym-client-core = { path = "../client-core", features = ["fs-surb-storage"] }
|
nym-client-core = { path = "../client-core", features = ["fs-surb-storage"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies.nym-client-core]
|
|
||||||
path = "../client-core"
|
|
||||||
features = []
|
|
||||||
|
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||||
nym-config = { path = "../config" }
|
nym-config = { path = "../config" }
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ pub enum PendingEpochEventData {
|
|||||||
mix_id: MixId,
|
mix_id: MixId,
|
||||||
amount: DecCoin,
|
amount: DecCoin,
|
||||||
},
|
},
|
||||||
|
DecreasePledge {
|
||||||
|
mix_id: MixId,
|
||||||
|
decrease_by: DecCoin,
|
||||||
|
},
|
||||||
UnbondMixnode {
|
UnbondMixnode {
|
||||||
mix_id: MixId,
|
mix_id: MixId,
|
||||||
},
|
},
|
||||||
@@ -101,6 +105,13 @@ impl PendingEpochEventData {
|
|||||||
amount: reg.attempt_convert_to_display_dec_coin(amount.into())?,
|
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 } => {
|
MixnetContractPendingEpochEventKind::UnbondMixnode { mix_id } => {
|
||||||
Ok(PendingEpochEventData::UnbondMixnode { mix_id })
|
Ok(PendingEpochEventData::UnbondMixnode { mix_id })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[alias]
|
||||||
|
wasm = "build --target wasm32-unknown-unknown"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
rustflags = ["-C", "link-arg=-s"]
|
||||||
|
#target = "wasm32-unknown-unknown"
|
||||||
Generated
+16
@@ -949,6 +949,22 @@ dependencies = [
|
|||||||
"cfg-if",
|
"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]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ members = [
|
|||||||
"coconut-dkg",
|
"coconut-dkg",
|
||||||
"coconut-test",
|
"coconut-test",
|
||||||
"mixnet",
|
"mixnet",
|
||||||
|
"mixnet-vesting-integration-tests",
|
||||||
"multisig/cw3-flex-multisig",
|
"multisig/cw3-flex-multisig",
|
||||||
"multisig/cw4-group",
|
"multisig/cw4-group",
|
||||||
"service-provider-directory",
|
"service-provider-directory",
|
||||||
|
|||||||
@@ -86,9 +86,9 @@ mod tests {
|
|||||||
assert!(res.is_none());
|
assert!(res.is_none());
|
||||||
|
|
||||||
let mut spend_credential = SpendCredential::new(
|
let mut spend_credential = SpendCredential::new(
|
||||||
funds.clone(),
|
funds,
|
||||||
blind_serial_number.to_string(),
|
blind_serial_number.to_string(),
|
||||||
gateway_cosmos_address.clone(),
|
gateway_cosmos_address,
|
||||||
);
|
);
|
||||||
spend_credential.mark_as_spent();
|
spend_credential.mark_as_spent();
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>
|
|||||||
};
|
};
|
||||||
let env = mock_env();
|
let env = mock_env();
|
||||||
let info = mock_info("creator", &[]);
|
let info = mock_info("creator", &[]);
|
||||||
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
instantiate(deps.as_mut(), env, info, msg).unwrap();
|
||||||
deps
|
deps
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let info = mock_info("requester", &[coin]);
|
let info = mock_info("requester", &[coin]);
|
||||||
|
|
||||||
let tx = deposit_funds(deps.as_mut(), env.clone(), info, data).unwrap();
|
let tx = deposit_funds(deps.as_mut(), env, info, data).unwrap();
|
||||||
|
|
||||||
let events: Vec<_> = tx
|
let events: Vec<_> = tx
|
||||||
.events
|
.events
|
||||||
@@ -246,13 +246,8 @@ mod tests {
|
|||||||
|
|
||||||
deps.querier
|
deps.querier
|
||||||
.update_balance(env.contract.address.clone(), vec![funds.clone()]);
|
.update_balance(env.contract.address.clone(), vec![funds.clone()]);
|
||||||
let err = release_funds(
|
let err =
|
||||||
deps.as_mut(),
|
release_funds(deps.as_mut(), env, mock_info(invalid_admin, &[]), funds).unwrap_err();
|
||||||
env.clone(),
|
|
||||||
mock_info(invalid_admin, &[]),
|
|
||||||
funds.clone(),
|
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
assert_eq!(err, ContractError::Admin(AdminError::NotAdmin {}));
|
assert_eq!(err, ContractError::Admin(AdminError::NotAdmin {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +289,7 @@ mod tests {
|
|||||||
{
|
{
|
||||||
assert_eq!(contract_addr, MULTISIG_CONTRACT);
|
assert_eq!(contract_addr, MULTISIG_CONTRACT);
|
||||||
assert!(funds.is_empty());
|
assert!(funds.is_empty());
|
||||||
let multisig_msg: MultisigExecuteMsg = from_binary(&msg).unwrap();
|
let multisig_msg: MultisigExecuteMsg = from_binary(msg).unwrap();
|
||||||
if let MultisigExecuteMsg::Propose {
|
if let MultisigExecuteMsg::Propose {
|
||||||
title: _,
|
title: _,
|
||||||
description,
|
description,
|
||||||
@@ -312,7 +307,7 @@ mod tests {
|
|||||||
{
|
{
|
||||||
assert_eq!(*contract_addr, env.contract.address.into_string());
|
assert_eq!(*contract_addr, env.contract.address.into_string());
|
||||||
assert!(funds.is_empty());
|
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 {
|
if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req {
|
||||||
assert_eq!(funds, *data.funds());
|
assert_eq!(funds, *data.funds());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let info = mock_info("creator", &[]);
|
let info = mock_info("creator", &[]);
|
||||||
|
|
||||||
let res = instantiate(deps.as_mut(), env.clone(), info, msg);
|
let res = instantiate(deps.as_mut(), env, info, msg);
|
||||||
assert!(res.is_ok())
|
assert!(res.is_ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ mod tests {
|
|||||||
announce_address: "127.0.0.1:8000".to_string(),
|
announce_address: "127.0.0.1:8000".to_string(),
|
||||||
resharing: false,
|
resharing: false,
|
||||||
},
|
},
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(parse_node_index(res), (idx + 1) as u64);
|
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(),
|
announce_address: "127.0.0.1:8000".to_string(),
|
||||||
resharing: false,
|
resharing: false,
|
||||||
},
|
},
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert_eq!(ContractError::AlreadyADealer, err.downcast().unwrap());
|
assert_eq!(ContractError::AlreadyADealer, err.downcast().unwrap());
|
||||||
@@ -269,13 +269,13 @@ mod tests {
|
|||||||
let err = app
|
let err = app
|
||||||
.execute_contract(
|
.execute_contract(
|
||||||
unauthorized_member,
|
unauthorized_member,
|
||||||
coconut_dkg_contract_addr.clone(),
|
coconut_dkg_contract_addr,
|
||||||
&RegisterDealer {
|
&RegisterDealer {
|
||||||
bte_key_with_proof: "bte_key_with_proof".to_string(),
|
bte_key_with_proof: "bte_key_with_proof".to_string(),
|
||||||
announce_address: "127.0.0.1:8000".to_string(),
|
announce_address: "127.0.0.1:8000".to_string(),
|
||||||
resharing: false,
|
resharing: false,
|
||||||
},
|
},
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert_eq!(ContractError::Unauthorized, err.downcast().unwrap());
|
assert_eq!(ContractError::Unauthorized, err.downcast().unwrap());
|
||||||
|
|||||||
@@ -153,9 +153,9 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
let ret = try_add_dealer(
|
let ret = try_add_dealer(
|
||||||
deps.as_mut(),
|
deps.as_mut(),
|
||||||
info.clone(),
|
info,
|
||||||
bte_key_with_proof.clone(),
|
bte_key_with_proof,
|
||||||
announce_address.clone(),
|
announce_address,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|||||||
@@ -56,11 +56,11 @@ pub(crate) mod tests {
|
|||||||
for n in 0..size {
|
for n in 0..size {
|
||||||
let dealing_share = dealing_bytes_fixture();
|
let dealing_share = dealing_bytes_fixture();
|
||||||
let sender = Addr::unchecked(format!("owner{}", n));
|
let sender = Addr::unchecked(format!("owner{}", n));
|
||||||
for idx in 0..TOTAL_DEALINGS {
|
(0..TOTAL_DEALINGS).for_each(|idx| {
|
||||||
DEALINGS_BYTES[idx]
|
DEALINGS_BYTES[idx]
|
||||||
.save(deps.storage, &sender, &dealing_share)
|
.save(deps.storage, &sender, &dealing_share)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,8 +131,7 @@ pub(crate) mod tests {
|
|||||||
assert!(ret.is_ok());
|
assert!(ret.is_ok());
|
||||||
assert!(dealings.has(deps.as_mut().storage, &owner));
|
assert!(dealings.has(deps.as_mut().storage, &owner));
|
||||||
}
|
}
|
||||||
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true)
|
let ret = try_commit_dealings(deps.as_mut(), info, dealing_bytes, true).unwrap_err();
|
||||||
.unwrap_err();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ret,
|
ret,
|
||||||
ContractError::AlreadyCommitted {
|
ContractError::AlreadyCommitted {
|
||||||
|
|||||||
@@ -236,10 +236,10 @@ pub(crate) mod tests {
|
|||||||
let limit = *limits.next().unwrap();
|
let limit = *limits.next().unwrap();
|
||||||
{
|
{
|
||||||
let mut group_members = GROUP_MEMBERS.lock().unwrap();
|
let mut group_members = GROUP_MEMBERS.lock().unwrap();
|
||||||
for i in 0..n as usize {
|
for dealer in dealers.iter() {
|
||||||
group_members.push((
|
group_members.push((
|
||||||
Member {
|
Member {
|
||||||
addr: dealers[i].address.to_string(),
|
addr: dealer.address.to_string(),
|
||||||
weight: 10,
|
weight: 10,
|
||||||
},
|
},
|
||||||
1,
|
1,
|
||||||
@@ -339,7 +339,7 @@ pub(crate) mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
for i in 0..3 as u64 {
|
for i in 0..3_u64 {
|
||||||
let details = dealer_details_fixture(i + 1);
|
let details = dealer_details_fixture(i + 1);
|
||||||
current_dealers()
|
current_dealers()
|
||||||
.save(deps.as_mut().storage, &details.address, &details)
|
.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 env = mock_env();
|
||||||
let info = mock_info(ADMIN_ADDRESS, &[]);
|
let info = mock_info(ADMIN_ADDRESS, &[]);
|
||||||
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
instantiate(deps.as_mut(), env, info, msg).unwrap();
|
||||||
deps
|
deps
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,13 +129,7 @@ mod tests {
|
|||||||
.save(deps.as_mut().storage, &dealer, &dealer_details)
|
.save(deps.as_mut().storage, &dealer, &dealer_details)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
try_commit_verification_key_share(
|
try_commit_verification_key_share(deps.as_mut(), env, info.clone(), share.clone(), false)
|
||||||
deps.as_mut(),
|
|
||||||
env.clone(),
|
|
||||||
info.clone(),
|
|
||||||
share.clone(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap();
|
let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -215,14 +209,8 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let ret = try_commit_verification_key_share(
|
let ret =
|
||||||
deps.as_mut(),
|
try_commit_verification_key_share(deps.as_mut(), env, info, share, false).unwrap_err();
|
||||||
env.clone(),
|
|
||||||
info.clone(),
|
|
||||||
share.clone(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ret,
|
ret,
|
||||||
ContractError::AlreadyCommitted {
|
ContractError::AlreadyCommitted {
|
||||||
@@ -318,14 +306,7 @@ mod tests {
|
|||||||
dealers_storage::current_dealers()
|
dealers_storage::current_dealers()
|
||||||
.save(deps.as_mut().storage, &owner, &dealer_details)
|
.save(deps.as_mut().storage, &owner, &dealer_details)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
try_commit_verification_key_share(
|
try_commit_verification_key_share(deps.as_mut(), env.clone(), info, share, false).unwrap();
|
||||||
deps.as_mut(),
|
|
||||||
env.clone(),
|
|
||||||
info.clone(),
|
|
||||||
share.clone(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
env.block.time = env
|
env.block.time = env
|
||||||
.block
|
.block
|
||||||
@@ -338,7 +319,6 @@ mod tests {
|
|||||||
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
|
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
|
||||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||||
|
|
||||||
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
|
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner, false).unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,12 +89,7 @@ fn deposit_and_release() {
|
|||||||
let msg = ExecuteMsg::ReleaseFunds {
|
let msg = ExecuteMsg::ReleaseFunds {
|
||||||
funds: deposit_funds[0].clone(),
|
funds: deposit_funds[0].clone(),
|
||||||
};
|
};
|
||||||
app.execute_contract(
|
app.execute_contract(Addr::unchecked(multisig_addr), contract_addr, &msg, &[])
|
||||||
Addr::unchecked(multisig_addr),
|
|
||||||
contract_addr.clone(),
|
|
||||||
&msg,
|
|
||||||
&[],
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap();
|
let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap();
|
||||||
assert_eq!(pool_bal, deposit_funds[0]);
|
assert_eq!(pool_bal, deposit_funds[0]);
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ fn spend_credential_creates_proposal() {
|
|||||||
Addr::unchecked(OWNER),
|
Addr::unchecked(OWNER),
|
||||||
coconut_bandwidth_contract_addr.clone(),
|
coconut_bandwidth_contract_addr.clone(),
|
||||||
&msg,
|
&msg,
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let proposal_id = res
|
let proposal_id = res
|
||||||
@@ -124,7 +124,7 @@ fn spend_credential_creates_proposal() {
|
|||||||
Addr::unchecked(OWNER),
|
Addr::unchecked(OWNER),
|
||||||
coconut_bandwidth_contract_addr.clone(),
|
coconut_bandwidth_contract_addr.clone(),
|
||||||
&msg,
|
&msg,
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -142,9 +142,9 @@ fn spend_credential_creates_proposal() {
|
|||||||
let res = app
|
let res = app
|
||||||
.execute_contract(
|
.execute_contract(
|
||||||
Addr::unchecked(OWNER),
|
Addr::unchecked(OWNER),
|
||||||
coconut_bandwidth_contract_addr.clone(),
|
coconut_bandwidth_contract_addr,
|
||||||
&msg,
|
&msg,
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let proposal_id = res
|
let proposal_id = res
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ fn dkg_proposal() {
|
|||||||
announce_address: "127.0.0.1:8000".to_string(),
|
announce_address: "127.0.0.1:8000".to_string(),
|
||||||
resharing: false,
|
resharing: false,
|
||||||
},
|
},
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ fn dkg_proposal() {
|
|||||||
Addr::unchecked(OWNER),
|
Addr::unchecked(OWNER),
|
||||||
coconut_dkg_contract_addr.clone(),
|
coconut_dkg_contract_addr.clone(),
|
||||||
&AdvanceEpochState {},
|
&AdvanceEpochState {},
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ fn dkg_proposal() {
|
|||||||
Addr::unchecked(MEMBER1),
|
Addr::unchecked(MEMBER1),
|
||||||
coconut_dkg_contract_addr.clone(),
|
coconut_dkg_contract_addr.clone(),
|
||||||
&msg,
|
&msg,
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ fn dkg_proposal() {
|
|||||||
proposal_id,
|
proposal_id,
|
||||||
vote: cw3::Vote::Yes,
|
vote: cw3::Vote::Yes,
|
||||||
},
|
},
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -184,16 +184,16 @@ fn dkg_proposal() {
|
|||||||
Addr::unchecked(OWNER),
|
Addr::unchecked(OWNER),
|
||||||
coconut_dkg_contract_addr.clone(),
|
coconut_dkg_contract_addr.clone(),
|
||||||
&AdvanceEpochState {},
|
&AdvanceEpochState {},
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.execute_contract(
|
app.execute_contract(
|
||||||
Addr::unchecked(MEMBER1),
|
Addr::unchecked(MEMBER1),
|
||||||
multisig_contract_addr.clone(),
|
multisig_contract_addr,
|
||||||
&Execute { proposal_id },
|
&Execute { proposal_id },
|
||||||
&vec![],
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
[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"
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// 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),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
pub mod fixtures;
|
||||||
|
pub mod helpers;
|
||||||
|
pub mod setup;
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
// 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 ¤t_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,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
mod decrease_mixnode_pledge;
|
||||||
|
mod support;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
allow-unwrap-in-tests = true
|
||||||
|
allow-expect-in-tests = true
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use cosmwasm_std::Uint128;
|
use cosmwasm_std::Uint128;
|
||||||
@@ -61,6 +61,7 @@ pub const CONTRACT_STATE_KEY: &str = "state";
|
|||||||
|
|
||||||
pub const LAYER_DISTRIBUTION_KEY: &str = "layers";
|
pub const LAYER_DISTRIBUTION_KEY: &str = "layers";
|
||||||
pub const NODE_ID_COUNTER_KEY: &str = "nic";
|
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_PK_NAMESPACE: &str = "mnn";
|
||||||
pub const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
|
pub const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
|
||||||
pub const MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "mni";
|
pub const MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "mni";
|
||||||
|
|||||||
@@ -251,6 +251,18 @@ pub fn execute(
|
|||||||
ExecuteMsg::PledgeMoreOnBehalf { owner } => {
|
ExecuteMsg::PledgeMoreOnBehalf { owner } => {
|
||||||
crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, 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 {} => {
|
ExecuteMsg::UnbondMixnode {} => {
|
||||||
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
|
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
|
||||||
}
|
}
|
||||||
@@ -573,6 +585,12 @@ pub fn query(
|
|||||||
limit,
|
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(
|
QueryMsg::GetNumberOfPendingEvents {} => to_binary(
|
||||||
&crate::interval::queries::query_number_of_pending_events(deps)?,
|
&crate::interval::queries::query_number_of_pending_events(deps)?,
|
||||||
),
|
),
|
||||||
@@ -586,7 +604,7 @@ pub fn query(
|
|||||||
|
|
||||||
#[entry_point]
|
#[entry_point]
|
||||||
pub fn migrate(
|
pub fn migrate(
|
||||||
deps: DepsMut<'_>,
|
mut deps: DepsMut<'_>,
|
||||||
_env: Env,
|
_env: Env,
|
||||||
msg: MigrateMsg,
|
msg: MigrateMsg,
|
||||||
) -> Result<Response, MixnetContractError> {
|
) -> Result<Response, MixnetContractError> {
|
||||||
@@ -612,6 +630,7 @@ pub fn migrate(
|
|||||||
|
|
||||||
// If state structure changed in any contract version in the way migration is needed, it
|
// 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::`
|
// 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
|
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ mod test {
|
|||||||
test.deps_mut(),
|
test.deps_mut(),
|
||||||
mock_info(illegal_proxy.as_ref(), &[]),
|
mock_info(illegal_proxy.as_ref(), &[]),
|
||||||
new_member.to_string(),
|
new_member.to_string(),
|
||||||
family_head.clone(),
|
family_head,
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ pub mod tests {
|
|||||||
let res = try_add_gateway(
|
let res = try_add_gateway(
|
||||||
test.deps_mut(),
|
test.deps_mut(),
|
||||||
env.clone(),
|
env.clone(),
|
||||||
info.clone(),
|
info,
|
||||||
gateway.clone(),
|
gateway.clone(),
|
||||||
signature.clone(),
|
signature.clone(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// 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;
|
||||||
use crate::delegations::storage as delegations_storage;
|
use crate::delegations::storage as delegations_storage;
|
||||||
use crate::interval::helpers::change_interval_config;
|
use crate::interval::helpers::change_interval_config;
|
||||||
@@ -9,20 +25,6 @@ use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_
|
|||||||
use crate::mixnodes::storage as mixnodes_storage;
|
use crate::mixnodes::storage as mixnodes_storage;
|
||||||
use crate::rewards::storage as rewards_storage;
|
use crate::rewards::storage as rewards_storage;
|
||||||
use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking};
|
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 {
|
pub(crate) trait ContractExecutableEvent {
|
||||||
// note: the error only means a HARD error like we failed to read from storage.
|
// note: the error only means a HARD error like we failed to read from storage.
|
||||||
@@ -146,10 +148,9 @@ pub(crate) fn undelegate(
|
|||||||
Some(delegation) => delegation,
|
Some(delegation) => delegation,
|
||||||
};
|
};
|
||||||
let mix_rewarding =
|
let mix_rewarding =
|
||||||
rewards_storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::InconsistentState {
|
rewards_storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::inconsistent_state(
|
||||||
comment: "mixnode rewarding got removed from the storage whilst there's still an existing delegation"
|
"mixnode rewarding got removed from the storage whilst there's still an existing delegation",
|
||||||
.into(),
|
))?;
|
||||||
})?;
|
|
||||||
// this also appropriately adjusts the storage
|
// this also appropriately adjusts the storage
|
||||||
let tokens_to_return =
|
let tokens_to_return =
|
||||||
delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?;
|
delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?;
|
||||||
@@ -180,11 +181,15 @@ pub(crate) fn unbond_mixnode(
|
|||||||
// in unbonding state and thus nothing could have been done to it (such as attempting to double unbond it)
|
// 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.
|
// 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(
|
let node_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
|
||||||
MixnetContractError::InconsistentState {
|
MixnetContractError::inconsistent_state(
|
||||||
comment: "mixnode getting processed to get unbonded doesn't exist in the storage"
|
"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
|
// 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;
|
let rewarding_denom = &node_details.bond_information.original_pledge.denom;
|
||||||
@@ -244,12 +249,15 @@ pub(crate) fn increase_pledge(
|
|||||||
// the target node MUST exist - we have checked it at the time of putting this event onto the queue
|
// 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
|
// we have also verified there were no preceding unbond events
|
||||||
let mix_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
|
let mix_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
|
||||||
MixnetContractError::InconsistentState {
|
MixnetContractError::inconsistent_state(
|
||||||
comment:
|
"mixnode getting processed to increase its pledge doesn't exist in the storage",
|
||||||
"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_bond = mix_details.bond_information.clone();
|
||||||
let mut updated_rewarding = mix_details.rewarding_details;
|
let mut updated_rewarding = mix_details.rewarding_details;
|
||||||
@@ -257,7 +265,10 @@ pub(crate) fn increase_pledge(
|
|||||||
updated_bond.original_pledge.amount += increase.amount;
|
updated_bond.original_pledge.amount += increase.amount;
|
||||||
updated_rewarding.increase_operator_uint128(increase.amount)?;
|
updated_rewarding.increase_operator_uint128(increase.amount)?;
|
||||||
|
|
||||||
// update both, bond information and rewarding details
|
let mut pending_changes = mix_details.pending_changes;
|
||||||
|
pending_changes.pledge_change = None;
|
||||||
|
|
||||||
|
// update all: bond information, rewarding details and pending pledge changes
|
||||||
mixnodes_storage::mixnode_bonds().replace(
|
mixnodes_storage::mixnode_bonds().replace(
|
||||||
deps.storage,
|
deps.storage,
|
||||||
mix_id,
|
mix_id,
|
||||||
@@ -265,10 +276,70 @@ pub(crate) fn increase_pledge(
|
|||||||
Some(&mix_details.bond_information),
|
Some(&mix_details.bond_information),
|
||||||
)?;
|
)?;
|
||||||
rewards_storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &updated_rewarding)?;
|
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)))
|
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 {
|
impl ContractExecutableEvent for PendingEpochEventData {
|
||||||
fn execute(self, deps: DepsMut<'_>, env: &Env) -> Result<Response, MixnetContractError> {
|
fn execute(self, deps: DepsMut<'_>, env: &Env) -> Result<Response, MixnetContractError> {
|
||||||
// note that the basic validation on all those events was already performed before
|
// note that the basic validation on all those events was already performed before
|
||||||
@@ -288,6 +359,10 @@ impl ContractExecutableEvent for PendingEpochEventData {
|
|||||||
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
|
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
|
||||||
increase_pledge(deps, self.created_at, 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 } => {
|
PendingEpochEventKind::UnbondMixnode { mix_id } => {
|
||||||
unbond_mixnode(deps, env, self.created_at, mix_id)
|
unbond_mixnode(deps, env, self.created_at, mix_id)
|
||||||
}
|
}
|
||||||
@@ -397,26 +472,33 @@ impl ContractExecutableEvent for PendingIntervalEventData {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use cosmwasm_std::Decimal;
|
||||||
|
|
||||||
|
use mixnet_contract_common::Percent;
|
||||||
|
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||||
|
|
||||||
use crate::support::tests::test_helpers;
|
use crate::support::tests::test_helpers;
|
||||||
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
|
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
|
||||||
use cosmwasm_std::Decimal;
|
|
||||||
use mixnet_contract_common::Percent;
|
use super::*;
|
||||||
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
|
// note that authorization and basic validation has already been performed for all of those
|
||||||
// before being pushed onto the event queues
|
// before being pushed onto the event queues
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod delegating {
|
mod delegating {
|
||||||
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;
|
||||||
|
|
||||||
use crate::mixnodes::transactions::try_remove_mixnode;
|
use crate::mixnodes::transactions::try_remove_mixnode;
|
||||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||||
use cosmwasm_std::testing::mock_info;
|
|
||||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, Decimal, WasmMsg};
|
use super::*;
|
||||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn returns_the_tokens_if_mixnode_has_unbonded() {
|
fn returns_the_tokens_if_mixnode_has_unbonded() {
|
||||||
@@ -829,7 +911,7 @@ mod tests {
|
|||||||
res_other_proxy,
|
res_other_proxy,
|
||||||
MixnetContractError::ProxyIsNotVestingContract {
|
MixnetContractError::ProxyIsNotVestingContract {
|
||||||
received: dummy_proxy,
|
received: dummy_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -837,11 +919,14 @@ mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod undelegating {
|
mod undelegating {
|
||||||
use super::*;
|
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
|
||||||
|
|
||||||
|
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||||
|
|
||||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
|
|
||||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn doesnt_return_any_tokens_if_it_doesnt_exist() {
|
fn doesnt_return_any_tokens_if_it_doesnt_exist() {
|
||||||
@@ -1021,7 +1106,7 @@ mod tests {
|
|||||||
res_other_proxy,
|
res_other_proxy,
|
||||||
MixnetContractError::ProxyIsNotVestingContract {
|
MixnetContractError::ProxyIsNotVestingContract {
|
||||||
received: dummy_proxy,
|
received: dummy_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1029,13 +1114,17 @@ mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod mixnode_unbonding {
|
mod mixnode_unbonding {
|
||||||
use super::*;
|
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 crate::mixnodes::storage as mixnodes_storage;
|
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::fixtures::TEST_COIN_DENOM;
|
||||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
|
|
||||||
use mixnet_contract_common::mixnode::UnbondedMixnode;
|
use super::*;
|
||||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
||||||
@@ -1050,6 +1139,71 @@ 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]
|
#[test]
|
||||||
fn returns_original_pledge_alongside_any_earned_rewards() {
|
fn returns_original_pledge_alongside_any_earned_rewards() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
@@ -1178,7 +1332,7 @@ mod tests {
|
|||||||
res_other_proxy,
|
res_other_proxy,
|
||||||
MixnetContractError::ProxyIsNotVestingContract {
|
MixnetContractError::ProxyIsNotVestingContract {
|
||||||
received: dummy_proxy,
|
received: dummy_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1186,10 +1340,12 @@ mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod increasing_pledge {
|
mod increasing_pledge {
|
||||||
use super::*;
|
|
||||||
use cosmwasm_std::Uint128;
|
use cosmwasm_std::Uint128;
|
||||||
|
|
||||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
||||||
// this should have never happened so hard error MUST be thrown here
|
// this should have never happened so hard error MUST be thrown here
|
||||||
@@ -1203,10 +1359,27 @@ 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]
|
#[test]
|
||||||
fn updates_stored_bond_information_and_rewarding_details() {
|
fn updates_stored_bond_information_and_rewarding_details() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
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)
|
let old_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -1239,6 +1412,8 @@ mod tests {
|
|||||||
let pledge3 = Uint128::new(200_000_000);
|
let pledge3 = Uint128::new(200_000_000);
|
||||||
|
|
||||||
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
|
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());
|
let increase = test.coin(pledge2.u128());
|
||||||
increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap();
|
increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap();
|
||||||
|
|
||||||
@@ -1274,6 +1449,7 @@ mod tests {
|
|||||||
let pledge2 = Uint128::new(50_000_000_000);
|
let pledge2 = Uint128::new(50_000_000_000);
|
||||||
|
|
||||||
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
|
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("alice", 123_456_789_000u128, mix_id_repledge);
|
||||||
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
|
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
|
||||||
@@ -1346,6 +1522,7 @@ mod tests {
|
|||||||
let pledge2 = Uint128::new(50_000_000_000);
|
let pledge2 = Uint128::new(50_000_000_000);
|
||||||
|
|
||||||
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
|
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("alice", 123_456_789_000u128, mix_id_repledge);
|
||||||
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
|
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
|
||||||
@@ -1420,6 +1597,373 @@ mod tests {
|
|||||||
assert_eq!(dist1, dist2)
|
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]
|
#[test]
|
||||||
@@ -1439,11 +1983,14 @@ mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod changing_mix_cost_params {
|
mod changing_mix_cost_params {
|
||||||
use super::*;
|
|
||||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
|
||||||
use cosmwasm_std::coin;
|
use cosmwasm_std::coin;
|
||||||
|
|
||||||
use mixnet_contract_common::Percent;
|
use mixnet_contract_common::Percent;
|
||||||
|
|
||||||
|
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn doesnt_do_anything_if_mixnode_has_unbonded() {
|
fn doesnt_do_anything_if_mixnode_has_unbonded() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
@@ -1480,7 +2027,7 @@ mod tests {
|
|||||||
Response::new().add_event(new_mixnode_cost_params_update_event(
|
Response::new().add_event(new_mixnode_cost_params_update_event(
|
||||||
123,
|
123,
|
||||||
mix_id,
|
mix_id,
|
||||||
&new_params
|
&new_params,
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use mixnet_contract_common::error::MixnetContractError;
|
|||||||
use mixnet_contract_common::pending_events::{PendingEpochEvent, PendingIntervalEvent};
|
use mixnet_contract_common::pending_events::{PendingEpochEvent, PendingIntervalEvent};
|
||||||
use mixnet_contract_common::{
|
use mixnet_contract_common::{
|
||||||
CurrentIntervalResponse, EpochEventId, EpochStatus, IntervalEventId, MixId,
|
CurrentIntervalResponse, EpochEventId, EpochStatus, IntervalEventId, MixId,
|
||||||
NumberOfPendingEventsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse,
|
NumberOfPendingEventsResponse, PagedRewardedSetResponse, PendingEpochEventResponse,
|
||||||
PendingIntervalEventsResponse,
|
PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn query_epoch_status(deps: Deps<'_>) -> StdResult<EpochStatus> {
|
pub fn query_epoch_status(deps: Deps<'_>) -> StdResult<EpochStatus> {
|
||||||
@@ -112,6 +112,22 @@ 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(
|
pub fn query_number_of_pending_events(
|
||||||
deps: Deps<'_>,
|
deps: Deps<'_>,
|
||||||
) -> Result<NumberOfPendingEventsResponse, MixnetContractError> {
|
) -> Result<NumberOfPendingEventsResponse, MixnetContractError> {
|
||||||
@@ -540,6 +556,88 @@ 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]
|
#[test]
|
||||||
fn querying_for_number_of_pending_events() {
|
fn querying_for_number_of_pending_events() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ pub(crate) fn push_new_epoch_event(
|
|||||||
storage: &mut dyn Storage,
|
storage: &mut dyn Storage,
|
||||||
env: &Env,
|
env: &Env,
|
||||||
event: PendingEpochEventKind,
|
event: PendingEpochEventKind,
|
||||||
) -> StdResult<()> {
|
) -> StdResult<EpochEventId> {
|
||||||
// not included in non-test code as it messes with our return types as we expected `StdResult`
|
// not included in non-test code as it messes with our return types as we expected `StdResult`
|
||||||
// from all storage-related operations.
|
// from all storage-related operations.
|
||||||
// However, the callers MUST HAVE ensured the below invariant
|
// However, the callers MUST HAVE ensured the below invariant
|
||||||
@@ -90,14 +90,15 @@ pub(crate) fn push_new_epoch_event(
|
|||||||
|
|
||||||
let event_id = next_epoch_event_id_counter(storage)?;
|
let event_id = next_epoch_event_id_counter(storage)?;
|
||||||
let event_data = event.attach_source_height(env.block.height);
|
let event_data = event.attach_source_height(env.block.height);
|
||||||
PENDING_EPOCH_EVENTS.save(storage, event_id, &event_data)
|
PENDING_EPOCH_EVENTS.save(storage, event_id, &event_data)?;
|
||||||
|
Ok(event_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn push_new_interval_event(
|
pub(crate) fn push_new_interval_event(
|
||||||
storage: &mut dyn Storage,
|
storage: &mut dyn Storage,
|
||||||
env: &Env,
|
env: &Env,
|
||||||
event: PendingIntervalEventKind,
|
event: PendingIntervalEventKind,
|
||||||
) -> StdResult<()> {
|
) -> StdResult<IntervalEventId> {
|
||||||
// not included in non-test code as it messes with our return types as we expected `StdResult`
|
// not included in non-test code as it messes with our return types as we expected `StdResult`
|
||||||
// from all storage-related operations.
|
// from all storage-related operations.
|
||||||
// However, the callers MUST HAVE ensured the below invariant
|
// However, the callers MUST HAVE ensured the below invariant
|
||||||
@@ -106,7 +107,8 @@ pub(crate) fn push_new_interval_event(
|
|||||||
|
|
||||||
let event_id = next_interval_event_id_counter(storage)?;
|
let event_id = next_interval_event_id_counter(storage)?;
|
||||||
let event_data = event.attach_source_height(env.block.height);
|
let event_data = event.attach_source_height(env.block.height);
|
||||||
PENDING_INTERVAL_EVENTS.save(storage, event_id, &event_data)
|
PENDING_INTERVAL_EVENTS.save(storage, event_id, &event_data)?;
|
||||||
|
Ok(event_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_rewarded_set(
|
pub(crate) fn update_rewarded_set(
|
||||||
@@ -168,8 +170,11 @@ pub(crate) fn initialise_storage(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::support::tests::fixtures;
|
||||||
|
use crate::support::tests::test_helpers::TestSetup;
|
||||||
use cosmwasm_std::testing::mock_dependencies;
|
use cosmwasm_std::testing::mock_dependencies;
|
||||||
use cosmwasm_std::Order;
|
use cosmwasm_std::Order;
|
||||||
|
use rand_chacha::rand_core::RngCore;
|
||||||
|
|
||||||
fn read_entire_set(storage: &mut dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> {
|
fn read_entire_set(storage: &mut dyn Storage) -> HashMap<MixId, RewardedSetNodeStatus> {
|
||||||
REWARDED_SET
|
REWARDED_SET
|
||||||
@@ -211,4 +216,62 @@ mod tests {
|
|||||||
assert!(current_set.get(&7).is_none());
|
assert!(current_set.get(&7).is_none());
|
||||||
assert!(current_set.get(&1).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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use mixnet_contract_common::error::MixnetContractError;
|
|||||||
use mixnet_contract_common::mixnode::{
|
use mixnet_contract_common::mixnode::{
|
||||||
MixNodeCostParams, MixNodeDetails, MixNodeRewarding, UnbondedMixnode,
|
MixNodeCostParams, MixNodeDetails, MixNodeRewarding, UnbondedMixnode,
|
||||||
};
|
};
|
||||||
use mixnet_contract_common::{Layer, MixId, MixNode, MixNodeBond};
|
use mixnet_contract_common::{IdentityKey, Layer, MixId, MixNode, MixNodeBond};
|
||||||
|
|
||||||
pub(crate) fn must_get_mixnode_bond_by_owner(
|
pub(crate) fn must_get_mixnode_bond_by_owner(
|
||||||
store: &dyn Storage,
|
store: &dyn Storage,
|
||||||
@@ -26,18 +26,34 @@ pub(crate) fn must_get_mixnode_bond_by_owner(
|
|||||||
.1)
|
.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(
|
pub(crate) fn get_mixnode_details_by_id(
|
||||||
store: &dyn Storage,
|
store: &dyn Storage,
|
||||||
mix_id: MixId,
|
mix_id: MixId,
|
||||||
) -> StdResult<Option<MixNodeDetails>> {
|
) -> StdResult<Option<MixNodeDetails>> {
|
||||||
if let Some(bond_information) = storage::mixnode_bonds().may_load(store, mix_id)? {
|
if let Some(bond_information) = storage::mixnode_bonds().may_load(store, mix_id)? {
|
||||||
// if bond exists, rewarding details MUST also exist
|
attach_mix_details(store, bond_information).map(Some)
|
||||||
let rewarding_details =
|
|
||||||
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.mix_id)?;
|
|
||||||
Ok(Some(MixNodeDetails::new(
|
|
||||||
bond_information,
|
|
||||||
rewarding_details,
|
|
||||||
)))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -53,13 +69,23 @@ pub(crate) fn get_mixnode_details_by_owner(
|
|||||||
.item(store, address)?
|
.item(store, address)?
|
||||||
.map(|record| record.1)
|
.map(|record| record.1)
|
||||||
{
|
{
|
||||||
// if bond exists, rewarding details MUST also exist
|
attach_mix_details(store, bond_information).map(Some)
|
||||||
let rewarding_details =
|
} else {
|
||||||
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.mix_id)?;
|
Ok(None)
|
||||||
Ok(Some(MixNodeDetails::new(
|
}
|
||||||
bond_information,
|
}
|
||||||
rewarding_details,
|
|
||||||
)))
|
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)
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -153,7 +179,13 @@ pub(crate) mod tests {
|
|||||||
mix_node_cost_params_fixture, mix_node_fixture, TEST_COIN_DENOM,
|
mix_node_cost_params_fixture, mix_node_fixture, TEST_COIN_DENOM,
|
||||||
};
|
};
|
||||||
use crate::support::tests::test_helpers::TestSetup;
|
use crate::support::tests::test_helpers::TestSetup;
|
||||||
use cosmwasm_std::coin;
|
use cosmwasm_std::{coin, Uint128};
|
||||||
|
|
||||||
|
pub(crate) struct DummyMixnode {
|
||||||
|
pub mix_id: MixId,
|
||||||
|
pub owner: Addr,
|
||||||
|
pub identity: IdentityKey,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) const OWNER_EXISTS: &str = "mix-owner-existing";
|
pub(crate) const OWNER_EXISTS: &str = "mix-owner-existing";
|
||||||
pub(crate) const OWNER_UNBONDING: &str = "mix-owner-unbonding";
|
pub(crate) const OWNER_UNBONDING: &str = "mix-owner-unbonding";
|
||||||
@@ -161,33 +193,59 @@ pub(crate) mod tests {
|
|||||||
pub(crate) const OWNER_UNBONDED_LEFTOVER: &str = "mix-owner-unbonded-leftover";
|
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
|
// 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) -> Vec<MixId> {
|
pub(crate) fn setup_mix_combinations(
|
||||||
let mix_id_exists = test.add_dummy_mixnode(OWNER_EXISTS, None);
|
test: &mut TestSetup,
|
||||||
let mix_id_unbonding = test.add_dummy_mixnode(OWNER_UNBONDING, None);
|
stake: Option<Uint128>,
|
||||||
let mix_id_unbonded = test.add_dummy_mixnode(OWNER_UNBONDED, None);
|
) -> Vec<DummyMixnode> {
|
||||||
let mix_id_unbonded_leftover = test.add_dummy_mixnode(OWNER_UNBONDED_LEFTOVER, None);
|
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(),
|
||||||
|
};
|
||||||
|
|
||||||
// manually adjust delegation info as to indicate the rewarding information shouldnt get removed
|
// manually adjust delegation info as to indicate the rewarding information shouldnt get removed
|
||||||
let mut rewarding_details = test.mix_rewarding(mix_id_unbonded_leftover);
|
let mut rewarding_details = test.mix_rewarding(mix_unbonded_leftover.mix_id);
|
||||||
rewarding_details.delegates = Decimal::raw(12345);
|
rewarding_details.delegates = Decimal::raw(12345);
|
||||||
rewarding_details.unique_delegations = 1;
|
rewarding_details.unique_delegations = 1;
|
||||||
rewards_storage::MIXNODE_REWARDING
|
rewards_storage::MIXNODE_REWARDING
|
||||||
.save(
|
.save(
|
||||||
test.deps_mut().storage,
|
test.deps_mut().storage,
|
||||||
mix_id_unbonded_leftover,
|
mix_unbonded_leftover.mix_id,
|
||||||
&rewarding_details,
|
&rewarding_details,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
test.immediately_unbond_mixnode(mix_id_unbonded);
|
test.immediately_unbond_mixnode(mix_unbonded.mix_id);
|
||||||
test.immediately_unbond_mixnode(mix_id_unbonded_leftover);
|
test.immediately_unbond_mixnode(mix_unbonded_leftover.mix_id);
|
||||||
test.start_unbonding_mixnode(mix_id_unbonding);
|
test.start_unbonding_mixnode(mix_unbonding.mix_id);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
mix_id_exists,
|
mix_exists,
|
||||||
mix_id_unbonding,
|
mix_unbonding,
|
||||||
mix_id_unbonded,
|
mix_unbonded,
|
||||||
mix_id_unbonded_leftover,
|
mix_unbonded_leftover,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,37 +253,35 @@ pub(crate) mod tests {
|
|||||||
fn getting_mixnode_bond_by_owner() {
|
fn getting_mixnode_bond_by_owner() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
|
|
||||||
let owner_exists = Addr::unchecked(OWNER_EXISTS);
|
let nodes = setup_mix_combinations(&mut test, None);
|
||||||
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
|
let mix_exists = &nodes[0];
|
||||||
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
|
let mix_unbonding = &nodes[1];
|
||||||
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
|
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];
|
|
||||||
|
|
||||||
// if this is a normally bonded mixnode, all should be fine
|
// if this is a normally bonded mixnode, all should be fine
|
||||||
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_exists).unwrap();
|
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_exists.owner).unwrap();
|
||||||
assert_eq!(res.mix_id, mix_id_exists);
|
assert_eq!(res.mix_id, mix_exists.mix_id);
|
||||||
|
|
||||||
// if node is in the process of unbonding, we still should be capable of retrieving its details
|
// 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, &owner_unbonding).unwrap();
|
let res =
|
||||||
assert_eq!(res.mix_id, mix_id_unbonding);
|
must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonding.owner).unwrap();
|
||||||
|
assert_eq!(res.mix_id, mix_unbonding.mix_id);
|
||||||
|
|
||||||
// but if node has unbonded, the information is purged and query fails
|
// but if node has unbonded, the information is purged and query fails
|
||||||
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_unbonded);
|
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonded.owner);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Err(MixnetContractError::NoAssociatedMixNodeBond {
|
Err(MixnetContractError::NoAssociatedMixNodeBond {
|
||||||
owner: owner_unbonded
|
owner: mix_unbonded.owner.clone()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &owner_unbonded_leftover);
|
let res = must_get_mixnode_bond_by_owner(test.deps().storage, &mix_unbonded_leftover.owner);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Err(MixnetContractError::NoAssociatedMixNodeBond {
|
Err(MixnetContractError::NoAssociatedMixNodeBond {
|
||||||
owner: owner_unbonded_leftover
|
owner: mix_unbonded_leftover.owner.clone()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -234,26 +290,27 @@ pub(crate) mod tests {
|
|||||||
fn getting_mixnode_details_by_id() {
|
fn getting_mixnode_details_by_id() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
|
|
||||||
let ids = setup_mix_combinations(&mut test);
|
let nodes = setup_mix_combinations(&mut test, None);
|
||||||
let mix_id_exists = ids[0];
|
let mix_exists = &nodes[0];
|
||||||
let mix_id_unbonding = ids[1];
|
let mix_unbonding = &nodes[1];
|
||||||
let mix_id_unbonded = ids[2];
|
let mix_unbonded = &nodes[2];
|
||||||
let mix_id_unbonded_leftover = ids[3];
|
let mix_unbonded_leftover = &nodes[3];
|
||||||
|
|
||||||
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_exists)
|
let res = get_mixnode_details_by_id(test.deps().storage, mix_exists.mix_id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(res.bond_information.mix_id, mix_id_exists);
|
assert_eq!(res.bond_information.mix_id, mix_exists.mix_id);
|
||||||
|
|
||||||
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonding)
|
let res = get_mixnode_details_by_id(test.deps().storage, mix_unbonding.mix_id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(res.bond_information.mix_id, mix_id_unbonding);
|
assert_eq!(res.bond_information.mix_id, mix_unbonding.mix_id);
|
||||||
|
|
||||||
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonded).unwrap();
|
let res = get_mixnode_details_by_id(test.deps().storage, mix_unbonded.mix_id).unwrap();
|
||||||
assert!(res.is_none());
|
assert!(res.is_none());
|
||||||
|
|
||||||
let res = get_mixnode_details_by_id(test.deps().storage, mix_id_unbonded_leftover).unwrap();
|
let res =
|
||||||
|
get_mixnode_details_by_id(test.deps().storage, mix_unbonded_leftover.mix_id).unwrap();
|
||||||
assert!(res.is_none())
|
assert!(res.is_none())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,33 +318,69 @@ pub(crate) mod tests {
|
|||||||
fn getting_mixnode_details_by_owner() {
|
fn getting_mixnode_details_by_owner() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
|
|
||||||
let owner_exists = Addr::unchecked(OWNER_EXISTS);
|
let nodes = setup_mix_combinations(&mut test, None);
|
||||||
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
|
let mix_exists = &nodes[0];
|
||||||
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
|
let mix_unbonding = &nodes[1];
|
||||||
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
|
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];
|
|
||||||
|
|
||||||
// if this is a normally bonded mixnode, all should be fine
|
// if this is a normally bonded mixnode, all should be fine
|
||||||
let res = get_mixnode_details_by_owner(test.deps().storage, owner_exists)
|
let res = get_mixnode_details_by_owner(test.deps().storage, mix_exists.owner.clone())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(res.bond_information.mix_id, mix_id_exists);
|
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
|
// 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, owner_unbonding)
|
let res = get_mixnode_details_by_owner(test.deps().storage, mix_unbonding.owner.clone())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(res.bond_information.mix_id, mix_id_unbonding);
|
assert_eq!(res.bond_information.mix_id, mix_unbonding.mix_id);
|
||||||
|
|
||||||
// but if node has unbonded, the information is purged and query fails
|
// but if node has unbonded, the information is purged and query fails
|
||||||
let res = get_mixnode_details_by_owner(test.deps().storage, owner_unbonded).unwrap();
|
let res =
|
||||||
|
get_mixnode_details_by_owner(test.deps().storage, mix_unbonded.owner.clone()).unwrap();
|
||||||
assert!(res.is_none());
|
assert!(res.is_none());
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
get_mixnode_details_by_owner(test.deps().storage, owner_unbonded_leftover).unwrap();
|
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();
|
||||||
assert!(res.is_none());
|
assert!(res.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ use crate::constants::{
|
|||||||
MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT, MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT,
|
MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT, MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT,
|
||||||
UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT, UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT,
|
UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT, UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT,
|
||||||
};
|
};
|
||||||
use crate::mixnodes::helpers::{get_mixnode_details_by_id, get_mixnode_details_by_owner};
|
use crate::mixnodes::helpers::{
|
||||||
|
attach_mix_details, get_mixnode_details_by_id, get_mixnode_details_by_identity,
|
||||||
|
get_mixnode_details_by_owner,
|
||||||
|
};
|
||||||
use crate::rewards::storage as rewards_storage;
|
use crate::rewards::storage as rewards_storage;
|
||||||
use cosmwasm_std::{Deps, Order, StdResult, Storage};
|
use cosmwasm_std::{Deps, Order, StdResult, Storage};
|
||||||
use cw_storage_plus::Bound;
|
use cw_storage_plus::Bound;
|
||||||
@@ -46,18 +49,12 @@ pub fn query_mixnode_bonds_paged(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attach_rewarding_info(
|
fn attach_node_details(
|
||||||
storage: &dyn Storage,
|
storage: &dyn Storage,
|
||||||
read_bond: StdResult<(MixId, MixNodeBond)>,
|
read_bond: StdResult<(MixId, MixNodeBond)>,
|
||||||
) -> StdResult<MixNodeDetails> {
|
) -> StdResult<MixNodeDetails> {
|
||||||
match read_bond {
|
match read_bond {
|
||||||
Ok((_, bond)) => {
|
Ok((_, bond)) => attach_mix_details(storage, 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),
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +73,7 @@ pub fn query_mixnodes_details_paged(
|
|||||||
let nodes = storage::mixnode_bonds()
|
let nodes = storage::mixnode_bonds()
|
||||||
.range(deps.storage, start, None, Order::Ascending)
|
.range(deps.storage, start, None, Order::Ascending)
|
||||||
.take(limit)
|
.take(limit)
|
||||||
.map(|res| attach_rewarding_info(deps.storage, res))
|
.map(|res| attach_node_details(deps.storage, res))
|
||||||
.collect::<StdResult<Vec<MixNodeDetails>>>()?;
|
.collect::<StdResult<Vec<MixNodeDetails>>>()?;
|
||||||
|
|
||||||
let start_next_after = nodes.last().map(|details| details.mix_id());
|
let start_next_after = nodes.last().map(|details| details.mix_id());
|
||||||
@@ -192,26 +189,12 @@ 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(
|
pub fn query_mixnode_details_by_identity(
|
||||||
deps: Deps<'_>,
|
deps: Deps<'_>,
|
||||||
mix_identity: IdentityKey,
|
mix_identity: IdentityKey,
|
||||||
) -> StdResult<Option<MixNodeDetails>> {
|
) -> StdResult<Option<MixNodeDetails>> {
|
||||||
if let Some(bond_information) = storage::mixnode_bonds()
|
get_mixnode_details_by_identity(deps.storage, mix_identity)
|
||||||
.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(
|
pub fn query_mixnode_rewarding_details(
|
||||||
@@ -459,10 +442,10 @@ pub(crate) mod tests {
|
|||||||
fn obeys_limits() {
|
fn obeys_limits() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
let limit = 2;
|
let limit = 2;
|
||||||
|
|
||||||
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000);
|
test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
|
||||||
let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(limit)).unwrap();
|
let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, Some(limit)).unwrap();
|
||||||
assert_eq!(limit, page1.nodes.len() as u32);
|
assert_eq!(limit, page1.nodes.len() as u32);
|
||||||
}
|
}
|
||||||
@@ -471,8 +454,8 @@ pub(crate) mod tests {
|
|||||||
fn has_default_limit() {
|
fn has_default_limit() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000);
|
test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
|
||||||
|
|
||||||
// query without explicitly setting a limit
|
// query without explicitly setting a limit
|
||||||
let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, None).unwrap();
|
let page1 = query_unbonded_mixnodes_paged(deps.as_ref(), None, None).unwrap();
|
||||||
@@ -487,8 +470,8 @@ pub(crate) mod tests {
|
|||||||
fn has_max_limit() {
|
fn has_max_limit() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
test_helpers::add_dummy_unbonded_mixnodes(&mut rng, deps.as_mut(), 1000);
|
test_helpers::add_dummy_unbonded_mixnodes(rng, deps.as_mut(), 1000);
|
||||||
|
|
||||||
// query with a crazily high limit in an attempt to use too many resources
|
// query with a crazily high limit in an attempt to use too many resources
|
||||||
let crazy_limit = 1000;
|
let crazy_limit = 1000;
|
||||||
@@ -589,16 +572,11 @@ pub(crate) mod tests {
|
|||||||
fn obeys_limits() {
|
fn obeys_limits() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
let limit = 2;
|
let limit = 2;
|
||||||
let owner = "owner";
|
let owner = "owner";
|
||||||
|
|
||||||
test_helpers::add_dummy_unbonded_mixnodes_with_owner(
|
test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
|
||||||
&mut rng,
|
|
||||||
deps.as_mut(),
|
|
||||||
owner,
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
let page1 = query_unbonded_mixnodes_by_owner_paged(
|
let page1 = query_unbonded_mixnodes_by_owner_paged(
|
||||||
deps.as_ref(),
|
deps.as_ref(),
|
||||||
owner.into(),
|
owner.into(),
|
||||||
@@ -613,15 +591,10 @@ pub(crate) mod tests {
|
|||||||
fn has_default_limit() {
|
fn has_default_limit() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
let owner = "owner";
|
let owner = "owner";
|
||||||
|
|
||||||
test_helpers::add_dummy_unbonded_mixnodes_with_owner(
|
test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
|
||||||
&mut rng,
|
|
||||||
deps.as_mut(),
|
|
||||||
owner,
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
// query without explicitly setting a limit
|
// query without explicitly setting a limit
|
||||||
let page1 =
|
let page1 =
|
||||||
@@ -638,15 +611,10 @@ pub(crate) mod tests {
|
|||||||
fn has_max_limit() {
|
fn has_max_limit() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
let owner = "owner";
|
let owner = "owner";
|
||||||
|
|
||||||
test_helpers::add_dummy_unbonded_mixnodes_with_owner(
|
test_helpers::add_dummy_unbonded_mixnodes_with_owner(rng, deps.as_mut(), owner, 1000);
|
||||||
&mut rng,
|
|
||||||
deps.as_mut(),
|
|
||||||
owner,
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
// query with a crazily high limit in an attempt to use too many resources
|
// query with a crazily high limit in an attempt to use too many resources
|
||||||
let crazy_limit = 1000;
|
let crazy_limit = 1000;
|
||||||
@@ -836,12 +804,12 @@ pub(crate) mod tests {
|
|||||||
fn obeys_limits() {
|
fn obeys_limits() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
let limit = 2;
|
let limit = 2;
|
||||||
let identity = "foomp123";
|
let identity = "foomp123";
|
||||||
|
|
||||||
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
|
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
|
||||||
&mut rng,
|
rng,
|
||||||
deps.as_mut(),
|
deps.as_mut(),
|
||||||
identity,
|
identity,
|
||||||
1000,
|
1000,
|
||||||
@@ -860,10 +828,10 @@ pub(crate) mod tests {
|
|||||||
fn has_default_limit() {
|
fn has_default_limit() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
let identity = "foomp123";
|
let identity = "foomp123";
|
||||||
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
|
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
|
||||||
&mut rng,
|
rng,
|
||||||
deps.as_mut(),
|
deps.as_mut(),
|
||||||
identity,
|
identity,
|
||||||
1000,
|
1000,
|
||||||
@@ -888,10 +856,10 @@ pub(crate) mod tests {
|
|||||||
fn has_max_limit() {
|
fn has_max_limit() {
|
||||||
let mut deps = test_helpers::init_contract();
|
let mut deps = test_helpers::init_contract();
|
||||||
let _env = mock_env();
|
let _env = mock_env();
|
||||||
let mut rng = test_helpers::test_rng();
|
let rng = test_helpers::test_rng();
|
||||||
let identity = "foomp123";
|
let identity = "foomp123";
|
||||||
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
|
test_helpers::add_dummy_unbonded_mixnodes_with_identity(
|
||||||
&mut rng,
|
rng,
|
||||||
deps.as_mut(),
|
deps.as_mut(),
|
||||||
identity,
|
identity,
|
||||||
1000,
|
1000,
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
LAYER_DISTRIBUTION_KEY, MIXNODES_IDENTITY_IDX_NAMESPACE, MIXNODES_OWNER_IDX_NAMESPACE,
|
LAYER_DISTRIBUTION_KEY, MIXNODES_IDENTITY_IDX_NAMESPACE, MIXNODES_OWNER_IDX_NAMESPACE,
|
||||||
MIXNODES_PK_NAMESPACE, MIXNODES_SPHINX_IDX_NAMESPACE, NODE_ID_COUNTER_KEY,
|
MIXNODES_PK_NAMESPACE, MIXNODES_SPHINX_IDX_NAMESPACE, NODE_ID_COUNTER_KEY,
|
||||||
UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE, UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE,
|
PENDING_MIXNODE_CHANGES_NAMESPACE, UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE,
|
||||||
UNBONDED_MIXNODES_PK_NAMESPACE,
|
UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE, UNBONDED_MIXNODES_PK_NAMESPACE,
|
||||||
};
|
};
|
||||||
use cosmwasm_std::{StdResult, Storage};
|
use cosmwasm_std::{StdResult, Storage};
|
||||||
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, MultiIndex, UniqueIndex};
|
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex, UniqueIndex};
|
||||||
use mixnet_contract_common::error::MixnetContractError;
|
use mixnet_contract_common::error::MixnetContractError;
|
||||||
use mixnet_contract_common::mixnode::UnbondedMixnode;
|
use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
|
||||||
use mixnet_contract_common::SphinxKey;
|
use mixnet_contract_common::SphinxKey;
|
||||||
use mixnet_contract_common::{Addr, IdentityKey, Layer, LayerDistribution, MixId, MixNodeBond};
|
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
|
// 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
|
// if we ever decide it's too bloaty, we can deprecate it and start removing all data in
|
||||||
// subsequent migrations
|
// subsequent migrations
|
||||||
|
|
||||||
pub(crate) struct UnbondedMixnodeIndex<'a> {
|
pub(crate) struct UnbondedMixnodeIndex<'a> {
|
||||||
pub(crate) owner: MultiIndex<'a, Addr, UnbondedMixnode, MixId>,
|
pub(crate) owner: MultiIndex<'a, Addr, UnbondedMixnode, MixId>,
|
||||||
|
|
||||||
@@ -48,9 +52,6 @@ pub(crate) fn unbonded_mixnodes<'a>(
|
|||||||
IndexedMap::new(UNBONDED_MIXNODES_PK_NAMESPACE, indexes)
|
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) struct MixnodeBondIndex<'a> {
|
||||||
pub(crate) owner: UniqueIndex<'a, Addr, MixNodeBond>,
|
pub(crate) owner: UniqueIndex<'a, Addr, MixNodeBond>,
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
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_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 crate::interval::storage as interval_storage;
|
use crate::interval::storage as interval_storage;
|
||||||
use crate::interval::storage::push_new_interval_event;
|
use crate::interval::storage::push_new_interval_event;
|
||||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||||
@@ -13,19 +25,11 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
|
|||||||
use crate::signing::storage as signing_storage;
|
use crate::signing::storage as signing_storage;
|
||||||
use crate::support::helpers::{
|
use crate::support::helpers::{
|
||||||
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
|
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
|
||||||
ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
|
ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
|
||||||
|
validate_pledge,
|
||||||
};
|
};
|
||||||
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
|
|
||||||
use mixnet_contract_common::error::MixnetContractError;
|
use super::storage;
|
||||||
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(
|
pub(crate) fn update_mixnode_layer(
|
||||||
mix_id: MixId,
|
mix_id: MixId,
|
||||||
@@ -195,6 +199,7 @@ pub fn _try_increase_pledge(
|
|||||||
) -> Result<Response, MixnetContractError> {
|
) -> Result<Response, MixnetContractError> {
|
||||||
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
|
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
|
||||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
|
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
|
||||||
|
let mut pending_changes = mix_details.pending_changes;
|
||||||
let mix_id = mix_details.mix_id();
|
let mix_id = mix_details.mix_id();
|
||||||
|
|
||||||
// increasing pledge is only allowed if the epoch is currently not in the process of being advanced
|
// increasing pledge is only allowed if the epoch is currently not in the process of being advanced
|
||||||
@@ -202,6 +207,7 @@ pub fn _try_increase_pledge(
|
|||||||
|
|
||||||
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
|
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
|
||||||
ensure_bonded(&mix_details.bond_information)?;
|
ensure_bonded(&mix_details.bond_information)?;
|
||||||
|
ensure_no_pending_pledge_changes(&pending_changes)?;
|
||||||
|
|
||||||
let rewarding_denom = rewarding_denom(deps.storage)?;
|
let rewarding_denom = rewarding_denom(deps.storage)?;
|
||||||
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
|
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
|
||||||
@@ -213,7 +219,95 @@ pub fn _try_increase_pledge(
|
|||||||
mix_id,
|
mix_id,
|
||||||
amount: pledge_increase,
|
amount: pledge_increase,
|
||||||
};
|
};
|
||||||
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
|
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)?;
|
||||||
|
|
||||||
Ok(Response::new().add_event(cosmos_event))
|
Ok(Response::new().add_event(cosmos_event))
|
||||||
}
|
}
|
||||||
@@ -246,6 +340,9 @@ pub(crate) fn _try_remove_mixnode(
|
|||||||
proxy: Option<Addr>,
|
proxy: Option<Addr>,
|
||||||
) -> Result<Response, MixnetContractError> {
|
) -> Result<Response, MixnetContractError> {
|
||||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
|
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
|
// unbonding is only allowed if the epoch is currently not in the process of being advanced
|
||||||
ensure_epoch_in_progress_state(deps.storage)?;
|
ensure_epoch_in_progress_state(deps.storage)?;
|
||||||
@@ -254,6 +351,9 @@ pub(crate) fn _try_remove_mixnode(
|
|||||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||||
ensure_bonded(&existing_bond)?;
|
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
|
// set `is_unbonding` field
|
||||||
let mut updated_bond = existing_bond.clone();
|
let mut updated_bond = existing_bond.clone();
|
||||||
updated_bond.is_unbonding = true;
|
updated_bond.is_unbonding = true;
|
||||||
@@ -392,18 +492,22 @@ pub(crate) fn _try_update_mixnode_cost_params(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
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 crate::contract::execute;
|
use crate::contract::execute;
|
||||||
use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge;
|
use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge;
|
||||||
use crate::mixnodes::helpers::get_mixnode_details_by_id;
|
use crate::mixnodes::helpers::get_mixnode_details_by_id;
|
||||||
use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM};
|
use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM};
|
||||||
use crate::support::tests::test_helpers::TestSetup;
|
use crate::support::tests::test_helpers::TestSetup;
|
||||||
use crate::support::tests::{fixtures, test_helpers};
|
use crate::support::tests::{fixtures, test_helpers};
|
||||||
use cosmwasm_std::testing::mock_info;
|
|
||||||
use cosmwasm_std::{Order, StdResult, Uint128};
|
use super::*;
|
||||||
use mixnet_contract_common::{
|
|
||||||
EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mixnode_add() {
|
fn mixnode_add() {
|
||||||
@@ -531,7 +635,7 @@ pub mod tests {
|
|||||||
let res = try_add_mixnode(
|
let res = try_add_mixnode(
|
||||||
test.deps_mut(),
|
test.deps_mut(),
|
||||||
env.clone(),
|
env.clone(),
|
||||||
info.clone(),
|
info,
|
||||||
mixnode.clone(),
|
mixnode.clone(),
|
||||||
cost_params.clone(),
|
cost_params.clone(),
|
||||||
signature.clone(),
|
signature.clone(),
|
||||||
@@ -601,7 +705,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
MixnetContractError::SenderIsNotVestingContract {
|
MixnetContractError::SenderIsNotVestingContract {
|
||||||
received: illegal_proxy,
|
received: illegal_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -669,7 +773,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
Err(MixnetContractError::ProxyMismatch {
|
Err(MixnetContractError::ProxyMismatch {
|
||||||
existing: "None".to_string(),
|
existing: "None".to_string(),
|
||||||
incoming: vesting_contract.into_string()
|
incoming: vesting_contract.into_string(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -717,11 +821,66 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
MixnetContractError::SenderIsNotVestingContract {
|
MixnetContractError::SenderIsNotVestingContract {
|
||||||
received: illegal_proxy,
|
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]
|
#[test]
|
||||||
fn updating_mixnode_config() {
|
fn updating_mixnode_config() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
@@ -760,7 +919,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
Err(MixnetContractError::ProxyMismatch {
|
Err(MixnetContractError::ProxyMismatch {
|
||||||
existing: "None".to_string(),
|
existing: "None".to_string(),
|
||||||
incoming: vesting_contract.into_string()
|
incoming: vesting_contract.into_string(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// "normal" update succeeds
|
// "normal" update succeeds
|
||||||
@@ -812,7 +971,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
MixnetContractError::SenderIsNotVestingContract {
|
MixnetContractError::SenderIsNotVestingContract {
|
||||||
received: illegal_proxy,
|
received: illegal_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -895,7 +1054,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
Err(MixnetContractError::ProxyMismatch {
|
Err(MixnetContractError::ProxyMismatch {
|
||||||
existing: "None".to_string(),
|
existing: "None".to_string(),
|
||||||
incoming: vesting_contract.into_string()
|
incoming: vesting_contract.into_string(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// "normal" update succeeds
|
// "normal" update succeeds
|
||||||
@@ -918,7 +1077,7 @@ pub mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
PendingIntervalEventKind::ChangeMixCostParams {
|
PendingIntervalEventKind::ChangeMixCostParams {
|
||||||
mix_id,
|
mix_id,
|
||||||
new_costs: update.clone()
|
new_costs: update.clone(),
|
||||||
},
|
},
|
||||||
event.1.kind
|
event.1.kind
|
||||||
);
|
);
|
||||||
@@ -967,7 +1126,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
MixnetContractError::SenderIsNotVestingContract {
|
MixnetContractError::SenderIsNotVestingContract {
|
||||||
received: illegal_proxy,
|
received: illegal_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1013,7 +1172,7 @@ pub mod tests {
|
|||||||
info_alice,
|
info_alice,
|
||||||
mixnode1,
|
mixnode1,
|
||||||
cost_params.clone(),
|
cost_params.clone(),
|
||||||
sig1
|
sig1,
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
@@ -1025,12 +1184,15 @@ pub mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod increasing_mixnode_pledge {
|
mod increasing_mixnode_pledge {
|
||||||
use super::*;
|
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||||
|
use mixnet_contract_common::{EpochState, EpochStatus};
|
||||||
|
|
||||||
use crate::mixnodes::helpers::tests::{
|
use crate::mixnodes::helpers::tests::{
|
||||||
setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
|
setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
|
||||||
};
|
};
|
||||||
use crate::support::tests::test_helpers::TestSetup;
|
use crate::support::tests::test_helpers::TestSetup;
|
||||||
use mixnet_contract_common::{EpochState, EpochStatus};
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cant_be_performed_if_epoch_transition_is_in_progress() {
|
fn cant_be_performed_if_epoch_transition_is_in_progress() {
|
||||||
@@ -1108,7 +1270,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
Err(MixnetContractError::ProxyMismatch {
|
Err(MixnetContractError::ProxyMismatch {
|
||||||
existing: "None".to_string(),
|
existing: "None".to_string(),
|
||||||
incoming: "proxy".to_string()
|
incoming: "proxy".to_string(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1123,7 +1285,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
Err(MixnetContractError::ProxyMismatch {
|
Err(MixnetContractError::ProxyMismatch {
|
||||||
existing: "proxy".to_string(),
|
existing: "proxy".to_string(),
|
||||||
incoming: "None".to_string()
|
incoming: "None".to_string(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1138,7 +1300,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
Err(MixnetContractError::ProxyMismatch {
|
Err(MixnetContractError::ProxyMismatch {
|
||||||
existing: "proxy".to_string(),
|
existing: "proxy".to_string(),
|
||||||
incoming: "unrelated-proxy".to_string()
|
incoming: "unrelated-proxy".to_string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1154,8 +1316,8 @@ pub mod tests {
|
|||||||
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
|
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
|
||||||
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
|
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
|
||||||
|
|
||||||
let ids = setup_mix_combinations(&mut test);
|
let ids = setup_mix_combinations(&mut test, None);
|
||||||
let mix_id_unbonding = ids[1];
|
let mix_id_unbonding = ids[1].mix_id;
|
||||||
|
|
||||||
let res = try_increase_pledge(
|
let res = try_increase_pledge(
|
||||||
test.deps_mut(),
|
test.deps_mut(),
|
||||||
@@ -1214,11 +1376,66 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
Err(MixnetContractError::InsufficientPledge {
|
Err(MixnetContractError::InsufficientPledge {
|
||||||
received: test.coin(0),
|
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]
|
#[test]
|
||||||
fn with_valid_information_creates_pending_event() {
|
fn with_valid_information_creates_pending_event() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
@@ -1238,11 +1455,10 @@ pub mod tests {
|
|||||||
events[0].kind,
|
events[0].kind,
|
||||||
PendingEpochEventKind::PledgeMore {
|
PendingEpochEventKind::PledgeMore {
|
||||||
mix_id,
|
mix_id,
|
||||||
amount: test.coin(1000)
|
amount: test.coin(1000),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_for_illegal_proxy() {
|
fn fails_for_illegal_proxy() {
|
||||||
@@ -1268,8 +1484,369 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
MixnetContractError::SenderIsNotVestingContract {
|
MixnetContractError::SenderIsNotVestingContract {
|
||||||
received: illegal_proxy,
|
received: illegal_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod decreasing_mixnode_pledge {
|
||||||
|
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||||
|
use mixnet_contract_common::{EpochState, EpochStatus};
|
||||||
|
|
||||||
|
use crate::mixnodes::helpers::tests::{
|
||||||
|
setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
|
||||||
|
};
|
||||||
|
use crate::support::tests::test_helpers::TestSetup;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
];
|
||||||
|
|
||||||
|
for bad_state in bad_states {
|
||||||
|
let mut test = TestSetup::new();
|
||||||
|
let env = test.env();
|
||||||
|
let owner = "mix-owner";
|
||||||
|
let decrease = test.coin(1000);
|
||||||
|
|
||||||
|
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 { .. })
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,2 +1,42 @@
|
|||||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// 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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -253,8 +253,8 @@ mod tests {
|
|||||||
fn withdrawing_delegator_reward() {
|
fn withdrawing_delegator_reward() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
|
|
||||||
let delegation_amount = Uint128::new(2500_000_000);
|
let delegation_amount = Uint128::new(2_500_000_000);
|
||||||
let delegation_dec = 2500_000_000u32.into_base_decimal().unwrap();
|
let delegation_dec = 2_500_000_000_u32.into_base_decimal().unwrap();
|
||||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||||
let delegator = "delegator";
|
let delegator = "delegator";
|
||||||
test.add_immediate_delegation(delegator, delegation_amount, mix_id);
|
test.add_immediate_delegation(delegator, delegation_amount, mix_id);
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// 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 cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response};
|
||||||
|
|
||||||
use mixnet_contract_common::error::MixnetContractError;
|
use mixnet_contract_common::error::MixnetContractError;
|
||||||
use mixnet_contract_common::events::{
|
use mixnet_contract_common::events::{
|
||||||
new_active_set_update_event, new_mix_rewarding_event,
|
new_active_set_update_event, new_mix_rewarding_event,
|
||||||
@@ -30,6 +18,21 @@ use mixnet_contract_common::reward_params::{
|
|||||||
use mixnet_contract_common::{Delegation, EpochState, MixId};
|
use mixnet_contract_common::{Delegation, EpochState, MixId};
|
||||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
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(
|
pub(crate) fn try_reward_mixnode(
|
||||||
deps: DepsMut<'_>,
|
deps: DepsMut<'_>,
|
||||||
env: Env,
|
env: Env,
|
||||||
@@ -233,23 +236,22 @@ pub(crate) fn _try_withdraw_delegator_reward(
|
|||||||
mix_id,
|
mix_id,
|
||||||
address: owner.into_string(),
|
address: owner.into_string(),
|
||||||
proxy: proxy.map(Addr::into_string),
|
proxy: proxy.map(Addr::into_string),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
Some(delegation) => delegation,
|
Some(delegation) => delegation,
|
||||||
};
|
};
|
||||||
|
|
||||||
// grab associated mixnode rewarding details
|
// grab associated mixnode rewarding details
|
||||||
let mix_rewarding =
|
let mix_rewarding =
|
||||||
storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::InconsistentState {
|
storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)?.ok_or(MixnetContractError::inconsistent_state(
|
||||||
comment: "mixnode rewarding got removed from the storage whilst there's still an existing delegation"
|
"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
|
// 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)
|
// (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)? {
|
match mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)? {
|
||||||
Some(mix_bond) if mix_bond.is_unbonding => {
|
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 }),
|
None => return Err(MixnetContractError::MixnodeHasUnbonded { mix_id }),
|
||||||
_ => (),
|
_ => (),
|
||||||
@@ -376,17 +378,17 @@ pub(crate) fn try_update_rewarding_params(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use cosmwasm_std::testing::mock_info;
|
||||||
|
|
||||||
use crate::mixnodes::storage as mixnodes_storage;
|
use crate::mixnodes::storage as mixnodes_storage;
|
||||||
use crate::support::tests::test_helpers;
|
use crate::support::tests::test_helpers;
|
||||||
use cosmwasm_std::testing::mock_info;
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod mixnode_rewarding {
|
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 cosmwasm_std::{Decimal, Uint128};
|
||||||
|
|
||||||
use mixnet_contract_common::events::{
|
use mixnet_contract_common::events::{
|
||||||
MixnetEventType, BOND_NOT_FOUND_VALUE, DELEGATES_REWARD_KEY, NO_REWARD_REASON_KEY,
|
MixnetEventType, BOND_NOT_FOUND_VALUE, DELEGATES_REWARD_KEY, NO_REWARD_REASON_KEY,
|
||||||
OPERATOR_REWARD_KEY, PRIOR_DELEGATES_KEY, PRIOR_UNIT_REWARD_KEY,
|
OPERATOR_REWARD_KEY, PRIOR_DELEGATES_KEY, PRIOR_UNIT_REWARD_KEY,
|
||||||
@@ -395,11 +397,17 @@ pub mod tests {
|
|||||||
use mixnet_contract_common::helpers::compare_decimals;
|
use mixnet_contract_common::helpers::compare_decimals;
|
||||||
use mixnet_contract_common::{EpochStatus, RewardedSetNodeStatus};
|
use mixnet_contract_common::{EpochStatus, RewardedSetNodeStatus};
|
||||||
|
|
||||||
|
use crate::interval::pending_events;
|
||||||
|
use crate::support::tests::test_helpers::{find_attribute, TestSetup};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod epoch_state_is_correctly_updated {
|
mod epoch_state_is_correctly_updated {
|
||||||
use super::*;
|
|
||||||
use mixnet_contract_common::EpochState;
|
use mixnet_contract_common::EpochState;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_target_mixnode_unbonded() {
|
fn when_target_mixnode_unbonded() {
|
||||||
let mut test = TestSetup::new();
|
let mut test = TestSetup::new();
|
||||||
@@ -459,7 +467,7 @@ pub mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
EpochState::Rewarding {
|
EpochState::Rewarding {
|
||||||
last_rewarded: mix_id_unbonded,
|
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)
|
interval_storage::current_epoch_status(test.deps().storage)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -477,7 +485,7 @@ pub mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
EpochState::Rewarding {
|
EpochState::Rewarding {
|
||||||
last_rewarded: mix_id_unbonded_leftover,
|
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)
|
interval_storage::current_epoch_status(test.deps().storage)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -486,8 +494,8 @@ pub mod tests {
|
|||||||
|
|
||||||
try_reward_mixnode(
|
try_reward_mixnode(
|
||||||
test.deps_mut(),
|
test.deps_mut(),
|
||||||
env.clone(),
|
env,
|
||||||
sender.clone(),
|
sender,
|
||||||
mix_id_never_existed,
|
mix_id_never_existed,
|
||||||
performance,
|
performance,
|
||||||
)
|
)
|
||||||
@@ -578,7 +586,7 @@ pub mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
EpochState::Rewarding {
|
EpochState::Rewarding {
|
||||||
last_rewarded: mix_id,
|
last_rewarded: mix_id,
|
||||||
final_node_id: 100
|
final_node_id: 100,
|
||||||
},
|
},
|
||||||
current_state
|
current_state
|
||||||
)
|
)
|
||||||
@@ -1399,12 +1407,15 @@ pub mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod withdrawing_delegator_reward {
|
mod withdrawing_delegator_reward {
|
||||||
use super::*;
|
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128};
|
||||||
|
|
||||||
|
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||||
|
|
||||||
use crate::interval::pending_events;
|
use crate::interval::pending_events;
|
||||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||||
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
|
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
|
||||||
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128};
|
|
||||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_only_be_done_if_delegation_exists() {
|
fn can_only_be_done_if_delegation_exists() {
|
||||||
@@ -1772,7 +1783,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
MixnetContractError::SenderIsNotVestingContract {
|
MixnetContractError::SenderIsNotVestingContract {
|
||||||
received: illegal_proxy,
|
received: illegal_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1780,11 +1791,13 @@ pub mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod withdrawing_operator_reward {
|
mod withdrawing_operator_reward {
|
||||||
use super::*;
|
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128};
|
||||||
|
|
||||||
use crate::interval::pending_events;
|
use crate::interval::pending_events;
|
||||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||||
use crate::support::tests::test_helpers::TestSetup;
|
use crate::support::tests::test_helpers::TestSetup;
|
||||||
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128};
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_only_be_done_if_bond_exists() {
|
fn can_only_be_done_if_bond_exists() {
|
||||||
@@ -1929,7 +1942,7 @@ pub mod tests {
|
|||||||
res,
|
res,
|
||||||
MixnetContractError::SenderIsNotVestingContract {
|
MixnetContractError::SenderIsNotVestingContract {
|
||||||
received: illegal_proxy,
|
received: illegal_proxy,
|
||||||
vesting_contract
|
vesting_contract,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1937,10 +1950,12 @@ pub mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod updating_active_set {
|
mod updating_active_set {
|
||||||
use super::*;
|
|
||||||
use crate::support::tests::test_helpers::TestSetup;
|
|
||||||
use mixnet_contract_common::{EpochState, EpochStatus};
|
use mixnet_contract_common::{EpochState, EpochStatus};
|
||||||
|
|
||||||
|
use crate::support::tests::test_helpers::TestSetup;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() {
|
fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() {
|
||||||
let bad_states = vec![
|
let bad_states = vec![
|
||||||
@@ -2094,7 +2109,7 @@ pub mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.active_set_size;
|
.active_set_size;
|
||||||
let env = test.env();
|
let env = test.env();
|
||||||
let res = try_update_active_set_size(test.deps_mut(), env, owner.clone(), 42, true);
|
let res = try_update_active_set_size(test.deps_mut(), env, owner, 42, true);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
let new = storage::REWARDING_PARAMS
|
let new = storage::REWARDING_PARAMS
|
||||||
.load(test.deps().storage)
|
.load(test.deps().storage)
|
||||||
@@ -2140,11 +2155,14 @@ pub mod tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod updating_rewarding_params {
|
mod updating_rewarding_params {
|
||||||
use super::*;
|
|
||||||
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
|
|
||||||
use cosmwasm_std::Decimal;
|
use cosmwasm_std::Decimal;
|
||||||
|
|
||||||
use mixnet_contract_common::{EpochState, EpochStatus};
|
use mixnet_contract_common::{EpochState, EpochStatus};
|
||||||
|
|
||||||
|
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() {
|
fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() {
|
||||||
let bad_states = vec![
|
let bad_states = vec![
|
||||||
@@ -2181,7 +2199,7 @@ pub mod tests {
|
|||||||
test.deps_mut(),
|
test.deps_mut(),
|
||||||
env.clone(),
|
env.clone(),
|
||||||
owner.clone(),
|
owner.clone(),
|
||||||
update.clone(),
|
update,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
@@ -2189,13 +2207,8 @@ pub mod tests {
|
|||||||
Err(MixnetContractError::EpochAdvancementInProgress { .. })
|
Err(MixnetContractError::EpochAdvancementInProgress { .. })
|
||||||
));
|
));
|
||||||
|
|
||||||
let res_forced = try_update_rewarding_params(
|
let res_forced =
|
||||||
test.deps_mut(),
|
try_update_rewarding_params(test.deps_mut(), env.clone(), owner, update, true);
|
||||||
env.clone(),
|
|
||||||
owner,
|
|
||||||
update.clone(),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert!(res_forced.is_ok())
|
assert!(res_forced.is_ok())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2308,8 +2321,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let old = storage::REWARDING_PARAMS.load(test.deps().storage).unwrap();
|
let old = storage::REWARDING_PARAMS.load(test.deps().storage).unwrap();
|
||||||
let env = test.env();
|
let env = test.env();
|
||||||
let res =
|
let res = try_update_rewarding_params(test.deps_mut(), env, owner, update, true);
|
||||||
try_update_rewarding_params(test.deps_mut(), env, owner.clone(), update, true);
|
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
let new = storage::REWARDING_PARAMS.load(test.deps().storage).unwrap();
|
let new = storage::REWARDING_PARAMS.load(test.deps().storage).unwrap();
|
||||||
assert_ne!(old, new);
|
assert_ne!(old, new);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
|||||||
use crate::mixnodes::storage as mixnodes_storage;
|
use crate::mixnodes::storage as mixnodes_storage;
|
||||||
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
|
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
|
||||||
use mixnet_contract_common::error::MixnetContractError;
|
use mixnet_contract_common::error::MixnetContractError;
|
||||||
|
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||||
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
|
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
|
||||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||||
|
|
||||||
@@ -46,6 +47,14 @@ where
|
|||||||
owner: String,
|
owner: String,
|
||||||
amount: Coin,
|
amount: Coin,
|
||||||
) -> Result<Self, MixnetContractError>;
|
) -> 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 {
|
impl VestingTracking for Response {
|
||||||
@@ -115,6 +124,33 @@ impl VestingTracking for Response {
|
|||||||
Ok(self)
|
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) {
|
// pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
|
||||||
@@ -342,6 +378,15 @@ pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractErro
|
|||||||
Ok(())
|
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,
|
// check if the target address has already bonded a mixnode or gateway,
|
||||||
// in either case, return an appropriate error
|
// in either case, return an appropriate error
|
||||||
pub(crate) fn ensure_no_existing_bond(
|
pub(crate) fn ensure_no_existing_bond(
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ pub mod test_helpers {
|
|||||||
use cosmwasm_std::testing::mock_info;
|
use cosmwasm_std::testing::mock_info;
|
||||||
use cosmwasm_std::testing::MockApi;
|
use cosmwasm_std::testing::MockApi;
|
||||||
use cosmwasm_std::testing::MockQuerier;
|
use cosmwasm_std::testing::MockQuerier;
|
||||||
use cosmwasm_std::{coin, Addr, Api, BankMsg, CosmosMsg, Storage};
|
use cosmwasm_std::{coin, coins, Addr, Api, BankMsg, CosmosMsg, Storage};
|
||||||
use cosmwasm_std::{Coin, Order};
|
use cosmwasm_std::{Coin, Order};
|
||||||
use cosmwasm_std::{Decimal, Empty, MemoryStorage};
|
use cosmwasm_std::{Decimal, Empty, MemoryStorage};
|
||||||
use cosmwasm_std::{Deps, OwnedDeps};
|
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::simulator::Simulator;
|
||||||
use mixnet_contract_common::rewarding::RewardDistribution;
|
use mixnet_contract_common::rewarding::RewardDistribution;
|
||||||
use mixnet_contract_common::{
|
use mixnet_contract_common::{
|
||||||
construct_family_join_permit, Delegation, EpochState, EpochStatus, Gateway,
|
construct_family_join_permit, Delegation, EpochEventId, EpochState, EpochStatus, Gateway,
|
||||||
GatewayBondingPayload, IdentityKey, IdentityKeyRef, InitialRewardingParams, InstantiateMsg,
|
GatewayBondingPayload, IdentityKey, IdentityKeyRef, InitialRewardingParams, InstantiateMsg,
|
||||||
Interval, MixId, MixNode, MixNodeBond, MixnodeBondingPayload, Percent,
|
Interval, MixId, MixNode, MixNodeBond, MixnodeBondingPayload, Percent,
|
||||||
RewardedSetNodeStatus, SignableGatewayBondingMsg, SignableMixNodeBondingMsg,
|
RewardedSetNodeStatus, SignableGatewayBondingMsg, SignableMixNodeBondingMsg,
|
||||||
@@ -159,6 +159,10 @@ pub mod test_helpers {
|
|||||||
coin(amount, rewarding_denom(self.deps().storage).unwrap())
|
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 {
|
pub fn current_interval(&self) -> Interval {
|
||||||
interval_storage::current_interval(self.deps().storage).unwrap()
|
interval_storage::current_interval(self.deps().storage).unwrap()
|
||||||
}
|
}
|
||||||
@@ -189,8 +193,7 @@ pub mod test_helpers {
|
|||||||
let family_head = FamilyHead::new(&identity);
|
let family_head = FamilyHead::new(&identity);
|
||||||
let owner = head_mixnode.owner;
|
let owner = head_mixnode.owner;
|
||||||
|
|
||||||
let nonce =
|
let nonce = signing_storage::get_signing_nonce(self.deps().storage, owner).unwrap();
|
||||||
signing_storage::get_signing_nonce(self.deps().storage, owner.clone()).unwrap();
|
|
||||||
|
|
||||||
let proxy = if vesting {
|
let proxy = if vesting {
|
||||||
Some(self.vesting_contract())
|
Some(self.vesting_contract())
|
||||||
@@ -243,6 +246,17 @@ pub mod test_helpers {
|
|||||||
(mix_id, keys)
|
(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 {
|
pub fn add_dummy_mixnode(&mut self, owner: &str, stake: Option<Uint128>) -> MixId {
|
||||||
let stake = self.make_mix_pledge(stake);
|
let stake = self.make_mix_pledge(stake);
|
||||||
let (mixnode, owner_signature, _) =
|
let (mixnode, owner_signature, _) =
|
||||||
@@ -541,13 +555,8 @@ pub mod test_helpers {
|
|||||||
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
|
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
|
||||||
..tests::fixtures::mix_node_fixture()
|
..tests::fixtures::mix_node_fixture()
|
||||||
};
|
};
|
||||||
let msg = mixnode_bonding_sign_payload(
|
let msg =
|
||||||
self.deps(),
|
mixnode_bonding_sign_payload(self.deps(), sender, None, mixnode.clone(), stake);
|
||||||
sender,
|
|
||||||
None,
|
|
||||||
mixnode.clone(),
|
|
||||||
stake.clone(),
|
|
||||||
);
|
|
||||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||||
|
|
||||||
(mixnode, owner_signature, keypair)
|
(mixnode, owner_signature, keypair)
|
||||||
@@ -570,13 +579,8 @@ pub mod test_helpers {
|
|||||||
..tests::fixtures::gateway_fixture()
|
..tests::fixtures::gateway_fixture()
|
||||||
};
|
};
|
||||||
|
|
||||||
let msg = gateway_bonding_sign_payload(
|
let msg =
|
||||||
self.deps(),
|
gateway_bonding_sign_payload(self.deps(), sender, None, gateway.clone(), stake);
|
||||||
sender,
|
|
||||||
None,
|
|
||||||
gateway.clone(),
|
|
||||||
stake.clone(),
|
|
||||||
);
|
|
||||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||||
|
|
||||||
(gateway, owner_signature)
|
(gateway, owner_signature)
|
||||||
@@ -1102,7 +1106,7 @@ pub mod test_helpers {
|
|||||||
deps.branch(),
|
deps.branch(),
|
||||||
&env,
|
&env,
|
||||||
env.block.height,
|
env.block.height,
|
||||||
Addr::unchecked(&format!("owner{}", i)),
|
Addr::unchecked(format!("owner{}", i)),
|
||||||
mix_id,
|
mix_id,
|
||||||
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
|
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -780,7 +780,7 @@ mod tests {
|
|||||||
let err = app
|
let err = app
|
||||||
.execute_contract(
|
.execute_contract(
|
||||||
Addr::unchecked(TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string()),
|
Addr::unchecked(TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string()),
|
||||||
flex_addr.clone(),
|
flex_addr,
|
||||||
&proposal_wrong_exp,
|
&proposal_wrong_exp,
|
||||||
&[],
|
&[],
|
||||||
)
|
)
|
||||||
@@ -856,12 +856,7 @@ mod tests {
|
|||||||
|
|
||||||
let proposer = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string();
|
let proposer = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string();
|
||||||
let res = app
|
let res = app
|
||||||
.execute_contract(
|
.execute_contract(Addr::unchecked(&proposer), flex_addr, &proposal, &[])
|
||||||
Addr::unchecked(&proposer),
|
|
||||||
flex_addr.clone(),
|
|
||||||
&proposal,
|
|
||||||
&[],
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.custom_attrs(1),
|
res.custom_attrs(1),
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ fn paging_works() {
|
|||||||
services: vec![
|
services: vec![
|
||||||
service_info(1, nym_address1.clone(), announcer1.clone()),
|
service_info(1, nym_address1.clone(), announcer1.clone()),
|
||||||
service_info(2, nym_address1.clone(), announcer1.clone()),
|
service_info(2, nym_address1.clone(), announcer1.clone()),
|
||||||
service_info(3, nym_address2.clone(), announcer1.clone()),
|
service_info(3, nym_address2.clone(), announcer1),
|
||||||
],
|
],
|
||||||
per_page: 3,
|
per_page: 3,
|
||||||
start_next_after: Some(3),
|
start_next_after: Some(3),
|
||||||
@@ -272,8 +272,8 @@ fn paging_works() {
|
|||||||
setup.query_all_with_limit(Some(3), Some(3)),
|
setup.query_all_with_limit(Some(3), Some(3)),
|
||||||
PagedServicesListResponse {
|
PagedServicesListResponse {
|
||||||
services: vec![
|
services: vec![
|
||||||
service_info(4, nym_address1.clone(), announcer2.clone()),
|
service_info(4, nym_address1, announcer2.clone()),
|
||||||
service_info(5, nym_address2.clone(), announcer2.clone()),
|
service_info(5, nym_address2, announcer2),
|
||||||
],
|
],
|
||||||
per_page: 3,
|
per_page: 3,
|
||||||
start_next_after: Some(5),
|
start_next_after: Some(5),
|
||||||
|
|||||||
@@ -160,10 +160,14 @@ pub fn execute(
|
|||||||
deps,
|
deps,
|
||||||
),
|
),
|
||||||
ExecuteMsg::PledgeMore { amount } => try_pledge_more(deps, env, info, amount),
|
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::UnbondMixnode {} => try_unbond_mixnode(info, deps),
|
||||||
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
|
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
|
||||||
try_track_unbond_mixnode(&owner, amount, info, deps)
|
try_track_unbond_mixnode(&owner, amount, info, deps)
|
||||||
}
|
}
|
||||||
|
ExecuteMsg::TrackDecreasePledge { owner, amount } => {
|
||||||
|
try_track_decrease_mixnode_pledge(&owner, amount, info, deps)
|
||||||
|
}
|
||||||
ExecuteMsg::BondGateway {
|
ExecuteMsg::BondGateway {
|
||||||
gateway,
|
gateway,
|
||||||
owner_signature,
|
owner_signature,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::storage::AccountStorageKey;
|
use crate::storage::AccountStorageKey;
|
||||||
use cosmwasm_std::{Addr, OverflowError, StdError, Uint128};
|
use cosmwasm_std::{Addr, Coin, OverflowError, StdError, Uint128};
|
||||||
use mixnet_contract_common::MixId;
|
use mixnet_contract_common::MixId;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -58,6 +58,9 @@ pub enum ContractError {
|
|||||||
#[error("VESTING ({}): No bond found for account {0}", line!())]
|
#[error("VESTING ({}): No bond found for account {0}", line!())]
|
||||||
NoBondFound(String),
|
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!())]
|
#[error("VESTING ({}): Action can only be executed by account owner -> {0}", line!())]
|
||||||
NotOwner(String),
|
NotOwner(String),
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#![warn(clippy::unwrap_used)]
|
#![warn(clippy::unwrap_used)]
|
||||||
|
|
||||||
pub mod contract;
|
pub mod contract;
|
||||||
mod errors;
|
pub mod errors;
|
||||||
mod queries;
|
mod queries;
|
||||||
mod queued_migrations;
|
mod queued_migrations;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::errors::ContractError;
|
use crate::errors::ContractError;
|
||||||
use crate::vesting::Account;
|
use crate::vesting::Account;
|
||||||
use cosmwasm_std::Order;
|
|
||||||
use cosmwasm_std::{Addr, Api, Storage, Uint128};
|
use cosmwasm_std::{Addr, Api, Storage, Uint128};
|
||||||
|
use cosmwasm_std::{Coin, Order};
|
||||||
use cw_storage_plus::{Item, Map};
|
use cw_storage_plus::{Item, Map};
|
||||||
use mixnet_contract_common::{IdentityKey, MixId};
|
use mixnet_contract_common::{IdentityKey, MixId};
|
||||||
use vesting_contract_common::PledgeData;
|
use vesting_contract_common::PledgeData;
|
||||||
@@ -157,6 +157,24 @@ pub fn save_bond_pledge(
|
|||||||
Ok(())
|
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(
|
pub fn load_gateway_pledge(
|
||||||
key: AccountStorageKey,
|
key: AccountStorageKey,
|
||||||
storage: &dyn Storage,
|
storage: &dyn Storage,
|
||||||
|
|||||||
@@ -325,12 +325,12 @@ pub mod helpers {
|
|||||||
};
|
};
|
||||||
let env = mock_env();
|
let env = mock_env();
|
||||||
let info = mock_info("admin", &[]);
|
let info = mock_info("admin", &[]);
|
||||||
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
instantiate(deps.as_mut(), env, info, msg).unwrap();
|
||||||
deps
|
deps
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vesting_account_mid_fixture(storage: &mut dyn Storage, env: &Env) -> Account {
|
pub fn vesting_account_mid_fixture(storage: &mut dyn Storage, env: &Env) -> Account {
|
||||||
let start_time_ts = env.block.time.clone();
|
let start_time_ts = env.block.time;
|
||||||
let start_time = env.block.time.seconds() - 7200;
|
let start_time = env.block.time.seconds() - 7200;
|
||||||
let periods = populate_vesting_periods(
|
let periods = populate_vesting_periods(
|
||||||
start_time,
|
start_time,
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ pub trait MixnodeBondingAccount {
|
|||||||
storage: &mut dyn Storage,
|
storage: &mut dyn Storage,
|
||||||
) -> Result<Response, ContractError>;
|
) -> 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_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||||
|
|
||||||
fn try_track_unbond_mixnode(
|
fn try_track_unbond_mixnode(
|
||||||
@@ -35,6 +41,12 @@ pub trait MixnodeBondingAccount {
|
|||||||
storage: &mut dyn Storage,
|
storage: &mut dyn Storage,
|
||||||
) -> Result<(), ContractError>;
|
) -> Result<(), ContractError>;
|
||||||
|
|
||||||
|
fn try_track_decrease_mixnode_pledge(
|
||||||
|
&self,
|
||||||
|
amount: Coin,
|
||||||
|
storage: &mut dyn Storage,
|
||||||
|
) -> Result<(), ContractError>;
|
||||||
|
|
||||||
fn try_update_mixnode_config(
|
fn try_update_mixnode_config(
|
||||||
&self,
|
&self,
|
||||||
new_config: MixNodeConfigUpdate,
|
new_config: MixNodeConfigUpdate,
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ use mixnet_contract_common::{
|
|||||||
use vesting_contract_common::events::{
|
use vesting_contract_common::events::{
|
||||||
new_ownership_transfer_event, new_periodic_vesting_account_event,
|
new_ownership_transfer_event, new_periodic_vesting_account_event,
|
||||||
new_staking_address_update_event, new_track_gateway_unbond_event,
|
new_staking_address_update_event, new_track_gateway_unbond_event,
|
||||||
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
|
new_track_mixnode_pledge_decrease_event, new_track_mixnode_unbond_event,
|
||||||
new_vested_coins_withdraw_event,
|
new_track_reward_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
|
||||||
};
|
};
|
||||||
use vesting_contract_common::messages::VestingSpecification;
|
use vesting_contract_common::messages::VestingSpecification;
|
||||||
use vesting_contract_common::PledgeCap;
|
use vesting_contract_common::PledgeCap;
|
||||||
@@ -278,6 +278,19 @@ pub fn try_pledge_more(
|
|||||||
account.try_pledge_additional_tokens(additional_pledge, &env, deps.storage)
|
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].
|
/// 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> {
|
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)?;
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
@@ -299,6 +312,22 @@ pub fn try_track_unbond_mixnode(
|
|||||||
Ok(Response::new().add_event(new_track_mixnode_unbond_event()))
|
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
|
/// Track reward collection, invoked by the mixnert contract after sucessful reward compounding or claiming
|
||||||
pub fn try_track_reward(
|
pub fn try_track_reward(
|
||||||
deps: DepsMut<'_>,
|
deps: DepsMut<'_>,
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ use mixnet_contract_common::mixnode::MixNodeConfigUpdate;
|
|||||||
use mixnet_contract_common::mixnode::MixNodeCostParams;
|
use mixnet_contract_common::mixnode::MixNodeCostParams;
|
||||||
use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, MixNode};
|
use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, MixNode};
|
||||||
use vesting_contract_common::events::{
|
use vesting_contract_common::events::{
|
||||||
new_vesting_mixnode_bonding_event, new_vesting_mixnode_unbonding_event,
|
new_vesting_decrease_pledge_event, new_vesting_mixnode_bonding_event,
|
||||||
new_vesting_pledge_more_event, new_vesting_update_mixnode_config_event,
|
new_vesting_mixnode_unbonding_event, new_vesting_pledge_more_event,
|
||||||
new_vesting_update_mixnode_cost_params_event,
|
new_vesting_update_mixnode_config_event, new_vesting_update_mixnode_cost_params_event,
|
||||||
};
|
};
|
||||||
use vesting_contract_common::PledgeData;
|
use vesting_contract_common::PledgeData;
|
||||||
|
|
||||||
@@ -109,6 +109,51 @@ impl MixnodeBondingAccount for Account {
|
|||||||
.add_event(new_vesting_pledge_more_event()))
|
.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> {
|
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError> {
|
||||||
let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf {
|
let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf {
|
||||||
owner: self.owner_address().into_string(),
|
owner: self.owner_address().into_string(),
|
||||||
@@ -135,8 +180,7 @@ impl MixnodeBondingAccount for Account {
|
|||||||
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
|
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
|
||||||
self.save_balance(new_balance, storage)?;
|
self.save_balance(new_balance, storage)?;
|
||||||
|
|
||||||
self.remove_mixnode_pledge(storage)?;
|
self.remove_mixnode_pledge(storage)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_update_mixnode_config(
|
fn try_update_mixnode_config(
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use super::VestingPeriod;
|
use super::VestingPeriod;
|
||||||
use crate::errors::ContractError;
|
use crate::errors::ContractError;
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
count_subdelegations_for_mix, load_balance, load_bond_pledge, load_delegation_timestamps,
|
count_subdelegations_for_mix, decrease_bond_pledge, load_balance, load_bond_pledge,
|
||||||
load_gateway_pledge, load_withdrawn, remove_bond_pledge, remove_delegation,
|
load_delegation_timestamps, load_gateway_pledge, load_withdrawn, remove_bond_pledge,
|
||||||
remove_gateway_pledge, save_account, save_balance, save_bond_pledge, save_gateway_pledge,
|
remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge,
|
||||||
save_withdrawn, AccountStorageKey, BlockTimestampSecs, DELEGATIONS, KEY,
|
save_gateway_pledge, save_withdrawn, AccountStorageKey, BlockTimestampSecs, DELEGATIONS, KEY,
|
||||||
};
|
};
|
||||||
use crate::traits::VestingAccount;
|
use crate::traits::VestingAccount;
|
||||||
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
|
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
|
||||||
@@ -247,6 +247,14 @@ impl Account {
|
|||||||
remove_bond_pledge(self.storage_key(), storage)
|
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(
|
pub fn load_gateway_pledge(
|
||||||
&self,
|
&self,
|
||||||
storage: &dyn Storage,
|
storage: &dyn Storage,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ mod tests {
|
|||||||
cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
|
cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
|
||||||
};
|
};
|
||||||
// Try creating an account when not admin
|
// Try creating an account when not admin
|
||||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||||
assert!(response.is_err());
|
assert!(response.is_err());
|
||||||
|
|
||||||
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
|
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());
|
assert!(old_owner_account.is_none());
|
||||||
|
|
||||||
// Not the owner
|
// Not the owner
|
||||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg);
|
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||||
assert!(response.is_err());
|
assert!(response.is_err());
|
||||||
|
|
||||||
// can't stake on behalf of the original owner anymore, but we can do it for the new one!
|
// 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());
|
assert!(response.is_ok());
|
||||||
|
|
||||||
let info = mock_info("owner", &[]);
|
let info = mock_info("owner", &[]);
|
||||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||||
assert!(response.is_err());
|
assert!(response.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ mod tests {
|
|||||||
let msg = ExecuteMsg::TransferOwnership {
|
let msg = ExecuteMsg::TransferOwnership {
|
||||||
to_address: "new_owner".to_string(),
|
to_address: "new_owner".to_string(),
|
||||||
};
|
};
|
||||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg);
|
||||||
// Only owner can transfer
|
// Only owner can transfer
|
||||||
assert!(response.is_err());
|
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);
|
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
|
||||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
let response = execute(deps.as_mut(), env, info, msg);
|
||||||
// Only owner can withdraw
|
// Only owner can withdraw
|
||||||
assert!(response.is_err());
|
assert!(response.is_err());
|
||||||
}
|
}
|
||||||
@@ -510,7 +510,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
account.load_balance(&deps.storage).unwrap(),
|
account.load_balance(&deps.storage).unwrap(),
|
||||||
Uint128::new(1000_000_000_000)
|
Uint128::new(1_000_000_000_000)
|
||||||
);
|
);
|
||||||
|
|
||||||
account
|
account
|
||||||
@@ -588,7 +588,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
|
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
|
||||||
|
|
||||||
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
let _response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||||
let account = load_account(Addr::unchecked("owner"), &deps.storage)
|
let account = load_account(Addr::unchecked("owner"), &deps.storage)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -837,7 +837,7 @@ mod tests {
|
|||||||
|
|
||||||
// but the additional one is going to fail
|
// but the additional one is going to fail
|
||||||
let res = vesting_account
|
let res = vesting_account
|
||||||
.try_delegate_to_mixnode(mix_id, delegation.clone(), &env, &mut deps.storage)
|
.try_delegate_to_mixnode(mix_id, delegation, &env, &mut deps.storage)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -196,6 +196,8 @@ 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.
|
You can either do this graphically via the Desktop Wallet, or the CLI.
|
||||||
|
|
||||||
#### Updating node information via the Desktop Wallet
|
#### 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:
|
* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner:
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -93,11 +93,6 @@ nym-validator-client = { path = "../common/client-libs/validator-client", featur
|
|||||||
] }
|
] }
|
||||||
nym-bin-common = { path = "../common/bin-common" }
|
nym-bin-common = { path = "../common/bin-common" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies.reqwest]
|
|
||||||
version = "0.11.11"
|
|
||||||
features = ["json", "native-tls-vendored"]
|
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
no-reward = []
|
no-reward = []
|
||||||
generate-ts = ["ts-rs"]
|
generate-ts = ["ts-rs"]
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use futures::Stream;
|
||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
|
use nym_gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
use tokio_stream::StreamMap;
|
use tokio_stream::StreamMap;
|
||||||
|
|
||||||
pub(crate) type GatewayMessages = Vec<Vec<u8>>;
|
pub(crate) type GatewayMessages = Vec<Vec<u8>>;
|
||||||
@@ -20,11 +23,7 @@ impl GatewaysReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stream_map(&mut self) -> &mut StreamMap<String, MixnetMessageReceiver> {
|
pub fn add_receivers(
|
||||||
&mut self.stream_map
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_recievers(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
id: identity::PublicKey,
|
id: identity::PublicKey,
|
||||||
message_receiver: MixnetMessageReceiver,
|
message_receiver: MixnetMessageReceiver,
|
||||||
@@ -35,8 +34,27 @@ impl GatewaysReader {
|
|||||||
self.ack_map.insert(channel_id, ack_receiver);
|
self.ack_map.insert(channel_id, ack_receiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_recievers(&mut self, id: &str) {
|
pub fn remove_receivers(&mut self, id: &str) {
|
||||||
self.stream_map.remove(id);
|
self.stream_map.remove(id);
|
||||||
self.ack_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 {
|
match update {
|
||||||
GatewayClientUpdate::New(id, (message_receiver, ack_receiver)) => {
|
GatewayClientUpdate::New(id, (message_receiver, ack_receiver)) => {
|
||||||
self.gateways_reader
|
self.gateways_reader
|
||||||
.add_recievers(id, message_receiver, ack_receiver);
|
.add_receivers(id, message_receiver, ack_receiver);
|
||||||
}
|
}
|
||||||
GatewayClientUpdate::Failure(id) => {
|
GatewayClientUpdate::Failure(id) => {
|
||||||
self.gateways_reader.remove_recievers(&id.to_string());
|
self.gateways_reader.remove_receivers(&id.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,10 +66,11 @@ impl PacketReceiver {
|
|||||||
// unwrap here is fine as it can only return a `None` if the PacketSender has died
|
// 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
|
// 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()),
|
update = self.clients_updater.next() => self.process_gateway_update(update.unwrap()),
|
||||||
// similarly gateway reader will never return a `None` as it's implemented
|
gateway_message = self.gateways_reader.next() => {
|
||||||
// as an infinite stream that returns Poll::Pending if it doesn't have anything
|
let Some((_gateway_id, message)) = gateway_message else {
|
||||||
// to return
|
log::error!("the gateways reader stream has terminated!");
|
||||||
Some((_gateway_id, message)) = self.gateways_reader.stream_map().next() => {
|
continue
|
||||||
|
};
|
||||||
self.process_gateway_messages(message)
|
self.process_gateway_messages(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,7 @@ async fn get_gateway_bond_annotated(
|
|||||||
let gateways = cache
|
let gateways = cache
|
||||||
.gateways_annotated_filtered()
|
.gateways_annotated_filtered()
|
||||||
.await
|
.await
|
||||||
.ok_or(ErrorResponse::new(
|
.ok_or_else(|| ErrorResponse::new("no data available", Status::ServiceUnavailable))?;
|
||||||
"no data available",
|
|
||||||
Status::ServiceUnavailable,
|
|
||||||
))?;
|
|
||||||
|
|
||||||
gateways
|
gateways
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"webpack:dev:onlyThis": "yarn webpack serve --config webpack.dev.js",
|
"webpack:dev:onlyThis": "yarn webpack serve --config webpack.dev.js",
|
||||||
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
|
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"tauri:dev:android": "cargo tauri android dev",
|
"tauri:dev:android": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android dev",
|
||||||
"tauri:build:android": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android build",
|
"tauri:build:android": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android build",
|
||||||
"dev:android": "run-p webpack:dev:onlyThis tauri:dev:android",
|
"dev:android": "run-p webpack:dev:onlyThis tauri:dev:android",
|
||||||
"prebuild": "yarn --cwd ../.. build",
|
"prebuild": "yarn --cwd ../.. build",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"clean:node": "rm -rf node_modules",
|
"clean:node": "rm -rf node_modules",
|
||||||
"clean:rust": "cargo clean --manifest-path src-tauri/Cargo.toml",
|
"clean:rust": "cargo clean --manifest-path src-tauri/Cargo.toml",
|
||||||
"clean:android": "cd src-tauri/gen/android/nym_connect/ && gradlew clean",
|
"clean:android": "cd src-tauri/gen/android/nym_connect/ && gradlew clean",
|
||||||
"clean": "run-p clean:node clean:rust clean:android"
|
"clean:": "run-p clean:node clean:rust clean:android"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.7.0",
|
"@emotion/react": "^11.7.0",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"@mui/system": ">= 5",
|
"@mui/system": ">= 5",
|
||||||
"@mui/lab": "^5.0.0-alpha.72",
|
"@mui/lab": "^5.0.0-alpha.72",
|
||||||
"@nymproject/react": "^1.0.0",
|
"@nymproject/react": "^1.0.0",
|
||||||
"@tauri-apps/api": "^2.0.0-alpha.3",
|
"@tauri-apps/api": "^2.0.0-alpha.0",
|
||||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"luxon": "^2.3.0",
|
"luxon": "^2.3.0",
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||||
"@storybook/react": "^6.5.15",
|
"@storybook/react": "^6.5.15",
|
||||||
"@svgr/webpack": "^6.1.1",
|
"@svgr/webpack": "^6.1.1",
|
||||||
"@tauri-apps/cli": "^2.0.0-alpha.8",
|
"@tauri-apps/cli": "^2.0.0-alpha.2",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^12.0.0",
|
"@testing-library/react": "^12.0.0",
|
||||||
"@types/jest": "^27.0.1",
|
"@types/jest": "^27.0.1",
|
||||||
|
|||||||
Generated
+518
-929
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,13 @@ rust-version = "1.58"
|
|||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.0.0-alpha.4", features = [] }
|
tauri-build = { version = "2.0.0-alpha.1", features = [] }
|
||||||
# tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = [] }
|
# tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = [] }
|
||||||
|
|
||||||
|
# TODO untill new tauri version includes https://github.com/tauri-apps/tauri-mobile/pull/111
|
||||||
|
[patch.crates-io]
|
||||||
|
tauri-mobile = { git = "https://github.com/tauri-apps/tauri-mobile", branch = "dev" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||||
@@ -31,14 +35,14 @@ itertools = "0.10.5"
|
|||||||
log = { version = "0.4", features = ["serde"] }
|
log = { version = "0.4", features = ["serde"] }
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
reqwest = { version = "0.11", features = ["json", "socks", "native-tls-vendored"] }
|
reqwest = { version = "0.11", features = ["json", "socks"] }
|
||||||
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
# tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open"] }
|
# tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open"] }
|
||||||
tauri = { version = "2.0.0-alpha.8", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open"] }
|
tauri = { version = "2.0.0-alpha.3", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open"] }
|
||||||
tendermint-rpc = "0.23.0"
|
tendermint-rpc = "0.23.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
time = { version = "0.3.17", features = ["local-offset"] }
|
time = { version = "0.3.17", features = ["local-offset"] }
|
||||||
@@ -46,14 +50,14 @@ tokio = { version = "1.24.1", features = ["sync", "time"] }
|
|||||||
url = "2.2"
|
url = "2.2"
|
||||||
yaml-rust = "0.4"
|
yaml-rust = "0.4"
|
||||||
|
|
||||||
nym-client-core = { path = "../../../common/client-core", features = [] }
|
nym-client-core = { path = "../../../common/client-core", features = ["mobile"], default-features = false }
|
||||||
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
||||||
nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common"}
|
nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common"}
|
||||||
nym-config-common = { path = "../../../common/config", package = "nym-config" }
|
nym-config-common = { path = "../../../common/config", package = "nym-config" }
|
||||||
nym-credential-storage = { path = "../../../common/credential-storage" }
|
nym-credential-storage = { path = "../../../common/credential-storage" }
|
||||||
nym-crypto = { path = "../../../common/crypto" }
|
nym-crypto = { path = "../../../common/crypto" }
|
||||||
nym-bin-common = { path = "../../../common/bin-common"}
|
nym-bin-common = { path = "../../../common/bin-common"}
|
||||||
nym-socks5-client-core = { path = "../../../common/socks5-client-core", default-features = true }
|
nym-socks5-client-core = { path = "../../../common/socks5-client-core", default-features = false }
|
||||||
nym-task = { path = "../../../common/task" }
|
nym-task = { path = "../../../common/task" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/build
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("com.android.library")
|
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "app.tauri"
|
|
||||||
compileSdk = 33
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 21
|
|
||||||
targetSdk = 33
|
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
consumerProguardFiles("proguard-rules.pro")
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
isMinifyEnabled = false
|
|
||||||
proguardFiles(
|
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
||||||
"proguard-rules.pro"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
implementation("androidx.core:core-ktx:1.7.0")
|
|
||||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
|
||||||
implementation("com.google.android.material:material:1.7.0")
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
|
||||||
}
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
-keep class app.tauri.** {
|
|
||||||
@app.tauri.JniMethod public <methods>;
|
|
||||||
native <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class app.tauri.plugin.JSArray {
|
|
||||||
public <init>(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclassmembers class org.json.JSONArray {
|
|
||||||
public put(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class app.tauri.plugin.JSObject {
|
|
||||||
public <init>(...);
|
|
||||||
public put(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep @app.tauri.annotation.TauriPlugin public class * {
|
|
||||||
@app.tauri.annotation.Command public <methods>;
|
|
||||||
@app.tauri.annotation.PermissionCallback <methods>;
|
|
||||||
@app.tauri.annotation.ActivityCallback <methods>;
|
|
||||||
@app.tauri.annotation.Permission <methods>;
|
|
||||||
public <init>(...);
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
assertEquals("app.tauri.test", appContext.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
</manifest>
|
|
||||||
-201
@@ -1,201 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri
|
|
||||||
|
|
||||||
import android.content.ContentUris
|
|
||||||
import android.content.Context
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.DocumentsContract
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.provider.OpenableColumns
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
internal class FsUtils {
|
|
||||||
companion object {
|
|
||||||
fun getFileUrlForUri(context: Context, uri: Uri): String? {
|
|
||||||
// DocumentProvider
|
|
||||||
if (DocumentsContract.isDocumentUri(context, uri)) {
|
|
||||||
// ExternalStorageProvider
|
|
||||||
if (isExternalStorageDocument(uri)) {
|
|
||||||
val docId: String = DocumentsContract.getDocumentId(uri)
|
|
||||||
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }
|
|
||||||
.toTypedArray()
|
|
||||||
val type = split[0]
|
|
||||||
if ("primary".equals(type, ignoreCase = true)) {
|
|
||||||
return legacyPrimaryPath(split[1])
|
|
||||||
} else {
|
|
||||||
val splitIndex = docId.indexOf(':', 1)
|
|
||||||
val tag = docId.substring(0, splitIndex)
|
|
||||||
val path = docId.substring(splitIndex + 1)
|
|
||||||
val nonPrimaryVolume = getPathToNonPrimaryVolume(context, tag)
|
|
||||||
if (nonPrimaryVolume != null) {
|
|
||||||
val result = "$nonPrimaryVolume/$path"
|
|
||||||
val file = File(result)
|
|
||||||
return if (file.exists() && file.canRead()) {
|
|
||||||
result
|
|
||||||
} else null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isDownloadsDocument(uri)) {
|
|
||||||
val id: String = DocumentsContract.getDocumentId(uri)
|
|
||||||
val contentUri: Uri = ContentUris.withAppendedId(
|
|
||||||
Uri.parse("content://downloads/public_downloads"),
|
|
||||||
java.lang.Long.valueOf(id)
|
|
||||||
)
|
|
||||||
return getDataColumn(context, contentUri, null, null)
|
|
||||||
} else if (isMediaDocument(uri)) {
|
|
||||||
val docId: String = DocumentsContract.getDocumentId(uri)
|
|
||||||
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }
|
|
||||||
.toTypedArray()
|
|
||||||
val type = split[0]
|
|
||||||
var contentUri: Uri? = null
|
|
||||||
when (type) {
|
|
||||||
"image" -> {
|
|
||||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
|
||||||
}
|
|
||||||
"video" -> {
|
|
||||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
|
||||||
}
|
|
||||||
"audio" -> {
|
|
||||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val selection = "_id=?"
|
|
||||||
val selectionArgs = arrayOf(split[1])
|
|
||||||
if (contentUri != null) {
|
|
||||||
return getDataColumn(context, contentUri, selection, selectionArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
|
|
||||||
// Return the remote address
|
|
||||||
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(
|
|
||||||
context,
|
|
||||||
uri,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
|
|
||||||
return uri.path
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of the data column for this Uri. This is useful for
|
|
||||||
* MediaStore Uris, and other file-based ContentProviders.
|
|
||||||
*
|
|
||||||
* @param context The context.
|
|
||||||
* @param uri The Uri to query.
|
|
||||||
* @param selection (Optional) Filter used in the query.
|
|
||||||
* @param selectionArgs (Optional) Selection arguments used in the query.
|
|
||||||
* @return The value of the _data column, which is typically a file path.
|
|
||||||
*/
|
|
||||||
private fun getDataColumn(
|
|
||||||
context: Context,
|
|
||||||
uri: Uri,
|
|
||||||
selection: String?,
|
|
||||||
selectionArgs: Array<String>?
|
|
||||||
): String? {
|
|
||||||
var path: String? = null
|
|
||||||
var cursor: Cursor? = null
|
|
||||||
val column = "_data"
|
|
||||||
val projection = arrayOf(column)
|
|
||||||
try {
|
|
||||||
cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
val index = cursor.getColumnIndexOrThrow(column)
|
|
||||||
path = cursor.getString(index)
|
|
||||||
}
|
|
||||||
} catch (ex: IllegalArgumentException) {
|
|
||||||
return getCopyFilePath(uri, context)
|
|
||||||
} finally {
|
|
||||||
cursor?.close()
|
|
||||||
}
|
|
||||||
return path ?: getCopyFilePath(uri, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCopyFilePath(uri: Uri, context: Context): String? {
|
|
||||||
val cursor = context.contentResolver.query(uri, null, null, null, null)!!
|
|
||||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
|
||||||
cursor.moveToFirst()
|
|
||||||
val name = cursor.getString(nameIndex)
|
|
||||||
val file = File(context.filesDir, name)
|
|
||||||
try {
|
|
||||||
val inputStream = context.contentResolver.openInputStream(uri)
|
|
||||||
val outputStream = FileOutputStream(file)
|
|
||||||
var read: Int
|
|
||||||
val maxBufferSize = 1024 * 1024
|
|
||||||
val bufferSize = min(inputStream!!.available(), maxBufferSize)
|
|
||||||
val buffers = ByteArray(bufferSize)
|
|
||||||
while (inputStream.read(buffers).also { read = it } != -1) {
|
|
||||||
outputStream.write(buffers, 0, read)
|
|
||||||
}
|
|
||||||
inputStream.close()
|
|
||||||
outputStream.close()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return null
|
|
||||||
} finally {
|
|
||||||
cursor.close()
|
|
||||||
}
|
|
||||||
return file.path
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun legacyPrimaryPath(pathPart: String): String {
|
|
||||||
return Environment.getExternalStorageDirectory().toString() + "/" + pathPart
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri The Uri to check.
|
|
||||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
|
||||||
*/
|
|
||||||
private fun isExternalStorageDocument(uri: Uri): Boolean {
|
|
||||||
return "com.android.externalstorage.documents" == uri.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri The Uri to check.
|
|
||||||
* @return Whether the Uri authority is DownloadsProvider.
|
|
||||||
*/
|
|
||||||
private fun isDownloadsDocument(uri: Uri): Boolean {
|
|
||||||
return "com.android.providers.downloads.documents" == uri.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri The Uri to check.
|
|
||||||
* @return Whether the Uri authority is MediaProvider.
|
|
||||||
*/
|
|
||||||
private fun isMediaDocument(uri: Uri): Boolean {
|
|
||||||
return "com.android.providers.media.documents" == uri.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri The Uri to check.
|
|
||||||
* @return Whether the Uri authority is Google Photos.
|
|
||||||
*/
|
|
||||||
private fun isGooglePhotosUri(uri: Uri): Boolean {
|
|
||||||
return "com.google.android.apps.photos.content" == uri.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPathToNonPrimaryVolume(context: Context, tag: String): String? {
|
|
||||||
val volumes = context.externalCacheDirs
|
|
||||||
if (volumes != null) {
|
|
||||||
for (volume in volumes) {
|
|
||||||
if (volume != null) {
|
|
||||||
val path = volume.absolutePath
|
|
||||||
val index = path.indexOf(tag)
|
|
||||||
if (index != -1) {
|
|
||||||
return path.substring(0, index) + tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
internal annotation class JniMethod
|
|
||||||
-85
@@ -1,85 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri
|
|
||||||
|
|
||||||
// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/Logger.java
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
class Logger {
|
|
||||||
companion object {
|
|
||||||
private const val LOG_TAG_CORE = "Tauri"
|
|
||||||
|
|
||||||
fun tags(vararg subtags: String): String {
|
|
||||||
return if (subtags.isNotEmpty()) {
|
|
||||||
LOG_TAG_CORE + "/" + TextUtils.join("/", subtags)
|
|
||||||
} else LOG_TAG_CORE
|
|
||||||
}
|
|
||||||
|
|
||||||
fun verbose(message: String) {
|
|
||||||
verbose(LOG_TAG_CORE, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun verbose(tag: String, message: String) {
|
|
||||||
if (!shouldLog()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Log.v(tag, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun debug(message: String) {
|
|
||||||
debug(LOG_TAG_CORE, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun debug(tag: String, message: String) {
|
|
||||||
if (!shouldLog()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Log.d(tag, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun info(message: String) {
|
|
||||||
info(LOG_TAG_CORE, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun info(tag: String, message: String) {
|
|
||||||
if (!shouldLog()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Log.i(tag, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun warn(message: String) {
|
|
||||||
warn(LOG_TAG_CORE, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun warn(tag: String, message: String) {
|
|
||||||
if (!shouldLog()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Log.w(tag, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun error(message: String) {
|
|
||||||
error(LOG_TAG_CORE, message, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun error(message: String, e: Throwable?) {
|
|
||||||
error(LOG_TAG_CORE, message, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun error(tag: String, message: String, e: Throwable?) {
|
|
||||||
if (!shouldLog()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Log.e(tag, message, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldLog(): Boolean {
|
|
||||||
return BuildConfig.DEBUG
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-78
@@ -1,78 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.os.Environment
|
|
||||||
import app.tauri.annotation.Command
|
|
||||||
import app.tauri.annotation.TauriPlugin
|
|
||||||
import app.tauri.plugin.Plugin
|
|
||||||
import app.tauri.plugin.Invoke
|
|
||||||
import app.tauri.plugin.JSObject
|
|
||||||
|
|
||||||
@TauriPlugin
|
|
||||||
class PathPlugin(private val activity: Activity): Plugin(activity) {
|
|
||||||
private fun resolvePath(invoke: Invoke, path: String?) {
|
|
||||||
val obj = JSObject()
|
|
||||||
obj.put("path", path)
|
|
||||||
invoke.resolve(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getAudioDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getExternalCacheDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.externalCacheDir?.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getConfigDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.dataDir.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getDataDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.dataDir.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getDocumentDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getDownloadDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getPictureDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getPublicDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DCIM)?.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getVideoDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.externalCacheDir?.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getResourcesDir(invoke: Invoke) {
|
|
||||||
// TODO
|
|
||||||
resolvePath(invoke, activity.cacheDir.absolutePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command
|
|
||||||
fun getCacheDir(invoke: Invoke) {
|
|
||||||
resolvePath(invoke, activity.cacheDir.absolutePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-115
@@ -1,115 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri
|
|
||||||
|
|
||||||
// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/PermissionHelper.java
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
object PermissionHelper {
|
|
||||||
/**
|
|
||||||
* Checks if a list of given permissions are all granted by the user
|
|
||||||
*
|
|
||||||
* @param permissions Permissions to check.
|
|
||||||
* @return True if all permissions are granted, false if at least one is not.
|
|
||||||
*/
|
|
||||||
fun hasPermissions(context: Context?, permissions: Array<String>): Boolean {
|
|
||||||
for (perm in permissions) {
|
|
||||||
if (ActivityCompat.checkSelfPermission(
|
|
||||||
context!!,
|
|
||||||
perm
|
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the given permission has been defined in the AndroidManifest.xml
|
|
||||||
*
|
|
||||||
* @param permission A permission to check.
|
|
||||||
* @return True if the permission has been defined in the Manifest, false if not.
|
|
||||||
*/
|
|
||||||
fun hasDefinedPermission(context: Context, permission: String): Boolean {
|
|
||||||
var hasPermission = false
|
|
||||||
val requestedPermissions = getManifestPermissions(context)
|
|
||||||
if (requestedPermissions != null && requestedPermissions.isNotEmpty()) {
|
|
||||||
val requestedPermissionsList = listOf(*requestedPermissions)
|
|
||||||
val requestedPermissionsArrayList = ArrayList(requestedPermissionsList)
|
|
||||||
if (requestedPermissionsArrayList.contains(permission)) {
|
|
||||||
hasPermission = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasPermission
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether all of the given permissions have been defined in the AndroidManifest.xml
|
|
||||||
* @param context the app context
|
|
||||||
* @param permissions a list of permissions
|
|
||||||
* @return true only if all permissions are defined in the AndroidManifest.xml
|
|
||||||
*/
|
|
||||||
fun hasDefinedPermissions(context: Context, permissions: Array<String>): Boolean {
|
|
||||||
for (permission in permissions) {
|
|
||||||
if (!hasDefinedPermission(context, permission)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the permissions defined in AndroidManifest.xml
|
|
||||||
*
|
|
||||||
* @return The permissions defined in AndroidManifest.xml
|
|
||||||
*/
|
|
||||||
private fun getManifestPermissions(context: Context): Array<String>? {
|
|
||||||
var requestedPermissions: Array<String>? = null
|
|
||||||
try {
|
|
||||||
val pm = context.packageManager
|
|
||||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
pm.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
pm.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
|
|
||||||
}
|
|
||||||
if (packageInfo != null) {
|
|
||||||
requestedPermissions = packageInfo.requestedPermissions
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
|
||||||
return requestedPermissions
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a list of permissions, return a new list with the ones not present in AndroidManifest.xml
|
|
||||||
*
|
|
||||||
* @param neededPermissions The permissions needed.
|
|
||||||
* @return The permissions not present in AndroidManifest.xml
|
|
||||||
*/
|
|
||||||
fun getUndefinedPermissions(context: Context, neededPermissions: Array<String>): Array<String?> {
|
|
||||||
val undefinedPermissions = ArrayList<String?>()
|
|
||||||
val requestedPermissions = getManifestPermissions(context)
|
|
||||||
if (requestedPermissions != null && requestedPermissions.isNotEmpty()) {
|
|
||||||
val requestedPermissionsList = listOf(*requestedPermissions)
|
|
||||||
val requestedPermissionsArrayList = ArrayList(requestedPermissionsList)
|
|
||||||
for (permission in neededPermissions) {
|
|
||||||
if (!requestedPermissionsArrayList.contains(permission)) {
|
|
||||||
undefinedPermissions.add(permission)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var undefinedPermissionArray = arrayOfNulls<String>(undefinedPermissions.size)
|
|
||||||
undefinedPermissionArray = undefinedPermissions.toArray(undefinedPermissionArray)
|
|
||||||
return undefinedPermissionArray
|
|
||||||
}
|
|
||||||
return neededPermissions as Array<String?>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
enum class PermissionState(private val state: String) {
|
|
||||||
GRANTED("granted"), DENIED("denied"), PROMPT("prompt"), PROMPT_WITH_RATIONALE("prompt-with-rationale");
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun byState(state: String): PermissionState {
|
|
||||||
return valueOf(state.uppercase(Locale.ROOT).replace('-', '_'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-9
@@ -1,9 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri.annotation
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
|
||||||
annotation class ActivityCallback
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package app.tauri.annotation
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class Permission(
|
|
||||||
/**
|
|
||||||
* An array of Android permission strings.
|
|
||||||
* Eg: {Manifest.permission.ACCESS_COARSE_LOCATION}
|
|
||||||
* or {"android.permission.ACCESS_COARSE_LOCATION"}
|
|
||||||
*/
|
|
||||||
val strings: Array<String> = [],
|
|
||||||
/**
|
|
||||||
* An optional name to use instead of the Android permission string.
|
|
||||||
*/
|
|
||||||
val alias: String = ""
|
|
||||||
)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user