Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eaa0f055af | |||
| ad507c6a12 | |||
| e5d68a5e7f | |||
| 7ddd819ff3 | |||
| 83b416d12d | |||
| b9c775c3ae | |||
| 6f669866e9 | |||
| 4e61fefec8 | |||
| b4514ecd83 | |||
| 4f6902525e | |||
| 881139e36f | |||
| 32e2557456 | |||
| 8b44820e51 | |||
| 5e6417f837 | |||
| dfb2a2f380 | |||
| d1de751850 | |||
| ecee6ca863 | |||
| 31ea3f92e2 | |||
| f19c934fae | |||
| 10d6f20de7 | |||
| 96b33bfbe4 | |||
| 444c787d0a | |||
| 61fcd4ac69 | |||
| b76802e6eb | |||
| 7d351029a4 | |||
| 4ee445c119 | |||
| 61ddeea495 | |||
| 7b802033b3 | |||
| b484f47369 | |||
| 66979df10c | |||
| 82f161fb91 | |||
| 9d0fd681d4 | |||
| c2ab47a102 | |||
| 8704c21621 | |||
| 03ffb25bf9 | |||
| 70db1ad062 | |||
| 952ed9b642 | |||
| f57fe79686 | |||
| 9179f1c351 | |||
| c4f7a1e09d | |||
| 701012a968 | |||
| 9767f72b8f | |||
| 7b10d92ca4 | |||
| 2c6e5eb673 | |||
| 02fde4e530 | |||
| cc25fc1f32 | |||
| c971e486b5 | |||
| 96a9eb6f6a | |||
| 9eeb61ea0a | |||
| 08042c61ad | |||
| 36c74f30e5 | |||
| 4956d13bdc | |||
| d9f6c0723e | |||
| 52f5656190 | |||
| 21cd90f238 | |||
| 72e243042e | |||
| c2ad4e5bb4 | |||
| 5f7f5ef92d | |||
| 008afe7a85 |
@@ -51,6 +51,10 @@ jobs:
|
||||
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
|
||||
|
||||
- name: Set CARGO_FEATURES
|
||||
run: |
|
||||
echo 'CARGO_FEATURES=--features wireguard' >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -60,8 +64,8 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
args: --workspace --release ${{ env.CARGO_FEATURES }}
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
+24
-1
@@ -4,6 +4,30 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.8-wispa] (2024-07-10)
|
||||
|
||||
- add event parsing to support cosmos_sdk > 0.50 ([#4697])
|
||||
- Fix NR config compatibility ([#4690])
|
||||
- Remove UserAgent constructor since it's weakly typed ([#4689])
|
||||
- [bugfix]: Node_api_check CLI looked over roles on blacklisted nodes ([#4687])
|
||||
- Add mixnodes to self describing api cache ([#4684])
|
||||
- Move and whole bump of crates to workspace and upgrade some ([#4680])
|
||||
- Remove code that refers to removed nym-network-statistics ([#4679])
|
||||
- Remove nym-network-statistics ([#4678])
|
||||
- Create UserAgent that can be passed from the binary to the nym api client ([#4677])
|
||||
- Add authenticator ([#4667])
|
||||
|
||||
[#4697]: https://github.com/nymtech/nym/pull/4697
|
||||
[#4690]: https://github.com/nymtech/nym/pull/4690
|
||||
[#4689]: https://github.com/nymtech/nym/pull/4689
|
||||
[#4687]: https://github.com/nymtech/nym/pull/4687
|
||||
[#4684]: https://github.com/nymtech/nym/pull/4684
|
||||
[#4680]: https://github.com/nymtech/nym/pull/4680
|
||||
[#4679]: https://github.com/nymtech/nym/pull/4679
|
||||
[#4678]: https://github.com/nymtech/nym/pull/4678
|
||||
[#4677]: https://github.com/nymtech/nym/pull/4677
|
||||
[#4667]: https://github.com/nymtech/nym/pull/4667
|
||||
|
||||
## [2024.7-doubledecker] (2024-07-04)
|
||||
|
||||
- Add an early return in `parse_raw_str_logs` for empty raw log strings. ([#4686])
|
||||
@@ -513,7 +537,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#3187]: https://github.com/nymtech/nym/issues/3187
|
||||
[#3203]: https://github.com/nymtech/nym/pull/3203
|
||||
[#3199]: https://github.com/nymtech/nym/pull/3199
|
||||
>>>>>>> master
|
||||
|
||||
## [v1.1.13] (2023-03-15)
|
||||
|
||||
|
||||
Generated
+31
-19
@@ -1378,7 +1378,7 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"parking_lot 0.12.2",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
@@ -1394,7 +1394,7 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"parking_lot 0.12.2",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
@@ -2093,7 +2093,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.36"
|
||||
version = "1.1.37"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.5.4",
|
||||
@@ -3596,6 +3596,18 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mix-fetch-wasm"
|
||||
version = "1.3.0-rc.0"
|
||||
@@ -3777,7 +3789,7 @@ dependencies = [
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
@@ -3849,7 +3861,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.40"
|
||||
version = "1.1.41"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4057,7 +4069,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.38"
|
||||
version = "1.1.39"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -4136,7 +4148,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.37"
|
||||
version = "1.1.38"
|
||||
dependencies = [
|
||||
"bs58 0.5.1",
|
||||
"clap 4.5.4",
|
||||
@@ -4950,7 +4962,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.38"
|
||||
version = "1.1.39"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -5001,7 +5013,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
@@ -5264,7 +5276,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.37"
|
||||
version = "1.1.38"
|
||||
dependencies = [
|
||||
"bs58 0.5.1",
|
||||
"clap 4.5.4",
|
||||
@@ -5779,7 +5791,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7745,7 +7757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
@@ -8454,22 +8466,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.37.0"
|
||||
version = "1.39.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
|
||||
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"mio 1.0.1",
|
||||
"parking_lot 0.12.2",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8484,9 +8495,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.2.0"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9439,6 +9450,7 @@ dependencies = [
|
||||
name = "wasm-utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"futures",
|
||||
"getrandom 0.2.15",
|
||||
"gloo-net",
|
||||
|
||||
+4
-4
@@ -277,11 +277,11 @@ tar = "0.4.40"
|
||||
tempfile = "3.5.0"
|
||||
thiserror = "1.0.48"
|
||||
time = "0.3.30"
|
||||
tokio = "1.33.0"
|
||||
tokio-stream = "0.1.14"
|
||||
tokio-test = "0.4.2"
|
||||
tokio = "1.39"
|
||||
tokio-stream = "0.1.15"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.10"
|
||||
tokio-util = "0.7.11"
|
||||
toml = "0.8.14"
|
||||
tower = "0.4.13"
|
||||
tower-http = "0.5.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.37"
|
||||
version = "1.1.38"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.37"
|
||||
version = "1.1.38"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -683,6 +683,24 @@ pub trait MixnetSigningClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn migrate_vested_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(fee, MixnetExecuteMsg::MigrateVestedMixNode {}, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn migrate_vested_delegation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::MigrateVestedDelegation { mix_id },
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
async fn testing_resolve_all_pending_events(
|
||||
&self,
|
||||
@@ -928,6 +946,12 @@ mod tests {
|
||||
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => client
|
||||
.withdraw_delegator_reward_on_behalf(owner.parse().unwrap(), mix_id, None)
|
||||
.ignore(),
|
||||
MixnetExecuteMsg::MigrateVestedMixNode { .. } => {
|
||||
client.migrate_vested_mixnode(None).ignore()
|
||||
}
|
||||
MixnetExecuteMsg::MigrateVestedDelegation { mix_id } => {
|
||||
client.migrate_vested_delegation(mix_id, None).ignore()
|
||||
}
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
||||
|
||||
@@ -437,6 +437,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
|
||||
use nym_vesting_contract_common::ExecuteMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
@@ -560,6 +561,9 @@ mod tests {
|
||||
VestingExecuteMsg::UpdateLockedPledgeCap { address, cap } => client
|
||||
.update_locked_pledge_cap(address.parse().unwrap(), cap, None)
|
||||
.ignore(),
|
||||
// those will never be manually called by clients
|
||||
ExecuteMsg::TrackMigratedMixnode { .. } => "explicitly_ignored".ignore(),
|
||||
ExecuteMsg::TrackMigratedDelegation { .. } => "explicitly_ignored".ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use crate::nyxd::cosmwasm_client::client_traits::SigningCosmWasmClient;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Config, GasPrice, Hash, Height};
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use crate::nyxd::contract_traits::{NymContractsProvider, TypedNymContracts};
|
||||
use crate::nyxd::cosmwasm_client::types::{
|
||||
ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::tendermint::{self, abci, block::Height, evidence::Evidence, Genesis, Hash};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use log::{debug, info};
|
||||
use nym_mixnet_contract_common::{
|
||||
InitialRewardingParams, InstantiateMsg, OperatingCostRange, Percent, ProfitMarginRange,
|
||||
};
|
||||
use nym_network_defaults::mainnet::MIX_DENOM;
|
||||
use nym_network_defaults::TOTAL_SUPPLY;
|
||||
use nym_validator_client::nyxd::{AccountId, Coin};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn default_maximum_operating_cost() -> Coin {
|
||||
Coin::new(TOTAL_SUPPLY, MIX_DENOM.base)
|
||||
}
|
||||
|
||||
pub fn default_minimum_operating_cost() -> Coin {
|
||||
Coin::new(0, MIX_DENOM.base)
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
@@ -50,6 +61,18 @@ pub struct Args {
|
||||
|
||||
#[clap(long, default_value_t = 240)]
|
||||
pub active_set_size: u32,
|
||||
|
||||
#[clap(long, default_value_t = Percent::zero())]
|
||||
pub minimum_profit_margin_percent: Percent,
|
||||
|
||||
#[clap(long, default_value_t = Percent::hundred())]
|
||||
pub maximum_profit_margin_percent: Percent,
|
||||
|
||||
#[clap(long, default_value_t = default_minimum_operating_cost())]
|
||||
pub minimum_interval_operating_cost: Coin,
|
||||
|
||||
#[clap(long, default_value_t = default_maximum_operating_cost())]
|
||||
pub maximum_interval_operating_cost: Coin,
|
||||
}
|
||||
|
||||
pub async fn generate(args: Args) {
|
||||
@@ -97,6 +120,10 @@ pub async fn generate(args: Args) {
|
||||
.expect("Rewarding (mix) denom has to be set")
|
||||
});
|
||||
|
||||
if args.minimum_interval_operating_cost.denom != args.maximum_interval_operating_cost.denom {
|
||||
panic!("different denoms for operating cost bounds")
|
||||
}
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
rewarding_validator_address: rewarding_validator_address.to_string(),
|
||||
vesting_contract_address: vesting_contract_address.to_string(),
|
||||
@@ -104,6 +131,14 @@ pub async fn generate(args: Args) {
|
||||
epochs_in_interval: args.epochs_in_interval,
|
||||
epoch_duration: Duration::from_secs(args.epoch_duration),
|
||||
initial_rewarding_params,
|
||||
profit_margin: ProfitMarginRange {
|
||||
minimum: args.minimum_profit_margin_percent,
|
||||
maximum: args.maximum_profit_margin_percent,
|
||||
},
|
||||
interval_operating_cost: OperatingCostRange {
|
||||
minimum: args.minimum_interval_operating_cost.amount.into(),
|
||||
maximum: args.maximum_interval_operating_cost.amount.into(),
|
||||
},
|
||||
};
|
||||
|
||||
debug!("instantiate_msg: {:?}", instantiate_msg);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2024 - 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::MixId;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn migrate_vested_delegation(args: Args, client: SigningClient) {
|
||||
let mix_id = match args.mix_id {
|
||||
Some(mix_id) => mix_id,
|
||||
None => {
|
||||
let identity_key = args
|
||||
.identity_key
|
||||
.expect("either mix_id or mix_identity has to be specified");
|
||||
let node_details = client
|
||||
.get_mixnode_details_by_identity(identity_key)
|
||||
.await
|
||||
.expect("contract query failed")
|
||||
.mixnode_details
|
||||
.expect("mixnode with the specified identity doesnt exist");
|
||||
node_details.mix_id()
|
||||
}
|
||||
};
|
||||
|
||||
let res = client
|
||||
.migrate_vested_delegation(mix_id, None)
|
||||
.await
|
||||
.expect("failed to migrate delegation!");
|
||||
|
||||
info!("migration result: {:?}", res)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ pub mod rewards;
|
||||
|
||||
pub mod delegate_to_mixnode;
|
||||
pub mod delegate_to_multiple_mixnodes;
|
||||
pub mod migrate_vested_delegation;
|
||||
pub mod query_for_delegations;
|
||||
pub mod undelegate_from_mixnode;
|
||||
pub mod vesting_delegate_to_mixnode;
|
||||
@@ -35,4 +36,6 @@ pub enum MixnetDelegatorsCommands {
|
||||
DelegateVesting(vesting_delegate_to_mixnode::Args),
|
||||
/// Undelegate from a mixnode (when originally using locked tokens)
|
||||
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
|
||||
/// Migrate the delegation to use liquid tokens
|
||||
MigrateVestedDelegation(migrate_vested_delegation::Args),
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
|
||||
mix_id,
|
||||
amount,
|
||||
proxy,
|
||||
..
|
||||
} => {
|
||||
if owner.as_str() == client.nyxd.address().as_ref() {
|
||||
table.add_row(vec![
|
||||
@@ -111,6 +112,7 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
|
||||
owner,
|
||||
mix_id,
|
||||
proxy,
|
||||
..
|
||||
} => {
|
||||
if owner.as_str() == client.nyxd.address().as_ref() {
|
||||
table.add_row(vec![
|
||||
|
||||
+2
-13
@@ -8,7 +8,7 @@ use cosmwasm_std::Coin;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_mixnet_contract_common::construct_gateway_bonding_sign_payload;
|
||||
use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -39,10 +39,6 @@ pub struct Args {
|
||||
)]
|
||||
pub amount: u128,
|
||||
|
||||
/// Indicates whether the gateway is going to get bonded via a vesting account
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
@@ -74,15 +70,8 @@ pub async fn create_payload(args: Args, client: SigningClient) {
|
||||
};
|
||||
|
||||
let address = account_id_to_cw_addr(&client.address());
|
||||
let proxy = if args.with_vesting_account {
|
||||
Some(account_id_to_cw_addr(
|
||||
client.vesting_contract_address().unwrap(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let payload = construct_gateway_bonding_sign_payload(nonce, address, proxy, coin, gateway);
|
||||
let payload = construct_gateway_bonding_sign_payload(nonce, address, coin, gateway);
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -5,33 +5,21 @@ use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Label that is going to be used for creating the family
|
||||
#[arg(long)]
|
||||
pub family_label: String,
|
||||
|
||||
/// Indicates whether the family is going to get created via a vesting account
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
}
|
||||
|
||||
pub async fn create_family(args: Args, client: SigningClient) {
|
||||
info!("Create family");
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to create family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to create family")
|
||||
};
|
||||
let res = client
|
||||
.create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to create family");
|
||||
|
||||
info!("Family creation result: {:?}", res);
|
||||
}
|
||||
|
||||
+3
-16
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::{account_id_to_cw_addr, DataWrapper};
|
||||
use crate::utils::DataWrapper;
|
||||
use clap::Parser;
|
||||
use cosmrs::AccountId;
|
||||
use log::info;
|
||||
@@ -10,7 +10,7 @@ use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::construct_family_join_permit;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -18,10 +18,6 @@ pub struct Args {
|
||||
#[arg(long)]
|
||||
pub address: AccountId,
|
||||
|
||||
/// Indicates whether the member joining the family is going to use the vesting account for joining.
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
// might as well validate the value when parsing the arguments
|
||||
/// Identity of the member for whom we're issuing the permit
|
||||
#[arg(long)]
|
||||
@@ -68,18 +64,9 @@ pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryCli
|
||||
}
|
||||
};
|
||||
|
||||
// let address = account_id_to_cw_addr(&args.address);
|
||||
let proxy = if args.with_vesting_account {
|
||||
Some(account_id_to_cw_addr(
|
||||
client.vesting_contract_address().unwrap(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let head = FamilyHead::new(mixnode.bond_information.identity());
|
||||
|
||||
let payload = construct_family_join_permit(nonce, head, proxy, args.member.to_base58_string());
|
||||
let payload = construct_family_join_permit(nonce, head, args.member.to_base58_string());
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use nym_contracts_common::signing::MessageSignature;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -16,10 +15,6 @@ pub struct Args {
|
||||
#[arg(long)]
|
||||
pub family_head: identity::PublicKey,
|
||||
|
||||
/// Indicates whether the member joining the family is going to do so via the vesting contract
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
/// Permission, as provided by the family head, for joining the family
|
||||
#[arg(long)]
|
||||
pub join_permit: MessageSignature,
|
||||
@@ -30,17 +25,10 @@ pub async fn join_family(args: Args, client: SigningClient) {
|
||||
|
||||
let family_head = FamilyHead::new(args.family_head.to_base58_string());
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family")
|
||||
};
|
||||
let res = client
|
||||
.join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family");
|
||||
|
||||
info!("Family join result: {:?}", res);
|
||||
}
|
||||
|
||||
@@ -7,17 +7,12 @@ use log::info;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// The head of the family that we intend to leave
|
||||
#[arg(long)]
|
||||
pub family_head: identity::PublicKey,
|
||||
|
||||
/// Indicates whether we joined the family via the vesting contract
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
}
|
||||
|
||||
pub async fn leave_family(args: Args, client: SigningClient) {
|
||||
@@ -25,17 +20,10 @@ pub async fn leave_family(args: Args, client: SigningClient) {
|
||||
|
||||
let family_head = FamilyHead::new(args.family_head.to_base58_string());
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family")
|
||||
};
|
||||
let res = client
|
||||
.leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family");
|
||||
|
||||
info!("Family leave result: {:?}", res);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
pub async fn migrate_vested_mixnode(_args: Args, client: SigningClient) {
|
||||
let res = client
|
||||
.migrate_vested_mixnode(None)
|
||||
.await
|
||||
.expect("failed to migrate mixnode!");
|
||||
|
||||
info!("migration result: {:?}", res)
|
||||
}
|
||||
+2
-13
@@ -11,7 +11,7 @@ use nym_mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNode
|
||||
use nym_network_defaults::{
|
||||
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
|
||||
};
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
|
||||
use nym_validator_client::nyxd::CosmWasmCoin;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -52,10 +52,6 @@ pub struct Args {
|
||||
)]
|
||||
pub amount: u128,
|
||||
|
||||
/// Indicates whether the mixnode is going to get bonded via a vesting account
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
@@ -100,16 +96,9 @@ pub async fn create_payload(args: Args, client: SigningClient) {
|
||||
};
|
||||
|
||||
let address = account_id_to_cw_addr(&client.address());
|
||||
let proxy = if args.with_vesting_account {
|
||||
Some(account_id_to_cw_addr(
|
||||
client.vesting_contract_address().unwrap(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let payload =
|
||||
construct_mixnode_bonding_sign_payload(nonce, address, proxy, coin, mixnode, cost_params);
|
||||
construct_mixnode_bonding_sign_payload(nonce, address, coin, mixnode, cost_params);
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ pub mod bond_mixnode;
|
||||
pub mod decrease_pledge;
|
||||
pub mod families;
|
||||
pub mod keys;
|
||||
pub mod migrate_vested_mixnode;
|
||||
pub mod mixnode_bonding_sign_payload;
|
||||
pub mod pledge_more;
|
||||
pub mod rewards;
|
||||
@@ -52,4 +53,6 @@ pub enum MixnetOperatorsMixnodeCommands {
|
||||
DecreasePledge(decrease_pledge::Args),
|
||||
/// Decrease pledge with locked tokens
|
||||
DecreasePledgeVesting(vesting_decrease_pledge::Args),
|
||||
/// Migrate the mixnode to use liquid tokens
|
||||
MigrateVestedNode(migrate_vested_mixnode::Args),
|
||||
}
|
||||
|
||||
@@ -218,7 +218,6 @@ where
|
||||
#[derive(Serialize)]
|
||||
pub struct ContractMessageContent<T> {
|
||||
pub sender: Addr,
|
||||
pub proxy: Option<Addr>,
|
||||
pub funds: Vec<Coin>,
|
||||
pub data: T,
|
||||
}
|
||||
@@ -233,25 +232,17 @@ where
|
||||
}
|
||||
|
||||
impl<T> ContractMessageContent<T> {
|
||||
pub fn new(sender: Addr, proxy: Option<Addr>, funds: Vec<Coin>, data: T) -> Self {
|
||||
pub fn new(sender: Addr, funds: Vec<Coin>, data: T) -> Self {
|
||||
ContractMessageContent {
|
||||
sender,
|
||||
proxy,
|
||||
funds,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_info(info: MessageInfo, signer: Addr, data: T) -> Self {
|
||||
let proxy = if info.sender == signer {
|
||||
None
|
||||
} else {
|
||||
Some(info.sender)
|
||||
};
|
||||
|
||||
ContractMessageContent {
|
||||
sender: signer,
|
||||
proxy,
|
||||
funds: info.funds,
|
||||
data,
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ impl Delegation {
|
||||
cumulative_reward_ratio: Decimal,
|
||||
amount: Coin,
|
||||
height: u64,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
amount.amount <= TOKEN_SUPPLY,
|
||||
@@ -78,7 +77,7 @@ impl Delegation {
|
||||
cumulative_reward_ratio,
|
||||
amount,
|
||||
height,
|
||||
proxy,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{EpochEventId, EpochState, IdentityKey, MixId};
|
||||
use crate::{EpochEventId, EpochState, IdentityKey, MixId, OperatingCostRange, ProfitMarginRange};
|
||||
use contracts_common::signing::verifier::ApiVerifierError;
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -76,21 +77,11 @@ pub enum MixnetContractError {
|
||||
#[error("Received multiple coin types during staking")]
|
||||
MultipleDenoms,
|
||||
|
||||
#[error("Proxy address mismatch, expected {existing}, got {incoming}")]
|
||||
ProxyMismatch { existing: String, incoming: String },
|
||||
|
||||
#[error("Proxy address ({received}) is not set to the vesting contract ({vesting_contract})")]
|
||||
ProxyIsNotVestingContract {
|
||||
received: Addr,
|
||||
vesting_contract: Addr,
|
||||
},
|
||||
#[error(
|
||||
"Sender of this message ({received}) is not the vesting contract ({vesting_contract})"
|
||||
)]
|
||||
SenderIsNotVestingContract {
|
||||
received: Addr,
|
||||
vesting_contract: Addr,
|
||||
},
|
||||
|
||||
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
|
||||
MalformedEd25519IdentityKey(String),
|
||||
@@ -239,6 +230,30 @@ pub enum MixnetContractError {
|
||||
#[from]
|
||||
source: ApiVerifierError,
|
||||
},
|
||||
|
||||
#[error("this operation is no longer allowed to be performed with vesting tokens. please move them to your liquid balance and try again")]
|
||||
DisabledVestingOperation,
|
||||
|
||||
#[error(
|
||||
"this mixnode has not been bonded with the vesting tokens or has already been migrated"
|
||||
)]
|
||||
NotAVestingMixnode,
|
||||
|
||||
#[error("this delegation has not been performed with the vesting tokens or has already been migrated")]
|
||||
NotAVestingDelegation,
|
||||
|
||||
#[error("the provided profit margin ({provided}) is outside the allowed range: {range}")]
|
||||
ProfitMarginOutsideRange {
|
||||
provided: Percent,
|
||||
range: ProfitMarginRange,
|
||||
},
|
||||
|
||||
#[error("the provided interval operating cost ({provided}{denom}) is outside the allowed range: {range}")]
|
||||
OperatingCostOutsideRange {
|
||||
denom: String,
|
||||
provided: Uint128,
|
||||
range: OperatingCostRange,
|
||||
},
|
||||
}
|
||||
|
||||
impl MixnetContractError {
|
||||
|
||||
@@ -103,7 +103,6 @@ impl Display for MixnetEventType {
|
||||
// attributes that are used in multiple places
|
||||
pub const OWNER_KEY: &str = "owner";
|
||||
pub const AMOUNT_KEY: &str = "amount";
|
||||
pub const PROXY_KEY: &str = "proxy";
|
||||
|
||||
// event-specific attributes
|
||||
|
||||
@@ -163,7 +162,6 @@ pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval";
|
||||
pub fn new_delegation_event(
|
||||
created_at: BlockHeight,
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_id: MixId,
|
||||
unit_reward: Decimal,
|
||||
@@ -171,58 +169,34 @@ pub fn new_delegation_event(
|
||||
Event::new(MixnetEventType::Delegation)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
.add_attribute(UNIT_REWARD_KEY, unit_reward.to_string())
|
||||
}
|
||||
|
||||
pub fn new_delegation_on_unbonded_node_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_delegation_on_unbonded_node_event(delegator: &Addr, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::Delegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_pending_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_pending_delegation_event(delegator: &Addr, amount: &Coin, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::PendingDelegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_withdraw_operator_reward_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Coin,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_withdraw_operator_reward_event(owner: &Addr, amount: Coin, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::WithdrawOperatorReward)
|
||||
.add_attribute(OWNER_KEY, owner.as_str())
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_withdraw_delegator_reward_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Coin,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_withdraw_delegator_reward_event(delegator: &Addr, amount: Coin, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::WithdrawDelegatorReward)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
}
|
||||
@@ -278,59 +252,43 @@ pub fn new_pending_rewarding_params_update_event(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_undelegation_event(
|
||||
created_at: BlockHeight,
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_undelegation_event(created_at: BlockHeight, delegator: &Addr, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::Undelegation)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_pending_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_pending_undelegation_event(delegator: &Addr, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::PendingUndelegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_gateway_bonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::GatewayBonding)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_gateway_unbonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::GatewayUnbonding)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_mixnode_bonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
mix_id: MixId,
|
||||
@@ -341,7 +299,6 @@ pub fn new_mixnode_bonding_event(
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(ASSIGNED_LAYER_KEY, assigned_layer)
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
@@ -380,7 +337,6 @@ pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Ev
|
||||
|
||||
pub fn new_pending_mixnode_unbonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
@@ -388,43 +344,33 @@ pub fn new_pending_mixnode_unbonding_event(
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
}
|
||||
|
||||
pub fn new_mixnode_config_update_event(
|
||||
mix_id: MixId,
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
update: &MixNodeConfigUpdate,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeConfigUpdate)
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
|
||||
}
|
||||
|
||||
pub fn new_gateway_config_update_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
update: &GatewayConfigUpdate,
|
||||
) -> Event {
|
||||
pub fn new_gateway_config_update_event(owner: &Addr, update: &GatewayConfigUpdate) -> Event {
|
||||
Event::new(MixnetEventType::GatewayConfigUpdate)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(UPDATED_GATEWAY_CONFIG_KEY, update.to_inline_json())
|
||||
}
|
||||
|
||||
pub fn new_mixnode_pending_cost_params_update_event(
|
||||
mix_id: MixId,
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
new_costs: &MixNodeCostParams,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::{IdentityKey, IdentityKeyRef};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -84,10 +83,10 @@ impl FamilyHead {
|
||||
}
|
||||
|
||||
impl Family {
|
||||
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: String) -> Self {
|
||||
pub fn new(head: FamilyHead, label: String) -> Self {
|
||||
Family {
|
||||
head,
|
||||
proxy: proxy.map(|p| p.to_string()),
|
||||
proxy: None,
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,19 +55,13 @@ pub struct GatewayBond {
|
||||
}
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn new(
|
||||
pledge_amount: Coin,
|
||||
owner: Addr,
|
||||
block_height: u64,
|
||||
gateway: Gateway,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
pub fn new(pledge_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
|
||||
GatewayBond {
|
||||
pledge_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
proxy,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ use crate::helpers::IntoBaseDecimal;
|
||||
use crate::reward_params::{NodeRewardParams, RewardingParams};
|
||||
use crate::rewarding::helpers::truncate_reward;
|
||||
use crate::rewarding::RewardDistribution;
|
||||
use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey};
|
||||
use crate::{
|
||||
Delegation, EpochEventId, EpochId, IdentityKey, MixId, OperatingCostRange, Percent,
|
||||
ProfitMarginRange, SphinxKey,
|
||||
};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
@@ -152,6 +155,16 @@ impl MixNodeRewarding {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn normalise_profit_margin(&mut self, allowed_range: ProfitMarginRange) {
|
||||
self.cost_params.profit_margin_percent =
|
||||
allowed_range.normalise(self.cost_params.profit_margin_percent)
|
||||
}
|
||||
|
||||
pub fn normalise_operating_cost(&mut self, allowed_range: OperatingCostRange) {
|
||||
self.cost_params.interval_operating_cost.amount =
|
||||
allowed_range.normalise(self.cost_params.interval_operating_cost.amount)
|
||||
}
|
||||
|
||||
/// Determines whether this node is still bonded. This is performed via a simple check,
|
||||
/// if there are no tokens left associated with the operator, it means they have unbonded
|
||||
/// and those params only exist for the purposes of calculating rewards for delegators that
|
||||
@@ -518,7 +531,6 @@ impl MixNodeBond {
|
||||
original_pledge: Coin,
|
||||
layer: Layer,
|
||||
mix_node: MixNode,
|
||||
proxy: Option<Addr>,
|
||||
bonding_height: u64,
|
||||
) -> Self {
|
||||
MixNodeBond {
|
||||
@@ -527,7 +539,7 @@ impl MixNodeBond {
|
||||
original_pledge,
|
||||
layer,
|
||||
mix_node,
|
||||
proxy,
|
||||
proxy: None,
|
||||
bonding_height,
|
||||
is_unbonding: false,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::delegation::{self, OwnerProxySubKey};
|
||||
@@ -12,6 +12,7 @@ use crate::reward_params::{
|
||||
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
|
||||
};
|
||||
use crate::types::{ContractStateParams, LayerAssignment, MixId};
|
||||
use crate::{OperatingCostRange, ProfitMarginRange};
|
||||
use contracts_common::{signing::MessageSignature, IdentityKey, Percent};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
@@ -57,6 +58,12 @@ pub struct InstantiateMsg {
|
||||
pub epochs_in_interval: u32,
|
||||
pub epoch_duration: Duration,
|
||||
pub initial_rewarding_params: InitialRewardingParams,
|
||||
|
||||
#[serde(default)]
|
||||
pub profit_margin: ProfitMarginRange,
|
||||
|
||||
#[serde(default)]
|
||||
pub interval_operating_cost: OperatingCostRange,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -269,6 +276,12 @@ pub enum ExecuteMsg {
|
||||
owner: String,
|
||||
},
|
||||
|
||||
// vesting migration:
|
||||
MigrateVestedMixNode {},
|
||||
MigrateVestedDelegation {
|
||||
mix_id: MixId,
|
||||
},
|
||||
|
||||
// testing-only
|
||||
#[cfg(feature = "contract-testing")]
|
||||
TestingResolveAllPendingEvents {
|
||||
@@ -381,6 +394,9 @@ impl ExecuteMsg {
|
||||
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => {
|
||||
format!("withdrawing delegator reward from mixnode {mix_id} on behalf")
|
||||
}
|
||||
ExecuteMsg::MigrateVestedMixNode { .. } => "migrate vested mixnode".into(),
|
||||
ExecuteMsg::MigrateVestedDelegation { .. } => "migrate vested delegation".to_string(),
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
ExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
||||
"resolving all pending events".into()
|
||||
|
||||
@@ -38,6 +38,7 @@ pub enum PendingEpochEventKind {
|
||||
/// Request to create a delegation towards particular mixnode.
|
||||
/// Note that if such delegation already exists, it will get updated with the provided token amount.
|
||||
#[serde(alias = "Delegate")]
|
||||
#[non_exhaustive]
|
||||
Delegate {
|
||||
/// The address of the owner of the delegation.
|
||||
owner: Addr,
|
||||
@@ -55,6 +56,7 @@ pub enum PendingEpochEventKind {
|
||||
|
||||
/// Request to remove delegation from particular mixnode.
|
||||
#[serde(alias = "Undelegate")]
|
||||
#[non_exhaustive]
|
||||
Undelegate {
|
||||
/// The address of the owner of the delegation.
|
||||
owner: Addr,
|
||||
@@ -109,6 +111,23 @@ impl PendingEpochEventKind {
|
||||
kind: self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_delegate(owner: Addr, mix_id: MixId, amount: Coin) -> Self {
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_undelegate(owner: Addr, mix_id: MixId) -> Self {
|
||||
PendingEpochEventKind::Undelegate {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
|
||||
|
||||
-1
@@ -47,7 +47,6 @@ impl SimulatedNode {
|
||||
self.rewarding_details.total_unit_reward,
|
||||
delegation,
|
||||
42,
|
||||
None,
|
||||
);
|
||||
|
||||
self.delegations.insert(delegator, delegation);
|
||||
|
||||
@@ -37,13 +37,12 @@ impl SigningPurpose for MixnodeBondingPayload {
|
||||
pub fn construct_mixnode_bonding_sign_payload(
|
||||
nonce: Nonce,
|
||||
sender: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
mix_node: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
) -> SignableMixNodeBondingMsg {
|
||||
let payload = MixnodeBondingPayload::new(mix_node, cost_params);
|
||||
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
|
||||
let content = ContractMessageContent::new(sender, vec![pledge], payload);
|
||||
|
||||
SignableMessage::new(nonce, content)
|
||||
}
|
||||
@@ -68,12 +67,11 @@ impl SigningPurpose for GatewayBondingPayload {
|
||||
pub fn construct_gateway_bonding_sign_payload(
|
||||
nonce: Nonce,
|
||||
sender: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
gateway: Gateway,
|
||||
) -> SignableGatewayBondingMsg {
|
||||
let payload = GatewayBondingPayload::new(gateway);
|
||||
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
|
||||
let content = ContractMessageContent::new(sender, vec![pledge], payload);
|
||||
|
||||
SignableMessage::new(nonce, content)
|
||||
}
|
||||
@@ -82,17 +80,14 @@ pub fn construct_gateway_bonding_sign_payload(
|
||||
pub struct FamilyJoinPermit {
|
||||
// the granter of this permit
|
||||
family_head: FamilyHead,
|
||||
// whether the **member** will want to join via the proxy (i.e. vesting contract)
|
||||
proxy: Option<Addr>,
|
||||
// the actual member we want to permit to join
|
||||
member_node: IdentityKey,
|
||||
}
|
||||
|
||||
impl FamilyJoinPermit {
|
||||
pub fn new(family_head: FamilyHead, proxy: Option<Addr>, member_node: IdentityKey) -> Self {
|
||||
pub fn new(family_head: FamilyHead, member_node: IdentityKey) -> Self {
|
||||
Self {
|
||||
family_head,
|
||||
proxy,
|
||||
member_node,
|
||||
}
|
||||
}
|
||||
@@ -107,10 +102,9 @@ impl SigningPurpose for FamilyJoinPermit {
|
||||
pub fn construct_family_join_permit(
|
||||
nonce: Nonce,
|
||||
family_head: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
member_node: IdentityKey,
|
||||
) -> SignableFamilyJoinPermitMsg {
|
||||
let payload = FamilyJoinPermit::new(family_head, proxy, member_node);
|
||||
let payload = FamilyJoinPermit::new(family_head, member_node);
|
||||
|
||||
// note: we're NOT wrapping it in `ContractMessageContent` because the family head is not going to be the one
|
||||
// sending the message to the contract
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::Layer;
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
use cosmwasm_std::Coin;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Index;
|
||||
|
||||
// type aliases for better reasoning about available data
|
||||
@@ -15,6 +17,65 @@ pub type SphinxKeyRef<'a> = &'a str;
|
||||
pub type MixId = u32;
|
||||
pub type BlockHeight = u64;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct RangedValue<T> {
|
||||
pub minimum: T,
|
||||
pub maximum: T,
|
||||
}
|
||||
|
||||
impl<T> Copy for RangedValue<T> where T: Copy {}
|
||||
|
||||
pub type ProfitMarginRange = RangedValue<Percent>;
|
||||
pub type OperatingCostRange = RangedValue<Uint128>;
|
||||
|
||||
impl<T> Display for RangedValue<T>
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} - {}", self.minimum, self.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProfitMarginRange {
|
||||
fn default() -> Self {
|
||||
ProfitMarginRange {
|
||||
minimum: Percent::zero(),
|
||||
maximum: Percent::hundred(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OperatingCostRange {
|
||||
fn default() -> Self {
|
||||
OperatingCostRange {
|
||||
minimum: Uint128::zero(),
|
||||
|
||||
// 1 billion (native tokens, i.e. 1 billion * 1'000'000 base tokens) - the total supply
|
||||
maximum: Uint128::new(1_000_000_000_000_000),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RangedValue<T>
|
||||
where
|
||||
T: Copy + PartialOrd + PartialEq,
|
||||
{
|
||||
pub fn normalise(&self, value: T) -> T {
|
||||
if value < self.minimum {
|
||||
self.minimum
|
||||
} else if value > self.maximum {
|
||||
self.maximum
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
pub fn within_range(&self, value: T) -> bool {
|
||||
value >= self.minimum && value <= self.maximum
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies layer assignment for the given mixnode.
|
||||
#[cw_serde]
|
||||
pub struct LayerAssignment {
|
||||
@@ -154,4 +215,14 @@ pub struct ContractStateParams {
|
||||
|
||||
/// Minimum amount a gateway must pledge to get into the system.
|
||||
pub minimum_gateway_pledge: Coin,
|
||||
|
||||
/// Defines the allowed profit margin range of operators.
|
||||
/// default: 0% - 100%
|
||||
#[serde(default)]
|
||||
pub profit_margin: ProfitMarginRange,
|
||||
|
||||
/// Defines the allowed interval operating cost range of operators.
|
||||
/// default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)
|
||||
#[serde(default)]
|
||||
pub interval_operating_cost: OperatingCostRange,
|
||||
}
|
||||
|
||||
@@ -167,3 +167,11 @@ pub fn new_track_undelegation_event() -> Event {
|
||||
pub fn new_track_reward_event() -> Event {
|
||||
Event::new(TRACK_REWARD_EVENT_TYPE)
|
||||
}
|
||||
|
||||
pub fn new_track_migrate_mixnode_event() -> Event {
|
||||
Event::new("track_migrate_vesting_mixnode")
|
||||
}
|
||||
|
||||
pub fn new_track_migrate_delegation_event() -> Event {
|
||||
Event::new("track_migrate_vesting_delegation")
|
||||
}
|
||||
|
||||
@@ -136,6 +136,14 @@ pub enum ExecuteMsg {
|
||||
address: String,
|
||||
cap: PledgeCap,
|
||||
},
|
||||
TrackMigratedMixnode {
|
||||
owner: String,
|
||||
},
|
||||
// no need to track migrated gateways as there are no vesting gateways on mainnet
|
||||
TrackMigratedDelegation {
|
||||
owner: String,
|
||||
mix_id: MixId,
|
||||
},
|
||||
}
|
||||
|
||||
impl ExecuteMsg {
|
||||
@@ -171,6 +179,10 @@ impl ExecuteMsg {
|
||||
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
|
||||
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
|
||||
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
|
||||
ExecuteMsg::TrackMigratedMixnode { .. } => "VestingExecuteMsg::TrackMigratedMixnode",
|
||||
ExecuteMsg::TrackMigratedDelegation { .. } => {
|
||||
"VestingExecuteMsg::TrackMigratedDelegation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{v6, v7};
|
||||
|
||||
impl From<v7::response::StaticConnectFailureReason> for v6::response::StaticConnectFailureReason {
|
||||
fn from(failure: v7::response::StaticConnectFailureReason) -> Self {
|
||||
match failure {
|
||||
v7::response::StaticConnectFailureReason::RequestedIpAlreadyInUse => {
|
||||
v6::response::StaticConnectFailureReason::RequestedIpAlreadyInUse
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse => {
|
||||
v6::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::OutOfDateTimestamp => {
|
||||
v6::response::StaticConnectFailureReason::Other("out of date timestamp".to_string())
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::Other(reason) => {
|
||||
v6::response::StaticConnectFailureReason::Other(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::DynamicConnectFailureReason> for v6::response::DynamicConnectFailureReason {
|
||||
fn from(failure: v7::response::DynamicConnectFailureReason) -> Self {
|
||||
match failure {
|
||||
v7::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse => {
|
||||
v6::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
v7::response::DynamicConnectFailureReason::NoAvailableIp => {
|
||||
v6::response::DynamicConnectFailureReason::NoAvailableIp
|
||||
}
|
||||
v7::response::DynamicConnectFailureReason::Other(err) => {
|
||||
v6::response::DynamicConnectFailureReason::Other(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::InfoResponseReply> for v6::response::InfoResponseReply {
|
||||
fn from(reply: v7::response::InfoResponseReply) -> Self {
|
||||
match reply {
|
||||
v7::response::InfoResponseReply::Generic { msg } => {
|
||||
v6::response::InfoResponseReply::Generic { msg }
|
||||
}
|
||||
v7::response::InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
} => v6::response::InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
},
|
||||
v7::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
|
||||
v6::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::InfoLevel> for v6::response::InfoLevel {
|
||||
fn from(level: v7::response::InfoLevel) -> Self {
|
||||
match level {
|
||||
v7::response::InfoLevel::Info => v6::response::InfoLevel::Info,
|
||||
v7::response::InfoLevel::Warn => v6::response::InfoLevel::Warn,
|
||||
v7::response::InfoLevel::Error => v6::response::InfoLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod conversion;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
|
||||
@@ -198,6 +198,17 @@ impl IpPacketRequestData {
|
||||
| IpPacketRequestData::Health(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
|
||||
match self {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
IpPacketRequestData::Ping(_) => None,
|
||||
IpPacketRequestData::Health(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A static connect request is when the client provides the internal IP address it will use on the
|
||||
|
||||
@@ -42,6 +42,7 @@ pub struct NymNetworkDetails {
|
||||
pub endpoints: Vec<ValidatorDetails>,
|
||||
pub contracts: NymContracts,
|
||||
pub explorer_api: Option<String>,
|
||||
pub nym_vpn_api_url: Option<String>,
|
||||
}
|
||||
|
||||
// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms
|
||||
@@ -71,6 +72,7 @@ impl NymNetworkDetails {
|
||||
endpoints: Default::default(),
|
||||
contracts: Default::default(),
|
||||
explorer_api: Default::default(),
|
||||
nym_vpn_api_url: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +128,7 @@ impl NymNetworkDetails {
|
||||
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
|
||||
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
|
||||
.with_explorer_api(get_optional_env(var_names::EXPLORER_API))
|
||||
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
|
||||
}
|
||||
|
||||
pub fn new_mainnet() -> Self {
|
||||
@@ -155,6 +158,7 @@ impl NymNetworkDetails {
|
||||
),
|
||||
},
|
||||
explorer_api: parse_optional_str(mainnet::EXPLORER_API),
|
||||
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +267,19 @@ impl NymNetworkDetails {
|
||||
self.explorer_api = endpoint.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_vpn_api_url<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
|
||||
self.nym_vpn_api_url = endpoint.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nym_vpn_api_url(&self) -> Option<Url> {
|
||||
self.nym_vpn_api_url.as_ref().map(|url| {
|
||||
url.parse()
|
||||
.expect("the provided nym-vpn api url is invalid!")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -31,6 +31,7 @@ pub const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
|
||||
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
|
||||
pub const NYM_VPN_API: &str = "https://nymvpn.net/api/";
|
||||
|
||||
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
|
||||
pub const EXIT_POLICY_URL: &str =
|
||||
@@ -114,6 +115,7 @@ pub fn export_to_env() {
|
||||
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
|
||||
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
|
||||
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
|
||||
set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
|
||||
}
|
||||
|
||||
pub fn export_to_env_if_not_set() {
|
||||
|
||||
@@ -24,6 +24,7 @@ pub const NYM_API: &str = "NYM_API";
|
||||
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
|
||||
pub const EXPLORER_API: &str = "EXPLORER_API";
|
||||
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
|
||||
pub const NYM_VPN_API: &str = "NYM_VPN_API";
|
||||
|
||||
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ pub enum NymTopologyError {
|
||||
PayloadBuilder,
|
||||
|
||||
#[error("Outfox: {0}")]
|
||||
#[cfg(feature = "outfox")]
|
||||
Outfox(#[from] nym_sphinx_types::OutfoxError),
|
||||
|
||||
#[error("{0}")]
|
||||
|
||||
@@ -7,6 +7,7 @@ use nym_sphinx_routing::SphinxRouteMaker;
|
||||
use nym_sphinx_types::Node;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct NymTopologyRouteProvider<R> {
|
||||
rng: R,
|
||||
inner: NymTopology,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use nym_mixnet_contract_common::ContractsCommonError;
|
||||
use nym_validator_client::error::TendermintRpcError;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::{nyxd::error::NyxdError, ValidatorClientError};
|
||||
@@ -8,6 +9,9 @@ use thiserror::Error;
|
||||
// TODO: ask @MS why this even exists
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TypesError {
|
||||
#[error(transparent)]
|
||||
ContractsCommon(#[from] ContractsCommonError),
|
||||
|
||||
#[error("{source}")]
|
||||
NyxdError {
|
||||
#[from]
|
||||
|
||||
@@ -84,6 +84,7 @@ impl PendingEpochEventData {
|
||||
mix_id,
|
||||
amount,
|
||||
proxy,
|
||||
..
|
||||
} => Ok(PendingEpochEventData::Delegate {
|
||||
owner: owner.into_string(),
|
||||
mix_id,
|
||||
@@ -94,6 +95,7 @@ impl PendingEpochEventData {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy,
|
||||
..
|
||||
} => Ok(PendingEpochEventData::Undelegate {
|
||||
owner: owner.into_string(),
|
||||
mix_id,
|
||||
|
||||
@@ -17,6 +17,8 @@ gloo-utils = { workspace = true }
|
||||
gloo-net = { workspace = true, features = ["websocket"], optional = true }
|
||||
#gloo-net = { path = "../../../../gloo/crates/net", features = ["websocket"], optional = true }
|
||||
|
||||
console_error_panic_hook = { workspace = true, optional = true }
|
||||
|
||||
# we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums
|
||||
[dependencies.tungstenite]
|
||||
workspace = true
|
||||
@@ -28,7 +30,7 @@ workspace = true
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["sleep"]
|
||||
default = ["sleep", "console_error_panic_hook"]
|
||||
sleep = ["web-sys", "web-sys/Window"]
|
||||
websocket = [
|
||||
"getrandom",
|
||||
|
||||
@@ -6,6 +6,8 @@ pub mod error;
|
||||
pub mod public_key;
|
||||
pub mod registration;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub use config::Config;
|
||||
pub use error::Error;
|
||||
pub use public_key::PeerPublicKey;
|
||||
@@ -13,5 +15,10 @@ pub use registration::{
|
||||
ClientMac, ClientMessage, GatewayClient, GatewayClientRegistry, InitMessage, Nonce,
|
||||
};
|
||||
|
||||
// To avoid any problems, keep this stale check time bigger (>2x) then the bandwidth cap
|
||||
// reset time (currently that one is 24h, at UTC midnight)
|
||||
pub const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60 * 24 * 3); // 3 days
|
||||
pub const DEFAULT_PEER_TIMEOUT_CHECK: Duration = Duration::from_secs(5); // 5 seconds
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub use registration::HmacSha256;
|
||||
|
||||
@@ -4,19 +4,15 @@
|
||||
use chrono::{Timelike, Utc};
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, WireguardInterfaceApi};
|
||||
use nym_wireguard_types::registration::{RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY};
|
||||
use nym_wireguard_types::{DEFAULT_PEER_TIMEOUT, DEFAULT_PEER_TIMEOUT_CHECK};
|
||||
use std::time::SystemTime;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::WgApiWrapper;
|
||||
|
||||
// To avoid any problems, keep this stale check time bigger (>2x) then the bandwidth cap
|
||||
// reset time (currently that one is 24h, at UTC midnight)
|
||||
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60 * 24 * 3); // 3 days
|
||||
const DEFAULT_PEER_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute
|
||||
|
||||
pub enum PeerControlRequest {
|
||||
AddPeer(Peer),
|
||||
RemovePeer(Key),
|
||||
@@ -43,6 +39,7 @@ pub struct PeerController {
|
||||
active_peers: HashMap<Key, Peer>,
|
||||
suspended_peers: HashMap<Key, Peer>,
|
||||
last_seen_bandwidth: HashMap<Key, u64>,
|
||||
timeout_count: u8,
|
||||
}
|
||||
|
||||
impl PeerController {
|
||||
@@ -68,6 +65,7 @@ impl PeerController {
|
||||
active_peers,
|
||||
suspended_peers: HashMap::new(),
|
||||
last_seen_bandwidth: HashMap::new(),
|
||||
timeout_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +117,15 @@ impl PeerController {
|
||||
.iter()
|
||||
.map(|(key, peer)| (key.clone(), peer.rx_bytes + peer.tx_bytes))
|
||||
.collect();
|
||||
|
||||
// Do in-memory updates of bandwidth every DEFAULT_PEER_TIMEOUT_CHECK
|
||||
// and storage updates every 5 * DEFAULT_PEER_TIMEOUT_CHECK, because in-memory
|
||||
// is more important for client query preciseness
|
||||
self.timeout_count = self.timeout_count % 5 + 1;
|
||||
if !reset && self.timeout_count < 5 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if reset {
|
||||
self.active_peers = host.peers;
|
||||
} else {
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::support::helpers::{mix_coin, mix_coins, vesting_owner};
|
||||
use crate::support::setup::{TestSetup, MIX_DENOM};
|
||||
use cosmwasm_std::Addr;
|
||||
use cw_multi_test::Executor;
|
||||
use nym_contracts_common::Percent;
|
||||
use nym_mixnet_contract_common::error::MixnetContractError;
|
||||
use nym_mixnet_contract_common::{ContractStateParams, MixNodeCostParams};
|
||||
use nym_mixnet_contract_common::{MixOwnershipResponse, QueryMsg as MixnetQueryMsg};
|
||||
use nym_vesting_contract_common::{ExecuteMsg as VestingExecuteMsg, 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);
|
||||
}
|
||||
@@ -24,5 +24,7 @@ pub fn default_mixnet_init_msg() -> nym_mixnet_contract_common::InstantiateMsg {
|
||||
rewarded_set_size: 240,
|
||||
active_set_size: 100,
|
||||
},
|
||||
profit_margin: Default::default(),
|
||||
interval_operating_cost: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,27 +12,33 @@ pub fn mixnet_owner() -> Addr {
|
||||
Addr::unchecked(MIXNET_OWNER)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn vesting_owner() -> Addr {
|
||||
Addr::unchecked(VESTING_OWNER)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn rewarding_validator() -> Addr {
|
||||
Addr::unchecked(REWARDING_VALIDATOR)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn mix_coins(amount: u128) -> Vec<Coin> {
|
||||
coins(amount, MIX_DENOM)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn mix_coin(amount: u128) -> Coin {
|
||||
coin(amount, MIX_DENOM)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn test_rng() -> ChaCha20Rng {
|
||||
let dummy_seed = [42u8; 32];
|
||||
ChaCha20Rng::from_seed(dummy_seed)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn mixnet_contract_wrapper() -> Box<dyn Contract<Empty>> {
|
||||
Box::new(
|
||||
ContractWrapper::new(
|
||||
|
||||
@@ -26,6 +26,7 @@ pub const VESTING_OWNER: &str = "vesting-owner";
|
||||
pub const REWARDING_VALIDATOR: &str = "rewarding-validator";
|
||||
pub const MIX_DENOM: &str = "unym";
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct ContractInstantiationResult {
|
||||
mixnet_contract_address: Addr,
|
||||
vesting_contract_address: Addr,
|
||||
@@ -69,14 +70,15 @@ impl TestSetupBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct TestSetup {
|
||||
pub app: App,
|
||||
pub rng: ChaCha20Rng,
|
||||
|
||||
pub mixnet_contract: Addr,
|
||||
pub vesting_contract: Addr,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl TestSetup {
|
||||
pub fn new_simple() -> Self {
|
||||
TestSetup::new(Default::default(), fixtures::default_mixnet_init_msg())
|
||||
@@ -91,7 +93,6 @@ impl TestSetup {
|
||||
app,
|
||||
rng: test_rng(),
|
||||
mixnet_contract: contracts.mixnet_contract_address,
|
||||
vesting_contract: contracts.vesting_contract_address,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +100,6 @@ impl TestSetup {
|
||||
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
|
||||
@@ -209,7 +206,6 @@ impl TestSetup {
|
||||
pub fn valid_mixnode_with_sig(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
cost_params: MixNodeCostParams,
|
||||
stake: Coin,
|
||||
) -> (MixNode, MessageSignature) {
|
||||
@@ -239,8 +235,7 @@ impl TestSetup {
|
||||
};
|
||||
|
||||
let payload = MixnodeBondingPayload::new(mixnode.clone(), cost_params);
|
||||
let content =
|
||||
ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), 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);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod decrease_mixnode_pledge;
|
||||
mod support;
|
||||
|
||||
@@ -26,6 +26,28 @@
|
||||
"initial_rewarding_params": {
|
||||
"$ref": "#/definitions/InitialRewardingParams"
|
||||
},
|
||||
"interval_operating_cost": {
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rewarding_denom": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -112,6 +134,42 @@
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1146,6 +1204,42 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"migrate_vested_mix_node"
|
||||
],
|
||||
"properties": {
|
||||
"migrate_vested_mix_node": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"migrate_vested_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"migrate_vested_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
@@ -1172,6 +1266,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -1198,6 +1304,18 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -1532,6 +1650,38 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
@@ -8063,6 +8213,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -8089,6 +8251,62 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -8109,6 +8327,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -8135,6 +8365,18 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -8154,6 +8396,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
|
||||
@@ -1029,6 +1029,42 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"migrate_vested_mix_node"
|
||||
],
|
||||
"properties": {
|
||||
"migrate_vested_mix_node": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"migrate_vested_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"migrate_vested_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
@@ -1055,6 +1091,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -1081,6 +1129,18 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -1415,6 +1475,38 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
|
||||
@@ -22,6 +22,28 @@
|
||||
"initial_rewarding_params": {
|
||||
"$ref": "#/definitions/InitialRewardingParams"
|
||||
},
|
||||
"interval_operating_cost": {
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rewarding_denom": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -108,6 +130,42 @@
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -103,6 +115,62 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -8,6 +8,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -34,6 +46,18 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -53,6 +77,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
|
||||
@@ -11,7 +11,8 @@ use cosmwasm_std::{
|
||||
};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{
|
||||
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg,
|
||||
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg,
|
||||
OperatingCostRange, ProfitMarginRange, QueryMsg,
|
||||
};
|
||||
use nym_contracts_common::set_build_information;
|
||||
|
||||
@@ -24,6 +25,8 @@ fn default_initial_state(
|
||||
rewarding_validator_address: Addr,
|
||||
rewarding_denom: String,
|
||||
vesting_contract_address: Addr,
|
||||
profit_margin: ProfitMarginRange,
|
||||
interval_operating_cost: OperatingCostRange,
|
||||
) -> ContractState {
|
||||
ContractState {
|
||||
owner,
|
||||
@@ -40,6 +43,8 @@ fn default_initial_state(
|
||||
denom: rewarding_denom,
|
||||
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
|
||||
},
|
||||
profit_margin,
|
||||
interval_operating_cost,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -71,6 +76,8 @@ pub fn instantiate(
|
||||
rewarding_validator_address.clone(),
|
||||
msg.rewarding_denom,
|
||||
vesting_contract_address,
|
||||
msg.profit_margin,
|
||||
msg.interval_operating_cost,
|
||||
);
|
||||
let starting_interval =
|
||||
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
|
||||
@@ -118,44 +125,6 @@ pub fn execute(
|
||||
ExecuteMsg::KickFamilyMember { member } => {
|
||||
crate::families::transactions::try_head_kick_member(deps, info, member)
|
||||
}
|
||||
ExecuteMsg::CreateFamilyOnBehalf {
|
||||
owner_address,
|
||||
label,
|
||||
} => crate::families::transactions::try_create_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
owner_address,
|
||||
label,
|
||||
),
|
||||
ExecuteMsg::JoinFamilyOnBehalf {
|
||||
member_address,
|
||||
join_permit,
|
||||
family_head,
|
||||
} => crate::families::transactions::try_join_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
member_address,
|
||||
join_permit,
|
||||
family_head,
|
||||
),
|
||||
ExecuteMsg::LeaveFamilyOnBehalf {
|
||||
member_address,
|
||||
family_head,
|
||||
} => crate::families::transactions::try_leave_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
member_address,
|
||||
family_head,
|
||||
),
|
||||
ExecuteMsg::KickFamilyMemberOnBehalf {
|
||||
head_address,
|
||||
member,
|
||||
} => crate::families::transactions::try_head_kick_member_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
head_address,
|
||||
member,
|
||||
),
|
||||
// state/sys-params-related
|
||||
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
|
||||
crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address(
|
||||
@@ -232,62 +201,23 @@ pub fn execute(
|
||||
cost_params,
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::BondMixnodeOnBehalf {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner,
|
||||
owner_signature,
|
||||
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner,
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::PledgeMore {} => {
|
||||
crate::mixnodes::transactions::try_increase_pledge(deps, env, info)
|
||||
}
|
||||
ExecuteMsg::PledgeMoreOnBehalf { owner } => {
|
||||
crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, owner)
|
||||
}
|
||||
ExecuteMsg::DecreasePledge { decrease_by } => {
|
||||
crate::mixnodes::transactions::try_decrease_pledge(deps, env, info, decrease_by)
|
||||
}
|
||||
ExecuteMsg::DecreasePledgeOnBehalf { owner, decrease_by } => {
|
||||
crate::mixnodes::transactions::try_decrease_pledge_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
decrease_by,
|
||||
owner,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UnbondMixnode {} => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
|
||||
}
|
||||
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, env, info, owner)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
|
||||
crate::mixnodes::transactions::try_update_mixnode_cost_params(
|
||||
deps, env, info, new_costs,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner } => {
|
||||
crate::mixnodes::transactions::try_update_mixnode_cost_params_on_behalf(
|
||||
deps, env, info, new_costs, owner,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
|
||||
crate::mixnodes::transactions::try_update_mixnode_config(deps, info, new_config)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfigOnBehalf { new_config, owner } => {
|
||||
crate::mixnodes::transactions::try_update_mixnode_config_on_behalf(
|
||||
deps, info, new_config, owner,
|
||||
)
|
||||
}
|
||||
|
||||
// gateway-related:
|
||||
ExecuteMsg::BondGateway {
|
||||
@@ -300,52 +230,22 @@ pub fn execute(
|
||||
gateway,
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::BondGatewayOnBehalf {
|
||||
gateway,
|
||||
owner,
|
||||
owner_signature,
|
||||
} => crate::gateways::transactions::try_add_gateway_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
gateway,
|
||||
owner,
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::UnbondGateway {} => {
|
||||
crate::gateways::transactions::try_remove_gateway(deps, info)
|
||||
}
|
||||
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
|
||||
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
|
||||
}
|
||||
ExecuteMsg::UpdateGatewayConfig { new_config } => {
|
||||
crate::gateways::transactions::try_update_gateway_config(deps, info, new_config)
|
||||
}
|
||||
ExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner } => {
|
||||
crate::gateways::transactions::try_update_gateway_config_on_behalf(
|
||||
deps, info, new_config, owner,
|
||||
)
|
||||
}
|
||||
|
||||
// delegation-related:
|
||||
ExecuteMsg::DelegateToMixnode { mix_id } => {
|
||||
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_id)
|
||||
}
|
||||
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate } => {
|
||||
crate::delegations::transactions::try_delegate_to_mixnode_on_behalf(
|
||||
deps, env, info, mix_id, delegate,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
|
||||
crate::delegations::transactions::try_remove_delegation_from_mixnode(
|
||||
deps, env, info, mix_id,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, delegate } => {
|
||||
crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
|
||||
deps, env, info, mix_id, delegate,
|
||||
)
|
||||
}
|
||||
|
||||
// reward-related
|
||||
ExecuteMsg::RewardMixnode {
|
||||
@@ -356,16 +256,37 @@ pub fn execute(
|
||||
ExecuteMsg::WithdrawOperatorReward {} => {
|
||||
crate::rewards::transactions::try_withdraw_operator_reward(deps, info)
|
||||
}
|
||||
ExecuteMsg::WithdrawOperatorRewardOnBehalf { owner } => {
|
||||
crate::rewards::transactions::try_withdraw_operator_reward_on_behalf(deps, info, owner)
|
||||
}
|
||||
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
|
||||
crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id)
|
||||
}
|
||||
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => {
|
||||
crate::rewards::transactions::try_withdraw_delegator_reward_on_behalf(
|
||||
deps, info, mix_id, owner,
|
||||
)
|
||||
|
||||
// vesting migration:
|
||||
ExecuteMsg::MigrateVestedMixNode { .. } => {
|
||||
crate::vesting_migration::try_migrate_vested_mixnode(deps, info)
|
||||
}
|
||||
ExecuteMsg::MigrateVestedDelegation { mix_id } => {
|
||||
crate::vesting_migration::try_migrate_vested_delegation(deps, info, mix_id)
|
||||
}
|
||||
|
||||
// legacy vesting
|
||||
ExecuteMsg::CreateFamilyOnBehalf { .. }
|
||||
| ExecuteMsg::JoinFamilyOnBehalf { .. }
|
||||
| ExecuteMsg::LeaveFamilyOnBehalf { .. }
|
||||
| ExecuteMsg::KickFamilyMemberOnBehalf { .. }
|
||||
| ExecuteMsg::BondMixnodeOnBehalf { .. }
|
||||
| ExecuteMsg::PledgeMoreOnBehalf { .. }
|
||||
| ExecuteMsg::DecreasePledgeOnBehalf { .. }
|
||||
| ExecuteMsg::UnbondMixnodeOnBehalf { .. }
|
||||
| ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. }
|
||||
| ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. }
|
||||
| ExecuteMsg::BondGatewayOnBehalf { .. }
|
||||
| ExecuteMsg::UnbondGatewayOnBehalf { .. }
|
||||
| ExecuteMsg::UpdateGatewayConfigOnBehalf { .. }
|
||||
| ExecuteMsg::DelegateToMixnodeOnBehalf { .. }
|
||||
| ExecuteMsg::UndelegateFromMixnodeOnBehalf { .. }
|
||||
| ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. }
|
||||
| ExecuteMsg::WithdrawDelegatorRewardOnBehalf { .. } => {
|
||||
Err(MixnetContractError::DisabledVestingOperation)
|
||||
}
|
||||
|
||||
// testing-only
|
||||
@@ -607,13 +528,15 @@ pub fn query(
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(
|
||||
deps: DepsMut<'_>,
|
||||
mut deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
msg: MigrateMsg,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
set_build_information!(deps.storage)?;
|
||||
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
|
||||
crate::queued_migrations::vesting_purge(deps.branch())?;
|
||||
|
||||
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
|
||||
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
|
||||
// environment, one of the contracts will HAVE TO go through a migration
|
||||
@@ -631,7 +554,7 @@ pub fn migrate(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::Decimal;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
use mixnet_contract_common::reward_params::{IntervalRewardParams, RewardingParams};
|
||||
use mixnet_contract_common::{InitialRewardingParams, Percent};
|
||||
use std::time::Duration;
|
||||
@@ -657,6 +580,14 @@ mod tests {
|
||||
rewarded_set_size: 543,
|
||||
active_set_size: 123,
|
||||
},
|
||||
profit_margin: ProfitMarginRange {
|
||||
minimum: "0.05".parse().unwrap(),
|
||||
maximum: "0.95".parse().unwrap(),
|
||||
},
|
||||
interval_operating_cost: OperatingCostRange {
|
||||
minimum: "1000".parse().unwrap(),
|
||||
maximum: "10000".parse().unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
let sender = mock_info("sender", &[]);
|
||||
@@ -678,6 +609,14 @@ mod tests {
|
||||
denom: "uatom".into(),
|
||||
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
|
||||
},
|
||||
profit_margin: ProfitMarginRange {
|
||||
minimum: Percent::from_percentage_value(5).unwrap(),
|
||||
maximum: Percent::from_percentage_value(95).unwrap(),
|
||||
},
|
||||
interval_operating_cost: OperatingCostRange {
|
||||
minimum: Uint128::new(1000),
|
||||
maximum: Uint128::new(10000),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -302,10 +302,7 @@ mod tests {
|
||||
|
||||
mod delegator_delegations {
|
||||
use super::*;
|
||||
use crate::delegations::transactions::try_delegate_to_mixnode_on_behalf;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{coin, Addr};
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
#[test]
|
||||
fn obeys_limits() {
|
||||
@@ -453,25 +450,10 @@ mod tests {
|
||||
#[test]
|
||||
fn all_retrieved_delegations_are_from_the_specified_delegator() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
// it means we have, for example, delegation from "delegator1" towards mix1, mix2, ...., from "delegator2" towards mix1, mix2, ...., etc
|
||||
add_dummy_mixes_with_delegations(&mut test, 50, 100);
|
||||
|
||||
// add some proxies while we're at it to make sure they're queried for separately
|
||||
let with_proxy = "delegator42";
|
||||
let vesting_contract = test.vesting_contract();
|
||||
for mix_id in 1..=25 {
|
||||
try_delegate_to_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(vesting_contract.as_ref(), &[coin(100_000, TEST_COIN_DENOM)]),
|
||||
mix_id,
|
||||
with_proxy.into(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
test.execute_all_pending_events();
|
||||
|
||||
// make few queries
|
||||
let res1 =
|
||||
query_delegator_delegations_paged(test.deps(), "delegator2".into(), None, None)
|
||||
@@ -490,59 +472,6 @@ mod tests {
|
||||
.delegations
|
||||
.into_iter()
|
||||
.all(|d| d.owner == Addr::unchecked("delegator35")));
|
||||
|
||||
let with_proxy_full =
|
||||
query_delegator_delegations_paged(test.deps(), with_proxy.into(), None, None)
|
||||
.unwrap();
|
||||
assert_eq!(with_proxy_full.delegations.len(), 125);
|
||||
|
||||
// all delegations have correct owner
|
||||
assert!(with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.all(|d| d.owner == Addr::unchecked(with_proxy)));
|
||||
|
||||
// and we have 100 delegations without proxy and 25 with
|
||||
let no_proxy = with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_none())
|
||||
.count();
|
||||
assert_eq!(no_proxy, 100);
|
||||
let proxy = with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_some())
|
||||
.count();
|
||||
assert_eq!(proxy, 25);
|
||||
|
||||
assert!(with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_some())
|
||||
.all(|d| d.proxy.as_ref().unwrap() == vesting_contract));
|
||||
|
||||
// now make sure that if we do it in paged manner, we'll get exactly the same result
|
||||
let per_page = Some(15);
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = query_delegator_delegations_paged(
|
||||
test.deps(),
|
||||
with_proxy.into(),
|
||||
start_after,
|
||||
per_page,
|
||||
)
|
||||
.unwrap();
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(with_proxy_full.delegations, delegations)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::support::helpers::{
|
||||
ensure_epoch_in_progress_state, ensure_sent_by_vesting_contract, validate_delegation_stake,
|
||||
};
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut, Env, MessageInfo, Response};
|
||||
use crate::support::helpers::{ensure_epoch_in_progress_state, validate_delegation_stake};
|
||||
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_pending_delegation_event, new_pending_undelegation_event,
|
||||
@@ -21,30 +19,6 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_delegate_to_mixnode(deps, env, mix_id, info.sender, info.funds, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_delegate_to_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
delegate: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let delegate = deps.api.addr_validate(&delegate)?;
|
||||
_try_delegate_to_mixnode(deps, env, mix_id, delegate, info.funds, Some(info.sender))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_delegate_to_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
mix_id: MixId,
|
||||
delegate: Addr,
|
||||
amount: Vec<Coin>,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// delegation is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
@@ -52,7 +26,7 @@ pub(crate) fn _try_delegate_to_mixnode(
|
||||
// check if the delegation contains any funds of the appropriate denomination
|
||||
let contract_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
|
||||
let delegation = validate_delegation_stake(
|
||||
amount,
|
||||
info.funds,
|
||||
contract_state.params.minimum_mixnode_delegation,
|
||||
contract_state.rewarding_denom,
|
||||
)?;
|
||||
@@ -67,14 +41,9 @@ pub(crate) fn _try_delegate_to_mixnode(
|
||||
}
|
||||
|
||||
// push the event onto the queue and wait for it to be picked up at the end of the epoch
|
||||
let cosmos_event = new_pending_delegation_event(&delegate, &proxy, &delegation, mix_id);
|
||||
let cosmos_event = new_pending_delegation_event(&info.sender, &delegation, mix_id);
|
||||
|
||||
let epoch_event = PendingEpochEventKind::Delegate {
|
||||
owner: delegate,
|
||||
mix_id,
|
||||
amount: delegation,
|
||||
proxy,
|
||||
};
|
||||
let epoch_event = PendingEpochEventKind::new_delegate(info.sender, mix_id, delegation);
|
||||
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
|
||||
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
@@ -85,35 +54,12 @@ pub(crate) fn try_remove_delegation_from_mixnode(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_remove_delegation_from_mixnode(deps, env, mix_id, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
delegate: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let delegate = deps.api.addr_validate(&delegate)?;
|
||||
_try_remove_delegation_from_mixnode(deps, env, mix_id, delegate, Some(info.sender))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_delegation_from_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
mix_id: MixId,
|
||||
delegate: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// undelegation is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
// see if the delegation even exists
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &delegate, proxy.as_ref());
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
|
||||
|
||||
if storage::delegations()
|
||||
.may_load(deps.storage, storage_key)?
|
||||
@@ -121,19 +67,15 @@ pub(crate) fn _try_remove_delegation_from_mixnode(
|
||||
{
|
||||
return Err(MixnetContractError::NoMixnodeDelegationFound {
|
||||
mix_id,
|
||||
address: delegate.into_string(),
|
||||
proxy: proxy.map(Addr::into_string),
|
||||
address: info.sender.into_string(),
|
||||
proxy: None,
|
||||
});
|
||||
}
|
||||
|
||||
// push the event onto the queue and wait for it to be picked up at the end of the epoch
|
||||
let cosmos_event = new_pending_undelegation_event(&delegate, &proxy, mix_id);
|
||||
let cosmos_event = new_pending_undelegation_event(&info.sender, mix_id);
|
||||
|
||||
let epoch_event = PendingEpochEventKind::Undelegate {
|
||||
owner: delegate,
|
||||
mix_id,
|
||||
proxy,
|
||||
};
|
||||
let epoch_event = PendingEpochEventKind::new_undelegate(info.sender, mix_id);
|
||||
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
|
||||
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
@@ -151,7 +93,7 @@ mod tests {
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{coin, Decimal};
|
||||
use cosmwasm_std::{coin, Addr, Decimal};
|
||||
use mixnet_contract_common::{EpochState, EpochStatus};
|
||||
|
||||
#[test]
|
||||
@@ -368,65 +310,17 @@ mod tests {
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
|
||||
let amount2 = coin(50_000_000, TEST_COIN_DENOM);
|
||||
|
||||
let sender1 = mock_info(owner, &[amount1.clone()]);
|
||||
let sender2 = mock_info(test.vesting_contract().as_str(), &[amount2.clone()]);
|
||||
|
||||
try_delegate_to_mixnode(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
|
||||
try_delegate_to_mixnode_on_behalf(test.deps_mut(), env, sender2, mix_id, owner.into())
|
||||
.unwrap();
|
||||
|
||||
let events = test.pending_epoch_events();
|
||||
|
||||
assert_eq!(
|
||||
events[0].kind,
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked(owner),
|
||||
mix_id,
|
||||
amount: amount1,
|
||||
proxy: None
|
||||
}
|
||||
PendingEpochEventKind::new_delegate(Addr::unchecked(owner), mix_id, amount1,)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
events[1].kind,
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked(owner),
|
||||
mix_id,
|
||||
amount: amount2,
|
||||
proxy: Some(test.vesting_contract())
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "delegator";
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let res = try_delegate_to_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
mix_id,
|
||||
owner.into(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,40 +467,5 @@ mod tests {
|
||||
);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "delegator";
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
test.add_immediate_delegation_with_illegal_proxy(
|
||||
owner,
|
||||
10000u32,
|
||||
mix_id,
|
||||
illegal_proxy.clone(),
|
||||
);
|
||||
|
||||
let res = try_remove_delegation_from_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
mix_id,
|
||||
owner.into(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::decode_ed25519_identity_key;
|
||||
use cosmwasm_std::{Addr, Deps};
|
||||
use cosmwasm_std::Deps;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::{construct_family_join_permit, IdentityKeyRef};
|
||||
@@ -13,7 +13,6 @@ use nym_contracts_common::signing::{MessageSignature, Verifier};
|
||||
pub(crate) fn verify_family_join_permit(
|
||||
deps: Deps<'_>,
|
||||
granter: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
member: IdentityKeyRef,
|
||||
signature: MessageSignature,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
@@ -32,7 +31,7 @@ pub(crate) fn verify_family_join_permit(
|
||||
});
|
||||
};
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, head_mixnode.owner)?;
|
||||
let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned());
|
||||
let msg = construct_family_join_permit(nonce, granter, member.to_owned());
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage::{
|
||||
@@ -7,41 +7,20 @@ use super::storage::{
|
||||
};
|
||||
use crate::families::queries::get_family_by_label;
|
||||
use crate::families::signature_helpers::verify_family_join_permit;
|
||||
use crate::support::helpers::{ensure_bonded, ensure_sent_by_vesting_contract};
|
||||
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
|
||||
use crate::support::helpers::ensure_bonded;
|
||||
use cosmwasm_std::{DepsMut, MessageInfo, Response};
|
||||
use mixnet_contract_common::families::{Family, FamilyHead};
|
||||
use mixnet_contract_common::{error::MixnetContractError, IdentityKey};
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
|
||||
/// Creates a new MixNode family with senders node as head
|
||||
pub fn try_create_family(
|
||||
pub(crate) fn try_create_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
label: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_create_family(deps, &info.sender, label, None)
|
||||
}
|
||||
|
||||
pub fn try_create_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
owner_address: String,
|
||||
label: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner_address = deps.api.addr_validate(&owner_address)?;
|
||||
_try_create_family(deps, &owner_address, label, Some(info.sender))
|
||||
}
|
||||
|
||||
fn _try_create_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
label: String,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -60,43 +39,19 @@ fn _try_create_family(
|
||||
return Err(MixnetContractError::FamilyWithLabelExists(label));
|
||||
}
|
||||
|
||||
let family = Family::new(family_head, proxy, label);
|
||||
let family = Family::new(family_head, label);
|
||||
save_family(&family, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_join_family(
|
||||
pub(crate) fn try_join_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_join_family(deps, &info.sender, join_permit, family_head, None)
|
||||
}
|
||||
|
||||
pub fn try_join_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member_address: String,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let member_address = deps.api.addr_validate(&member_address)?;
|
||||
let proxy = Some(info.sender);
|
||||
_try_join_family(deps, &member_address, join_permit, family_head, proxy)
|
||||
}
|
||||
|
||||
fn _try_join_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -116,7 +71,6 @@ fn _try_join_family(
|
||||
verify_family_join_permit(
|
||||
deps.as_ref(),
|
||||
family_head.clone(),
|
||||
proxy,
|
||||
existing_bond.identity(),
|
||||
join_permit,
|
||||
)?;
|
||||
@@ -128,33 +82,13 @@ fn _try_join_family(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_leave_family(
|
||||
pub(crate) fn try_leave_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_leave_family(deps, &info.sender, family_head)
|
||||
}
|
||||
|
||||
pub fn try_leave_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member_address: String,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let member_address = deps.api.addr_validate(&member_address)?;
|
||||
_try_leave_family(deps, &member_address, family_head)
|
||||
}
|
||||
|
||||
fn _try_leave_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -178,32 +112,13 @@ fn _try_leave_family(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_head_kick_member(
|
||||
pub(crate) fn try_head_kick_member(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_head_kick_member(deps, &info.sender, member)
|
||||
}
|
||||
|
||||
pub fn try_head_kick_member_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
head_address: String,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let head_address = deps.api.addr_validate(&head_address)?;
|
||||
_try_head_kick_member(deps, &head_address, member)
|
||||
}
|
||||
|
||||
fn _try_head_kick_member(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let head_bond = crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
let head_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
// make sure we're still in the mixnet
|
||||
ensure_bonded(&head_bond)?;
|
||||
@@ -321,7 +236,7 @@ mod test {
|
||||
assert_eq!(family.head_identity(), family_head.identity());
|
||||
|
||||
let join_permit =
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
@@ -345,7 +260,7 @@ mod test {
|
||||
);
|
||||
|
||||
let new_join_permit =
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
@@ -373,189 +288,4 @@ mod test {
|
||||
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod creating_family {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let head = "alice";
|
||||
|
||||
test.add_dummy_mixnode(head, None);
|
||||
|
||||
let res = try_create_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
head.to_string(),
|
||||
"label".to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod joining_family {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let head = "alice";
|
||||
let label = "family";
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
let res = try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
join_permit,
|
||||
family_head,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod leaving_family {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let head = "alice";
|
||||
let label = "family";
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
true,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
join_permit,
|
||||
family_head.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = try_leave_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
family_head,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod kicking_family_member {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let head = "alice";
|
||||
let label = "family";
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
true,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
|
||||
try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
join_permit,
|
||||
family_head,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = try_head_kick_member_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
head.to_string(),
|
||||
member_keys.public_key().to_base58_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use nym_contracts_common::signing::Verifier;
|
||||
pub(crate) fn verify_gateway_bonding_signature(
|
||||
deps: Deps<'_>,
|
||||
sender: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
gateway: Gateway,
|
||||
signature: MessageSignature,
|
||||
@@ -22,7 +21,7 @@ pub(crate) fn verify_gateway_bonding_signature(
|
||||
|
||||
// reconstruct the payload
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
|
||||
let msg = construct_gateway_bonding_sign_payload(nonce, sender, proxy, pledge, gateway);
|
||||
let msg = construct_gateway_bonding_sign_payload(nonce, sender, pledge, gateway);
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::helpers::must_get_gateway_bond_by_owner;
|
||||
@@ -6,10 +6,8 @@ use super::storage;
|
||||
use crate::gateways::signature_helpers::verify_gateway_bonding_signature;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::{
|
||||
ensure_no_existing_bond, ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
|
||||
};
|
||||
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
|
||||
use crate::support::helpers::{ensure_no_existing_bond, validate_pledge};
|
||||
use cosmwasm_std::{BankMsg, DepsMut, Env, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_gateway_bonding_event, new_gateway_config_update_event, new_gateway_unbonding_event,
|
||||
@@ -17,72 +15,28 @@ use mixnet_contract_common::events::{
|
||||
use mixnet_contract_common::gateway::GatewayConfigUpdate;
|
||||
use mixnet_contract_common::{Gateway, GatewayBond};
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
pub fn try_add_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_add_gateway(
|
||||
deps,
|
||||
env,
|
||||
gateway,
|
||||
info.funds,
|
||||
info.sender,
|
||||
owner_signature,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_add_gateway_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
owner: String,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_add_gateway(
|
||||
deps,
|
||||
env,
|
||||
gateway,
|
||||
info.funds,
|
||||
owner,
|
||||
owner_signature,
|
||||
Some(proxy),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
|
||||
// so that we could return a better error message if it doesn't match?
|
||||
pub(crate) fn _try_add_gateway(
|
||||
pub(crate) fn try_add_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
pledge: Vec<Coin>,
|
||||
owner: Addr,
|
||||
owner_signature: MessageSignature,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// check if the pledge contains any funds of the appropriate denomination
|
||||
let minimum_pledge = mixnet_params_storage::minimum_gateway_pledge(deps.storage)?;
|
||||
let pledge = validate_pledge(pledge, minimum_pledge)?;
|
||||
let pledge = validate_pledge(info.funds, minimum_pledge)?;
|
||||
|
||||
// if the client has an active bonded mixnode or gateway, don't allow bonding
|
||||
ensure_no_existing_bond(&owner, deps.storage)?;
|
||||
ensure_no_existing_bond(&info.sender, deps.storage)?;
|
||||
|
||||
// check if somebody else has already bonded a gateway with this identity
|
||||
if let Some(existing_bond) =
|
||||
storage::gateways().may_load(deps.storage, &gateway.identity_key)?
|
||||
{
|
||||
if existing_bond.owner != owner {
|
||||
if existing_bond.owner != info.sender {
|
||||
return Err(MixnetContractError::DuplicateGateway {
|
||||
owner: existing_bond.owner,
|
||||
});
|
||||
@@ -92,105 +46,62 @@ pub(crate) fn _try_add_gateway(
|
||||
// check if this sender actually owns the gateway by checking the signature
|
||||
verify_gateway_bonding_signature(
|
||||
deps.as_ref(),
|
||||
owner.clone(),
|
||||
proxy.clone(),
|
||||
info.sender.clone(),
|
||||
pledge.clone(),
|
||||
gateway.clone(),
|
||||
owner_signature,
|
||||
)?;
|
||||
|
||||
// update the signing nonce associated with this sender so that the future signature would be made on the new value
|
||||
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
|
||||
signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?;
|
||||
|
||||
let gateway_identity = gateway.identity_key.clone();
|
||||
let bond = GatewayBond::new(
|
||||
pledge.clone(),
|
||||
owner.clone(),
|
||||
info.sender.clone(),
|
||||
env.block.height,
|
||||
gateway,
|
||||
proxy.clone(),
|
||||
);
|
||||
|
||||
storage::gateways().save(deps.storage, bond.identity(), &bond)?;
|
||||
|
||||
Ok(Response::new().add_event(new_gateway_bonding_event(
|
||||
&owner,
|
||||
&proxy,
|
||||
&info.sender,
|
||||
&pledge,
|
||||
&gateway_identity,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn try_remove_gateway_on_behalf(
|
||||
pub(crate) fn try_remove_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
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_remove_gateway(deps, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn try_remove_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_remove_gateway(deps, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// try to find the node of the sender
|
||||
let gateway_bond = match storage::gateways()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, owner.clone())?
|
||||
.item(deps.storage, info.sender.clone())?
|
||||
{
|
||||
Some(record) => record.1,
|
||||
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner }),
|
||||
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner: info.sender }),
|
||||
};
|
||||
|
||||
if proxy != gateway_bond.proxy {
|
||||
return Err(MixnetContractError::ProxyMismatch {
|
||||
existing: gateway_bond
|
||||
.proxy
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
// send bonded funds back to the bond owner
|
||||
let return_tokens = BankMsg::Send {
|
||||
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
|
||||
to_address: info.sender.to_string(),
|
||||
amount: vec![gateway_bond.pledge_amount()],
|
||||
};
|
||||
|
||||
// remove the bond
|
||||
storage::gateways().remove(deps.storage, gateway_bond.identity())?;
|
||||
|
||||
let mut response = Response::new().add_message(return_tokens);
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
let msg = VestingContractExecuteMsg::TrackUnbondGateway {
|
||||
owner: owner.as_str().to_string(),
|
||||
amount: gateway_bond.pledge_amount(),
|
||||
};
|
||||
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
response = response.add_message(track_unbond_message);
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_gateway_unbonding_event(
|
||||
&owner,
|
||||
&proxy,
|
||||
&gateway_bond.pledge_amount,
|
||||
gateway_bond.identity(),
|
||||
)))
|
||||
Ok(Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_gateway_unbonding_event(
|
||||
&info.sender,
|
||||
&gateway_bond.pledge_amount,
|
||||
gateway_bond.identity(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn try_update_gateway_config(
|
||||
@@ -198,36 +109,9 @@ pub(crate) fn try_update_gateway_config(
|
||||
info: MessageInfo,
|
||||
new_config: GatewayConfigUpdate,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let owner = info.sender;
|
||||
_try_update_gateway_config(deps, new_config, owner, None)
|
||||
}
|
||||
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &info.sender)?;
|
||||
let cfg_update_event = new_gateway_config_update_event(&info.sender, &new_config);
|
||||
|
||||
pub(crate) fn try_update_gateway_config_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
new_config: GatewayConfigUpdate,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
let proxy = info.sender;
|
||||
_try_update_gateway_config(deps, new_config, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_update_gateway_config(
|
||||
deps: DepsMut,
|
||||
new_config: GatewayConfigUpdate,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &owner)?;
|
||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||
|
||||
let cfg_update_event = new_gateway_config_update_event(&owner, &proxy, &new_config);
|
||||
|
||||
// clippy beta 1.70.0-beta.1 false positive
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let mut updated_bond = existing_bond.clone();
|
||||
updated_bond.gateway.host = new_config.host;
|
||||
updated_bond.gateway.mix_port = new_config.mix_port;
|
||||
@@ -254,10 +138,10 @@ pub mod tests {
|
||||
use crate::mixnet_contract_settings::storage::minimum_gateway_pledge;
|
||||
use crate::support::tests;
|
||||
use crate::support::tests::fixtures;
|
||||
use crate::support::tests::fixtures::{good_gateway_pledge, good_mixnode_pledge};
|
||||
use crate::support::tests::fixtures::good_mixnode_pledge;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::Uint128;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use mixnet_contract_common::ExecuteMsg;
|
||||
|
||||
#[test]
|
||||
@@ -392,42 +276,12 @@ pub mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(1, updated_nonce);
|
||||
|
||||
_try_remove_gateway(test.deps_mut(), Addr::unchecked(sender), None).unwrap();
|
||||
try_remove_gateway(test.deps_mut(), info.clone()).unwrap();
|
||||
|
||||
let res = try_add_gateway(test.deps_mut(), env, info, gateway, signature);
|
||||
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_add_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
let (gateway, sig) = test.gateway_with_signature(owner, None);
|
||||
|
||||
let res = try_add_gateway_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()),
|
||||
gateway,
|
||||
owner.to_string(),
|
||||
sig,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_remove() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -495,7 +349,6 @@ pub mod tests {
|
||||
.add_message(expected_message)
|
||||
.add_event(new_gateway_unbonding_event(
|
||||
&Addr::unchecked("fred"),
|
||||
&None,
|
||||
&tests::fixtures::good_gateway_pledge()[0],
|
||||
&fred_identity,
|
||||
));
|
||||
@@ -510,33 +363,6 @@ pub mod tests {
|
||||
assert_eq!(&Addr::unchecked("bob"), nodes[0].owner());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_remove_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
|
||||
let res = try_remove_gateway_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()),
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_gateway_config() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -561,22 +387,6 @@ pub mod tests {
|
||||
);
|
||||
|
||||
test.add_dummy_gateway(owner, None);
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// attempted to remove on behalf with invalid proxy (current is `None`)
|
||||
let res = try_update_gateway_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
update.clone(),
|
||||
owner.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: vesting_contract.into_string()
|
||||
})
|
||||
);
|
||||
|
||||
// "normal" update succeeds
|
||||
let res = try_update_gateway_config(test.deps_mut(), info, update.clone());
|
||||
@@ -591,39 +401,4 @@ pub mod tests {
|
||||
assert_eq!(bond.gateway.location, update.location);
|
||||
assert_eq!(bond.gateway.version, update.version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_gateway_config_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
let update = GatewayConfigUpdate {
|
||||
host: "1.1.1.1:1234".to_string(),
|
||||
mix_port: 1234,
|
||||
clients_port: 1235,
|
||||
location: "at home".to_string(),
|
||||
version: "v1.2.3".to_string(),
|
||||
};
|
||||
|
||||
let res = try_update_gateway_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
update,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::interval::storage;
|
||||
use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_details_by_id};
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::rewards::storage as rewards_storage;
|
||||
use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking};
|
||||
use crate::support::helpers::AttachSendTokens;
|
||||
|
||||
pub(crate) trait ContractExecutableEvent {
|
||||
// note: the error only means a HARD error like we failed to read from storage.
|
||||
@@ -40,7 +40,6 @@ pub(crate) fn delegate(
|
||||
owner: Addr,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// check if the target node still exists (it might have unbonded between this event getting created
|
||||
// and being executed). Do note that it's absolutely possible for a mixnode to get immediately
|
||||
@@ -56,20 +55,9 @@ pub(crate) fn delegate(
|
||||
_ => {
|
||||
// if mixnode is no longer bonded or in the process of unbonding, return the tokens back to the
|
||||
// delegator;
|
||||
// (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`)
|
||||
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![amount.clone()]);
|
||||
let response = Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_delegation_on_unbonded_node_event(
|
||||
&owner, &proxy, mix_id,
|
||||
))
|
||||
.maybe_add_track_vesting_undelegation_message(
|
||||
deps.storage,
|
||||
proxy,
|
||||
owner.to_string(),
|
||||
mix_id,
|
||||
amount,
|
||||
)?;
|
||||
.send_tokens(&owner, amount.clone())
|
||||
.add_event(new_delegation_on_unbonded_node_event(&owner, mix_id));
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
@@ -84,7 +72,7 @@ pub(crate) fn delegate(
|
||||
|
||||
// if there's an existing delegation, then withdraw the full reward and create a new delegation
|
||||
// with the sum of both
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, None);
|
||||
let old_delegation = if let Some(existing_delegation) =
|
||||
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
|
||||
{
|
||||
@@ -106,7 +94,6 @@ pub(crate) fn delegate(
|
||||
let cosmos_event = new_delegation_event(
|
||||
created_at,
|
||||
&owner,
|
||||
&proxy,
|
||||
&new_delegation_amount,
|
||||
mix_id,
|
||||
mix_rewarding.total_unit_reward,
|
||||
@@ -118,7 +105,6 @@ pub(crate) fn delegate(
|
||||
mix_rewarding.total_unit_reward,
|
||||
stored_delegation_amount,
|
||||
env.block.height,
|
||||
proxy,
|
||||
);
|
||||
|
||||
// save on reading since `.save()` would have attempted to read old data that we already have on hand
|
||||
@@ -138,11 +124,10 @@ pub(crate) fn undelegate(
|
||||
created_at: BlockHeight,
|
||||
owner: Addr,
|
||||
mix_id: MixId,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// see if the delegation still exists (in case of impatient user who decided to send multiple
|
||||
// undelegation requests in an epoch)
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, None);
|
||||
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
|
||||
None => return Ok(Response::default()),
|
||||
Some(delegation) => delegation,
|
||||
@@ -155,18 +140,9 @@ pub(crate) fn undelegate(
|
||||
let tokens_to_return =
|
||||
delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?;
|
||||
|
||||
// (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`)
|
||||
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![tokens_to_return.clone()]);
|
||||
let response = Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_undelegation_event(created_at, &owner, &proxy, mix_id))
|
||||
.maybe_add_track_vesting_undelegation_message(
|
||||
deps.storage,
|
||||
proxy,
|
||||
owner.to_string(),
|
||||
mix_id,
|
||||
tokens_to_return,
|
||||
)?;
|
||||
.send_tokens(&owner, tokens_to_return.clone())
|
||||
.add_event(new_undelegation_event(created_at, &owner, mix_id));
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -197,25 +173,15 @@ pub(crate) fn unbond_mixnode(
|
||||
.rewarding_details
|
||||
.operator_pledge_with_reward(rewarding_denom);
|
||||
|
||||
let proxy = &node_details.bond_information.proxy;
|
||||
let owner = &node_details.bond_information.owner;
|
||||
|
||||
// send bonded funds (alongside all earned rewards) to the bond owner
|
||||
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![tokens.clone()]);
|
||||
|
||||
// remove the bond and if there are no delegations left, also the rewarding information
|
||||
// decrement the associated layer count
|
||||
cleanup_post_unbond_mixnode_storage(deps.storage, env, &node_details)?;
|
||||
|
||||
let response = Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_mixnode_unbonding_event(created_at, mix_id))
|
||||
.maybe_add_track_vesting_unbond_mixnode_message(
|
||||
deps.storage,
|
||||
proxy.clone(),
|
||||
owner.clone().into_string(),
|
||||
tokens,
|
||||
)?;
|
||||
.send_tokens(owner, tokens.clone())
|
||||
.add_event(new_mixnode_unbonding_event(created_at, mix_id));
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -311,12 +277,8 @@ pub(crate) fn decrease_pledge(
|
||||
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,
|
||||
@@ -328,14 +290,8 @@ pub(crate) fn decrease_pledge(
|
||||
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,
|
||||
)?;
|
||||
.send_tokens(owner, decrease_by.clone())
|
||||
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by));
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -349,13 +305,11 @@ impl ContractExecutableEvent for PendingEpochEventData {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
proxy,
|
||||
} => delegate(deps, env, self.created_at, owner, mix_id, amount, proxy),
|
||||
PendingEpochEventKind::Undelegate {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy,
|
||||
} => undelegate(deps, self.created_at, owner, mix_id, proxy),
|
||||
..
|
||||
} => delegate(deps, env, self.created_at, owner, mix_id, amount),
|
||||
PendingEpochEventKind::Undelegate { owner, mix_id, .. } => {
|
||||
undelegate(deps, self.created_at, owner, mix_id)
|
||||
}
|
||||
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
|
||||
increase_pledge(deps, self.created_at, mix_id, amount)
|
||||
}
|
||||
@@ -472,33 +426,25 @@ impl ContractExecutableEvent for PendingIntervalEventData {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
|
||||
use mixnet_contract_common::Percent;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
use super::*;
|
||||
use crate::support::tests::test_helpers;
|
||||
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
|
||||
|
||||
use super::*;
|
||||
use cosmwasm_std::Decimal;
|
||||
use mixnet_contract_common::Percent;
|
||||
use std::time::Duration;
|
||||
|
||||
// note that authorization and basic validation has already been performed for all of those
|
||||
// before being pushed onto the event queues
|
||||
|
||||
#[cfg(test)]
|
||||
mod delegating {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use super::*;
|
||||
use crate::mixnodes::transactions::try_remove_mixnode;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
|
||||
use super::*;
|
||||
use cosmwasm_std::coin;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
#[test]
|
||||
fn returns_the_tokens_if_mixnode_has_unbonded() {
|
||||
@@ -523,7 +469,6 @@ mod tests {
|
||||
Addr::unchecked(owner1),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -549,7 +494,6 @@ mod tests {
|
||||
Addr::unchecked(owner2),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key =
|
||||
@@ -588,7 +532,6 @@ mod tests {
|
||||
Addr::unchecked(owner1),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -614,7 +557,6 @@ mod tests {
|
||||
Addr::unchecked(owner2),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key =
|
||||
@@ -650,7 +592,6 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin_new,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -725,7 +666,6 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin_new,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -797,7 +737,6 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(get_bank_send_msg(&res).is_none());
|
||||
@@ -816,117 +755,13 @@ mod tests {
|
||||
Decimal::from_atomics(delegation, 0).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let delegation = 120_000_000u128;
|
||||
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
|
||||
let owner = "delegator";
|
||||
|
||||
let env = test.env();
|
||||
unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// for a fresh delegation, nothing was added to the storage either
|
||||
let res_vesting = delegate(
|
||||
test.deps_mut(),
|
||||
&env,
|
||||
123,
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
Some(vesting_contract.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key = Delegation::generate_storage_key(
|
||||
mix_id,
|
||||
&Addr::unchecked(owner),
|
||||
Some(vesting_contract.clone()).as_ref(),
|
||||
);
|
||||
assert!(delegations_storage::delegations()
|
||||
.may_load(test.deps().storage, storage_key)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
// and all tokens are returned back to the proxy
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap();
|
||||
assert_eq!(receiver, vesting_contract.as_str());
|
||||
assert_eq!(sent_amount[0], delegation_coin);
|
||||
|
||||
// and we get appropriate track message
|
||||
let mut found_track = true;
|
||||
for msg in &res_vesting.messages {
|
||||
if let CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr,
|
||||
msg,
|
||||
funds,
|
||||
}) = &msg.msg
|
||||
{
|
||||
found_track = true;
|
||||
assert_eq!(contract_addr, vesting_contract.as_str());
|
||||
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: owner.to_string(),
|
||||
mix_id,
|
||||
amount: delegation_coin.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(&expected_msg, msg);
|
||||
assert!(funds.is_empty())
|
||||
}
|
||||
}
|
||||
assert!(found_track);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_error_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let delegation = 120_000_000u128;
|
||||
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
|
||||
let owner = "delegator";
|
||||
let dummy_proxy = Addr::unchecked("not-vesting-contract");
|
||||
|
||||
let env = test.env();
|
||||
unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// try to add illegal delegation (with invalid proxy)
|
||||
let res_other_proxy = delegate(
|
||||
test.deps_mut(),
|
||||
&env,
|
||||
123,
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin,
|
||||
Some(dummy_proxy.clone()),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res_other_proxy,
|
||||
MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: dummy_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod undelegating {
|
||||
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::test_helpers::get_bank_send_msg;
|
||||
|
||||
use super::*;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
#[test]
|
||||
fn doesnt_return_any_tokens_if_it_doesnt_exist() {
|
||||
@@ -935,7 +770,7 @@ mod tests {
|
||||
|
||||
let owner = Addr::unchecked("delegator");
|
||||
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None).unwrap();
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id).unwrap();
|
||||
assert!(get_bank_send_msg(&res).is_none());
|
||||
}
|
||||
|
||||
@@ -950,7 +785,7 @@ mod tests {
|
||||
// this should never happen in actual code, but if we manually messed something up,
|
||||
// lets make sure this throws an error
|
||||
rewards_storage::MIXNODE_REWARDING.remove(test.deps_mut().storage, mix_id);
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None);
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id);
|
||||
assert!(matches!(
|
||||
res,
|
||||
Err(MixnetContractError::InconsistentState { .. })
|
||||
@@ -996,8 +831,7 @@ mod tests {
|
||||
|
||||
let expected_return = delegation + truncated_reward.u128();
|
||||
|
||||
let res =
|
||||
undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id, None).unwrap();
|
||||
let res = undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id).unwrap();
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
|
||||
assert_eq!(receiver, owner);
|
||||
assert_eq!(sent_amount[0].amount.u128(), expected_return);
|
||||
@@ -1015,117 +849,19 @@ mod tests {
|
||||
assert!(rewarding.delegates.is_zero());
|
||||
assert_eq!(rewarding.unique_delegations, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let delegation = 120_000_000u128;
|
||||
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
|
||||
let owner = "delegator";
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
test.add_immediate_delegation_with_legal_proxy(owner, delegation, mix_id);
|
||||
|
||||
let res_vesting = undelegate(
|
||||
test.deps_mut(),
|
||||
123,
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
Some(vesting_contract.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key = Delegation::generate_storage_key(
|
||||
mix_id,
|
||||
&Addr::unchecked(owner),
|
||||
Some(vesting_contract.clone()).as_ref(),
|
||||
);
|
||||
assert!(delegations_storage::delegations()
|
||||
.may_load(test.deps().storage, storage_key)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
// and all tokens are returned back to the proxy
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap();
|
||||
assert_eq!(receiver, vesting_contract.as_str());
|
||||
assert_eq!(sent_amount[0], delegation_coin);
|
||||
|
||||
// and we get appropriate track message
|
||||
let mut found_track = true;
|
||||
for msg in &res_vesting.messages {
|
||||
if let CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr,
|
||||
msg,
|
||||
funds,
|
||||
}) = &msg.msg
|
||||
{
|
||||
found_track = true;
|
||||
assert_eq!(contract_addr, vesting_contract.as_str());
|
||||
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: owner.to_string(),
|
||||
mix_id,
|
||||
amount: delegation_coin.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(&expected_msg, msg);
|
||||
assert!(funds.is_empty())
|
||||
}
|
||||
}
|
||||
assert!(found_track);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_error_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let delegation = 120_000_000u128;
|
||||
let owner = "delegator1";
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
let dummy_proxy = Addr::unchecked("not-vesting-contract");
|
||||
|
||||
test.add_immediate_delegation_with_illegal_proxy(
|
||||
owner,
|
||||
delegation,
|
||||
mix_id,
|
||||
dummy_proxy.clone(),
|
||||
);
|
||||
|
||||
let res_other_proxy = undelegate(
|
||||
test.deps_mut(),
|
||||
123,
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
Some(dummy_proxy.clone()),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
res_other_proxy,
|
||||
MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: dummy_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod mixnode_unbonding {
|
||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
|
||||
|
||||
use super::*;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::mixnodes::transactions::{try_decrease_pledge, try_increase_pledge};
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::Uint128;
|
||||
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::transactions::{_try_decrease_pledge, _try_increase_pledge};
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
||||
// this should have never happened so hard error MUST be thrown here
|
||||
@@ -1150,12 +886,10 @@ mod tests {
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
|
||||
|
||||
_try_increase_pledge(
|
||||
try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
change.clone(),
|
||||
Addr::unchecked(owner),
|
||||
None,
|
||||
mock_info(owner, &change.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1170,12 +904,11 @@ mod tests {
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
|
||||
|
||||
_try_decrease_pledge(
|
||||
try_decrease_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(owner, &[]),
|
||||
change[0].clone(),
|
||||
Addr::unchecked(owner),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1263,79 +996,6 @@ mod tests {
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
let pledge_coin = coin(250_000_000, TEST_COIN_DENOM);
|
||||
let owner = "mix-owner1";
|
||||
let mix_id_vesting = test.add_dummy_mixnode_with_legal_proxy(owner, Some(pledge));
|
||||
|
||||
let env = test.env();
|
||||
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id_vesting).unwrap();
|
||||
|
||||
assert!(mixnodes_storage::mixnode_bonds()
|
||||
.may_load(test.deps().storage, mix_id_vesting)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
// and all tokens are returned back to the proxy
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
|
||||
assert_eq!(receiver, vesting_contract.as_str());
|
||||
assert_eq!(sent_amount[0], pledge_coin);
|
||||
|
||||
// and we get appropriate track message
|
||||
let mut found_track = true;
|
||||
for msg in &res.messages {
|
||||
if let CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr,
|
||||
msg,
|
||||
funds,
|
||||
}) = &msg.msg
|
||||
{
|
||||
found_track = true;
|
||||
assert_eq!(contract_addr, vesting_contract.as_str());
|
||||
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUnbondMixnode {
|
||||
owner: owner.to_string(),
|
||||
amount: pledge_coin.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(&expected_msg, msg);
|
||||
assert!(funds.is_empty())
|
||||
}
|
||||
}
|
||||
assert!(found_track);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_error_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let dummy_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let env = test.env();
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
let owner = "mix-owner";
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
|
||||
let mix_id_illegal_proxy =
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(pledge), dummy_proxy.clone());
|
||||
|
||||
// this is the halting issue that should have never occurred
|
||||
let res_other_proxy =
|
||||
unbond_mixnode(test.deps_mut(), &env, 123, mix_id_illegal_proxy).unwrap_err();
|
||||
assert_eq!(
|
||||
res_other_proxy,
|
||||
MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: dummy_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1615,11 +1275,9 @@ mod tests {
|
||||
|
||||
#[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::*;
|
||||
use cosmwasm_std::{BankMsg, CosmosMsg, Uint128};
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
#[test]
|
||||
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
||||
@@ -1699,64 +1357,6 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
@@ -159,11 +159,8 @@ mod tests {
|
||||
}
|
||||
|
||||
fn push_dummy_epoch_action(test: &mut TestSetup) {
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let dummy_action =
|
||||
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
|
||||
let env = test.env();
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
}
|
||||
@@ -571,11 +568,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// it exists
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let dummy_action =
|
||||
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
|
||||
let env = test.env();
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action.clone()).unwrap();
|
||||
let expected = PendingEpochEventResponse {
|
||||
|
||||
@@ -222,11 +222,10 @@ mod tests {
|
||||
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 dummy_action = PendingEpochEventKind::new_undelegate(
|
||||
Addr::unchecked("foomp"),
|
||||
test.rng.next_u32(),
|
||||
);
|
||||
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);
|
||||
@@ -235,11 +234,10 @@ mod tests {
|
||||
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 dummy_action = PendingEpochEventKind::new_undelegate(
|
||||
Addr::unchecked("foomp"),
|
||||
test.rng.next_u32(),
|
||||
);
|
||||
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);
|
||||
|
||||
@@ -373,18 +373,14 @@ mod tests {
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::Addr;
|
||||
use mixnet_contract_common::pending_events::PendingEpochEventKind;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) {
|
||||
// if you attempt to undelegate non-existent delegation,
|
||||
// it will return an empty response, but will not fail
|
||||
let env = test.env();
|
||||
for i in 0..n {
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: i as MixId,
|
||||
proxy: None,
|
||||
};
|
||||
let dummy_action =
|
||||
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), i as MixId);
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -406,7 +402,7 @@ mod tests {
|
||||
mod performing_pending_epoch_actions {
|
||||
use super::*;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use cosmwasm_std::{coin, coins, wasm_execute, BankMsg, Empty, SubMsg};
|
||||
use cosmwasm_std::{coin, coins, BankMsg, Empty, SubMsg};
|
||||
use mixnet_contract_common::events::{
|
||||
new_active_set_update_event, new_delegation_on_unbonded_node_event,
|
||||
new_undelegation_event,
|
||||
@@ -495,7 +491,6 @@ mod tests {
|
||||
#[test]
|
||||
fn catches_all_events_and_messages_from_executed_actions() {
|
||||
let mut test = TestSetup::new();
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let env = test.env();
|
||||
let legit_mix = test.add_dummy_mixnode("mix-owner", None);
|
||||
@@ -509,17 +504,15 @@ mod tests {
|
||||
// delegate to node that doesn't exist,
|
||||
// we expect to receive BankMsg with tokens being returned,
|
||||
// and event regarding delegation
|
||||
let non_existent_delegation = PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
proxy: None,
|
||||
};
|
||||
let non_existent_delegation = PendingEpochEventKind::new_delegate(
|
||||
Addr::unchecked("foomp"),
|
||||
123,
|
||||
coin(123, TEST_COIN_DENOM),
|
||||
);
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
|
||||
.unwrap();
|
||||
expected_events.push(new_delegation_on_unbonded_node_event(
|
||||
&Addr::unchecked("foomp"),
|
||||
&None,
|
||||
123,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
@@ -527,33 +520,6 @@ mod tests {
|
||||
amount: coins(123, TEST_COIN_DENOM),
|
||||
}));
|
||||
|
||||
// delegation to node that doesn't exist with vesting contract
|
||||
// we expect the same as above PLUS TrackUndelegation message
|
||||
let non_existent_delegation = PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked("foomp2"),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
proxy: Some(vesting_contract.clone()),
|
||||
};
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
|
||||
.unwrap();
|
||||
expected_events.push(new_delegation_on_unbonded_node_event(
|
||||
&Addr::unchecked("foomp2"),
|
||||
&Some(vesting_contract.clone()),
|
||||
123,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
to_address: vesting_contract.clone().into_string(),
|
||||
amount: coins(123, TEST_COIN_DENOM),
|
||||
}));
|
||||
let msg = VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: "foomp2".to_string(),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
};
|
||||
let track_undelegate_message = wasm_execute(vesting_contract, &msg, vec![]).unwrap();
|
||||
expected_messages.push(SubMsg::new(track_undelegate_message));
|
||||
|
||||
// updating active set should only emit events and no cosmos messages
|
||||
let action_with_event = PendingEpochEventKind::UpdateActiveSetSize { new_size: 50 };
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, action_with_event)
|
||||
@@ -561,16 +527,12 @@ mod tests {
|
||||
expected_events.push(new_active_set_update_event(env.block.height, 50));
|
||||
|
||||
// undelegation just returns tokens and emits event
|
||||
let legit_undelegate = PendingEpochEventKind::Undelegate {
|
||||
owner: delegator.clone(),
|
||||
mix_id: legit_mix,
|
||||
proxy: None,
|
||||
};
|
||||
let legit_undelegate =
|
||||
PendingEpochEventKind::new_undelegate(delegator.clone(), legit_mix);
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, legit_undelegate).unwrap();
|
||||
expected_events.push(new_undelegation_event(
|
||||
env.block.height,
|
||||
&delegator,
|
||||
&None,
|
||||
legit_mix,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
@@ -583,9 +545,9 @@ mod tests {
|
||||
let mut expected = Response::new().add_events(expected_events);
|
||||
expected.messages = expected_messages;
|
||||
assert_eq!(res, expected);
|
||||
assert_eq!(executed, 4);
|
||||
assert_eq!(executed, 3);
|
||||
assert_eq!(
|
||||
4,
|
||||
3,
|
||||
storage::LAST_PROCESSED_EPOCH_EVENT
|
||||
.load(test.deps().storage)
|
||||
.unwrap()
|
||||
@@ -1330,17 +1292,15 @@ mod tests {
|
||||
let mut expected_messages: Vec<SubMsg<Empty>> = Vec::new();
|
||||
|
||||
// epoch event
|
||||
let non_existent_delegation = PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
proxy: None,
|
||||
};
|
||||
let non_existent_delegation = PendingEpochEventKind::new_delegate(
|
||||
Addr::unchecked("foomp"),
|
||||
123,
|
||||
coin(123, TEST_COIN_DENOM),
|
||||
);
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
|
||||
.unwrap();
|
||||
expected_events.push(new_delegation_on_unbonded_node_event(
|
||||
&Addr::unchecked("foomp"),
|
||||
&None,
|
||||
123,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
|
||||
@@ -19,3 +19,4 @@ mod support;
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
mod testing;
|
||||
mod vesting_migration;
|
||||
|
||||
@@ -45,6 +45,8 @@ pub(crate) mod tests {
|
||||
minimum_mixnode_delegation: None,
|
||||
minimum_mixnode_pledge: coin(123u128, "unym"),
|
||||
minimum_gateway_pledge: coin(456u128, "unym"),
|
||||
profit_margin: Default::default(),
|
||||
interval_operating_cost: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use cosmwasm_std::{Addr, Storage};
|
||||
use cosmwasm_std::{Coin, StdResult};
|
||||
use cw_storage_plus::Item;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::ContractState;
|
||||
use mixnet_contract_common::{ContractState, OperatingCostRange, ProfitMarginRange};
|
||||
|
||||
pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new(CONTRACT_STATE_KEY);
|
||||
|
||||
@@ -28,6 +28,22 @@ pub(crate) fn minimum_gateway_pledge(storage: &dyn Storage) -> Result<Coin, Mixn
|
||||
.map(|state| state.params.minimum_gateway_pledge)?)
|
||||
}
|
||||
|
||||
pub(crate) fn profit_margin_range(
|
||||
storage: &dyn Storage,
|
||||
) -> Result<ProfitMarginRange, MixnetContractError> {
|
||||
Ok(CONTRACT_STATE
|
||||
.load(storage)
|
||||
.map(|state| state.params.profit_margin)?)
|
||||
}
|
||||
|
||||
pub(crate) fn interval_oprating_cost_range(
|
||||
storage: &dyn Storage,
|
||||
) -> Result<OperatingCostRange, MixnetContractError> {
|
||||
Ok(CONTRACT_STATE
|
||||
.load(storage)
|
||||
.map(|state| state.params.interval_operating_cost)?)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn minimum_delegation_stake(
|
||||
storage: &dyn Storage,
|
||||
|
||||
@@ -121,6 +121,8 @@ pub mod tests {
|
||||
denom,
|
||||
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT + Uint128::new(1234),
|
||||
},
|
||||
profit_margin: Default::default(),
|
||||
interval_operating_cost: Default::default(),
|
||||
};
|
||||
|
||||
let initial_params = storage::CONTRACT_STATE
|
||||
|
||||
@@ -97,7 +97,6 @@ pub(crate) fn save_new_mixnode(
|
||||
mixnode: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
) -> Result<(MixId, Layer), MixnetContractError> {
|
||||
let layer = assign_layer(storage)?;
|
||||
@@ -105,15 +104,7 @@ pub(crate) fn save_new_mixnode(
|
||||
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
|
||||
|
||||
let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?;
|
||||
let mixnode_bond = MixNodeBond::new(
|
||||
mix_id,
|
||||
owner,
|
||||
pledge,
|
||||
layer,
|
||||
mixnode,
|
||||
proxy,
|
||||
env.block.height,
|
||||
);
|
||||
let mixnode_bond = MixNodeBond::new(mix_id, owner, pledge, layer, mixnode, env.block.height);
|
||||
|
||||
// save mixnode bond data
|
||||
// note that this implicitly checks for uniqueness on identity key, sphinx key and owner
|
||||
@@ -411,7 +402,6 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
owner.clone(),
|
||||
None,
|
||||
pledge.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -444,7 +434,6 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
Addr::unchecked("different-owner"),
|
||||
None,
|
||||
pledge.clone(),
|
||||
);
|
||||
assert!(res.is_err());
|
||||
@@ -457,7 +446,6 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
owner,
|
||||
None,
|
||||
pledge.clone(),
|
||||
);
|
||||
assert!(res.is_err());
|
||||
@@ -471,7 +459,6 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params,
|
||||
Addr::unchecked("different-owner"),
|
||||
None,
|
||||
pledge,
|
||||
);
|
||||
assert!(res.is_err());
|
||||
|
||||
@@ -12,7 +12,6 @@ use nym_contracts_common::signing::Verifier;
|
||||
pub(crate) fn verify_mixnode_bonding_signature(
|
||||
deps: Deps<'_>,
|
||||
sender: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
mixnode: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
@@ -23,8 +22,7 @@ pub(crate) fn verify_mixnode_bonding_signature(
|
||||
|
||||
// reconstruct the payload
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
|
||||
let msg =
|
||||
construct_mixnode_bonding_sign_payload(nonce, sender, proxy, pledge, mixnode, cost_params);
|
||||
let msg = construct_mixnode_bonding_sign_payload(nonce, sender, pledge, mixnode, cost_params);
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
|
||||
|
||||
use cosmwasm_std::{coin, 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,
|
||||
@@ -25,8 +24,8 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::{
|
||||
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
|
||||
ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
|
||||
validate_pledge,
|
||||
ensure_no_pending_pledge_changes, ensure_operating_cost_within_range,
|
||||
ensure_profit_margin_within_range, validate_pledge,
|
||||
};
|
||||
|
||||
use super::storage;
|
||||
@@ -61,74 +60,30 @@ pub fn assign_mixnode_layer(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_add_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_node: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_add_mixnode(
|
||||
deps,
|
||||
env,
|
||||
mix_node,
|
||||
cost_params,
|
||||
info.funds,
|
||||
info.sender,
|
||||
owner_signature,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_add_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_node: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
owner: String,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_add_mixnode(
|
||||
deps,
|
||||
env,
|
||||
mix_node,
|
||||
cost_params,
|
||||
info.funds,
|
||||
owner,
|
||||
owner_signature,
|
||||
Some(proxy),
|
||||
)
|
||||
}
|
||||
|
||||
// I'm not entirely sure how to deal with this warning at the current moment
|
||||
//
|
||||
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
|
||||
// so that we could return a better error message if it doesn't match?
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn _try_add_mixnode(
|
||||
pub(crate) fn try_add_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mixnode: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
pledge: Vec<Coin>,
|
||||
owner: Addr,
|
||||
owner_signature: MessageSignature,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// ensure the profit margin is within the defined range
|
||||
ensure_profit_margin_within_range(deps.storage, cost_params.profit_margin_percent)?;
|
||||
|
||||
// ensure the operating cost is within the defined range
|
||||
ensure_operating_cost_within_range(deps.storage, &cost_params.interval_operating_cost)?;
|
||||
|
||||
// check if the pledge contains any funds of the appropriate denomination
|
||||
let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
|
||||
let pledge = validate_pledge(pledge, minimum_pledge)?;
|
||||
let pledge = validate_pledge(info.funds, minimum_pledge)?;
|
||||
|
||||
// if the client has an active bonded mixnode or gateway, don't allow bonding
|
||||
// note that this has to be done explicitly as `UniqueIndex` constraint would not protect us
|
||||
// against attempting to use different node types (i.e. gateways and mixnodes)
|
||||
ensure_no_existing_bond(&owner, deps.storage)?;
|
||||
ensure_no_existing_bond(&info.sender, deps.storage)?;
|
||||
|
||||
// there's no need to explicitly check whether there already exists mixnode with the same
|
||||
// identity or sphinx keys as this is going to be done implicitly when attempting to save
|
||||
@@ -137,8 +92,7 @@ fn _try_add_mixnode(
|
||||
// check if this sender actually owns the mixnode by checking the signature
|
||||
verify_mixnode_bonding_signature(
|
||||
deps.as_ref(),
|
||||
owner.clone(),
|
||||
proxy.clone(),
|
||||
info.sender.clone(),
|
||||
pledge.clone(),
|
||||
mixnode.clone(),
|
||||
cost_params.clone(),
|
||||
@@ -146,7 +100,7 @@ fn _try_add_mixnode(
|
||||
)?;
|
||||
|
||||
// update the signing nonce associated with this sender so that the future signature would be made on the new value
|
||||
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
|
||||
signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?;
|
||||
|
||||
let node_identity = mixnode.identity_key.clone();
|
||||
let (node_id, layer) = save_new_mixnode(
|
||||
@@ -154,14 +108,12 @@ fn _try_add_mixnode(
|
||||
env,
|
||||
mixnode,
|
||||
cost_params,
|
||||
owner.clone(),
|
||||
proxy.clone(),
|
||||
info.sender.clone(),
|
||||
pledge.clone(),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(new_mixnode_bonding_event(
|
||||
&owner,
|
||||
&proxy,
|
||||
&info.sender,
|
||||
&pledge,
|
||||
&node_identity,
|
||||
node_id,
|
||||
@@ -174,43 +126,19 @@ pub fn try_increase_pledge(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_increase_pledge(deps, env, info.funds, info.sender, None)
|
||||
}
|
||||
|
||||
pub fn try_increase_pledge_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
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_increase_pledge(deps, env, info.funds, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn _try_increase_pledge(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
increase: Vec<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 mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
|
||||
let mut pending_changes = mix_details.pending_changes;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
// increasing pledge is only allowed if the epoch is currently not in the process of being advanced
|
||||
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 rewarding_denom = rewarding_denom(deps.storage)?;
|
||||
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
|
||||
let pledge_increase = validate_pledge(info.funds, coin(1, rewarding_denom))?;
|
||||
|
||||
let cosmos_event = new_pending_pledge_increase_event(mix_id, &pledge_increase);
|
||||
|
||||
@@ -232,39 +160,14 @@ pub fn try_decrease_pledge(
|
||||
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 mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
|
||||
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)?;
|
||||
|
||||
@@ -312,34 +215,12 @@ pub fn _try_decrease_pledge(
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
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_remove_mixnode(deps, env, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode(
|
||||
pub(crate) fn try_remove_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_remove_mixnode(deps, env, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> 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, &info.sender)?;
|
||||
let pending_changes = storage::PENDING_MIXNODE_CHANGES
|
||||
.may_load(deps.storage, existing_bond.mix_id)?
|
||||
.unwrap_or_default();
|
||||
@@ -348,15 +229,12 @@ pub(crate) fn _try_remove_mixnode(
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
// see if the proxy matches
|
||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
// if there are any pending requests to change the pledge, wait for them to resolve before allowing the unbonding
|
||||
ensure_no_pending_pledge_changes(&pending_changes)?;
|
||||
|
||||
// set `is_unbonding` field
|
||||
// clippy beta 1.70.0-beta.1 false positive
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let mut updated_bond = existing_bond.clone();
|
||||
updated_bond.is_unbonding = true;
|
||||
storage::mixnode_bonds().replace(
|
||||
@@ -375,7 +253,6 @@ pub(crate) fn _try_remove_mixnode(
|
||||
Ok(
|
||||
Response::new().add_event(new_pending_mixnode_unbonding_event(
|
||||
&existing_bond.owner,
|
||||
&existing_bond.proxy,
|
||||
existing_bond.identity(),
|
||||
existing_bond.mix_id,
|
||||
)),
|
||||
@@ -387,39 +264,13 @@ pub(crate) fn try_update_mixnode_config(
|
||||
info: MessageInfo,
|
||||
new_config: MixNodeConfigUpdate,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let owner = info.sender;
|
||||
_try_update_mixnode_config(deps, new_config, owner, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_update_mixnode_config_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
new_config: MixNodeConfigUpdate,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
let proxy = info.sender;
|
||||
_try_update_mixnode_config(deps, new_config, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_update_mixnode_config(
|
||||
deps: DepsMut<'_>,
|
||||
new_config: MixNodeConfigUpdate,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> 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, &info.sender)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||
|
||||
let cfg_update_event =
|
||||
new_mixnode_config_update_event(existing_bond.mix_id, &owner, &proxy, &new_config);
|
||||
new_mixnode_config_update_event(existing_bond.mix_id, &info.sender, &new_config);
|
||||
|
||||
// clippy beta 1.70.0-beta.1 false positive
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let mut updated_bond = existing_bond.clone();
|
||||
updated_bond.mix_node.host = new_config.host;
|
||||
updated_bond.mix_node.mix_port = new_config.mix_port;
|
||||
@@ -442,45 +293,24 @@ pub(crate) fn try_update_mixnode_cost_params(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
new_costs: MixNodeCostParams,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let owner = info.sender;
|
||||
_try_update_mixnode_cost_params(deps, env, new_costs, owner, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_update_mixnode_cost_params_on_behalf(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
new_costs: MixNodeCostParams,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
let proxy = info.sender;
|
||||
_try_update_mixnode_cost_params(deps, env, new_costs, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_update_mixnode_cost_params(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
new_costs: MixNodeCostParams,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// see if the node still exists
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
// changing cost params 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, &existing_bond.proxy)?;
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
// ensure the profit margin is within the defined range
|
||||
ensure_profit_margin_within_range(deps.storage, new_costs.profit_margin_percent)?;
|
||||
|
||||
// ensure the operating cost is within the defined range
|
||||
ensure_operating_cost_within_range(deps.storage, &new_costs.interval_operating_cost)?;
|
||||
|
||||
let cosmos_event = new_mixnode_pending_cost_params_update_event(
|
||||
existing_bond.mix_id,
|
||||
&owner,
|
||||
&proxy,
|
||||
&info.sender,
|
||||
&new_costs,
|
||||
);
|
||||
|
||||
@@ -497,7 +327,7 @@ pub(crate) fn _try_update_mixnode_cost_params(
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{Order, StdResult, Uint128};
|
||||
use cosmwasm_std::{Addr, Order, StdResult, Uint128};
|
||||
|
||||
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, ExecuteMsg, LayerDistribution, Percent};
|
||||
@@ -680,38 +510,6 @@ pub mod tests {
|
||||
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_add_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
let (mixnode, sig, _) = test.mixnode_with_signature(owner, None);
|
||||
let cost_params = fixtures::mix_node_cost_params_fixture();
|
||||
|
||||
let res = try_add_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &good_mixnode_pledge()),
|
||||
mixnode,
|
||||
cost_params,
|
||||
owner.to_string(),
|
||||
sig,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_mixnode_cant_be_performed_if_epoch_transition_is_in_progress() {
|
||||
let bad_states = vec![
|
||||
@@ -761,23 +559,6 @@ pub mod tests {
|
||||
);
|
||||
|
||||
let mix_id = test.add_dummy_mixnode(owner, None);
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// attempted to remove on behalf with invalid proxy (current is `None`)
|
||||
let res = try_remove_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
owner.to_string(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: vesting_contract.into_string(),
|
||||
})
|
||||
);
|
||||
|
||||
// "normal" unbonding succeeds and unbonding event is pushed to the pending epoch events
|
||||
let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone());
|
||||
@@ -799,35 +580,6 @@ pub mod tests {
|
||||
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_remove_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
|
||||
let res = try_remove_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_remove_is_not_allowed_if_there_are_pending_pledge_changes() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -908,22 +660,7 @@ pub mod tests {
|
||||
);
|
||||
|
||||
let mix_id = test.add_dummy_mixnode(owner, None);
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// attempted to remove on behalf with invalid proxy (current is `None`)
|
||||
let res = try_update_mixnode_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
update.clone(),
|
||||
owner.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: vesting_contract.into_string(),
|
||||
})
|
||||
);
|
||||
// "normal" update succeeds
|
||||
let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone());
|
||||
assert!(res.is_ok());
|
||||
@@ -943,41 +680,6 @@ pub mod tests {
|
||||
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_mixnode_config_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
let update = MixNodeConfigUpdate {
|
||||
host: "1.1.1.1:1234".to_string(),
|
||||
mix_port: 1234,
|
||||
verloc_port: 1235,
|
||||
http_api_port: 1236,
|
||||
version: "v1.2.3".to_string(),
|
||||
};
|
||||
|
||||
let res = try_update_mixnode_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
update,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_cost_params_cant_be_updated_when_epoch_transition_is_in_progress() {
|
||||
let bad_states = vec![
|
||||
@@ -1042,23 +744,7 @@ pub mod tests {
|
||||
);
|
||||
|
||||
let mix_id = test.add_dummy_mixnode(owner, None);
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// attempted to remove on behalf with invalid proxy (current is `None`)
|
||||
let res = try_update_mixnode_cost_params_on_behalf(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
update.clone(),
|
||||
owner.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: vesting_contract.into_string(),
|
||||
})
|
||||
);
|
||||
// "normal" update succeeds
|
||||
let res = try_update_mixnode_cost_params(
|
||||
test.deps_mut(),
|
||||
@@ -1099,40 +785,6 @@ pub mod tests {
|
||||
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_mixnode_cost_params_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
let update = MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(42).unwrap(),
|
||||
interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM),
|
||||
};
|
||||
|
||||
let res = try_update_mixnode_cost_params_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
update,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adding_mixnode_with_duplicate_sphinx_key_errors_out() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -1240,69 +892,6 @@ pub mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[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");
|
||||
|
||||
test.add_dummy_mixnode(owner_without_proxy.as_str(), None);
|
||||
test.add_dummy_mixnode_with_illegal_proxy(
|
||||
owner_with_proxy.as_str(),
|
||||
None,
|
||||
proxy.clone(),
|
||||
);
|
||||
|
||||
let res = _try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
Vec::new(),
|
||||
owner_without_proxy.clone(),
|
||||
Some(proxy),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: "proxy".to_string(),
|
||||
})
|
||||
);
|
||||
|
||||
let res = _try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
Vec::new(),
|
||||
owner_with_proxy.clone(),
|
||||
None,
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "proxy".to_string(),
|
||||
incoming: "None".to_string(),
|
||||
})
|
||||
);
|
||||
|
||||
let res = _try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
Vec::new(),
|
||||
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();
|
||||
@@ -1457,35 +1046,6 @@ pub mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
|
||||
let res = try_increase_pledge_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1546,73 +1106,6 @@ pub mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[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();
|
||||
@@ -1809,38 +1302,5 @@ pub mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[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,51 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::interval::storage as interval_storage;
|
||||
use cosmwasm_std::{DepsMut, Order, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::PendingEpochEventKind;
|
||||
|
||||
fn ensure_no_pending_proxy_events(storage: &dyn Storage) -> Result<(), MixnetContractError> {
|
||||
let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(storage)?;
|
||||
let last_inserted = interval_storage::EPOCH_EVENT_ID_COUNTER.load(storage)?;
|
||||
|
||||
// no pending events
|
||||
if last_executed == last_inserted {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for maybe_event in
|
||||
interval_storage::PENDING_EPOCH_EVENTS.range(storage, None, None, Order::Ascending)
|
||||
{
|
||||
let (id, event_data) = maybe_event?;
|
||||
match event_data.kind {
|
||||
PendingEpochEventKind::Delegate { proxy, .. } => {
|
||||
if proxy.is_some() {
|
||||
return Err(MixnetContractError::FailedMigration {
|
||||
comment: format!(
|
||||
"there is a pending vesting contract delegation with id {id}"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
PendingEpochEventKind::Undelegate { proxy, .. } => {
|
||||
if proxy.is_some() {
|
||||
return Err(MixnetContractError::FailedMigration {
|
||||
comment: format!(
|
||||
"there is a pending vesting contract undelegation with id {id}"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> {
|
||||
ensure_no_pending_proxy_events(deps.storage)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response};
|
||||
|
||||
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,
|
||||
AttachSendTokens,
|
||||
};
|
||||
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_active_set_update_event, new_mix_rewarding_event,
|
||||
@@ -16,22 +28,6 @@ use mixnet_contract_common::reward_params::{
|
||||
IntervalRewardingParamsUpdate, NodeRewardParams, Performance,
|
||||
};
|
||||
use mixnet_contract_common::{Delegation, EpochState, MixId};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::rewards::helpers;
|
||||
use crate::rewards::helpers::update_and_save_last_rewarded;
|
||||
use crate::support::helpers::{
|
||||
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
|
||||
ensure_proxy_match, ensure_sent_by_vesting_contract, send_to_proxy_or_owner,
|
||||
};
|
||||
|
||||
use super::storage;
|
||||
|
||||
pub(crate) fn try_reward_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
@@ -111,6 +107,14 @@ pub(crate) fn try_reward_mixnode(
|
||||
);
|
||||
}
|
||||
|
||||
// make sure node's profit margin is within the allowed range,
|
||||
// if not adjust it accordingly
|
||||
let params = mixnet_params_storage::CONTRACT_STATE
|
||||
.load(deps.storage)?
|
||||
.params;
|
||||
mix_rewarding.normalise_profit_margin(params.profit_margin);
|
||||
mix_rewarding.normalise_operating_cost(params.interval_operating_cost);
|
||||
|
||||
let rewarding_params = storage::REWARDING_PARAMS.load(deps.storage)?;
|
||||
let node_reward_params = NodeRewardParams::new(node_performance, node_status.is_active());
|
||||
|
||||
@@ -140,37 +144,16 @@ pub(crate) fn try_withdraw_operator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_withdraw_operator_reward(deps, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_withdraw_operator_reward_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
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_withdraw_operator_reward(deps, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_withdraw_operator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// we need to grab all of the node's details so we'd known original pledge alongside
|
||||
// we need to grab all of the node's details, so we'd known original pledge alongside
|
||||
// all the earned rewards (and obviously to know if this node even exists and is still
|
||||
// in the bonded state)
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?.ok_or(
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or(
|
||||
MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner.clone(),
|
||||
owner: info.sender.clone(),
|
||||
},
|
||||
)?;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
|
||||
ensure_bonded(&mix_details.bond_information)?;
|
||||
|
||||
let reward = helpers::withdraw_operator_reward(deps.storage, mix_details)?;
|
||||
@@ -178,26 +161,13 @@ pub(crate) fn _try_withdraw_operator_reward(
|
||||
|
||||
// if the reward is zero, don't track or send anything - there's no point
|
||||
if !reward.amount.is_zero() {
|
||||
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]);
|
||||
response = response.add_message(return_tokens);
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
|
||||
// otherwise, we don't care
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
if proxy == vesting_contract {
|
||||
let msg = VestingContractExecuteMsg::TrackReward {
|
||||
amount: reward.clone(),
|
||||
address: owner.clone().into_string(),
|
||||
};
|
||||
let track_reward_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
response = response.add_message(track_reward_message);
|
||||
}
|
||||
}
|
||||
response = response.send_tokens(&info.sender, reward.clone())
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_withdraw_operator_reward_event(
|
||||
&owner, &proxy, reward, mix_id,
|
||||
&info.sender,
|
||||
reward,
|
||||
mix_id,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -205,37 +175,15 @@ pub(crate) fn try_withdraw_delegator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_withdraw_delegator_reward(deps, mix_id, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_withdraw_delegator_reward_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
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_withdraw_delegator_reward(deps, mix_id, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_withdraw_delegator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
mix_id: MixId,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// see if the delegation even exists
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
|
||||
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
|
||||
None => {
|
||||
return Err(MixnetContractError::NoMixnodeDelegationFound {
|
||||
mix_id,
|
||||
address: owner.into_string(),
|
||||
proxy: proxy.map(Addr::into_string),
|
||||
address: info.sender.into_string(),
|
||||
proxy: None,
|
||||
});
|
||||
}
|
||||
Some(delegation) => delegation,
|
||||
@@ -257,33 +205,18 @@ pub(crate) fn _try_withdraw_delegator_reward(
|
||||
_ => (),
|
||||
};
|
||||
|
||||
ensure_proxy_match(&proxy, &delegation.proxy)?;
|
||||
|
||||
let reward = helpers::withdraw_delegator_reward(deps.storage, delegation, mix_rewarding)?;
|
||||
let mut response = Response::new();
|
||||
|
||||
// if the reward is zero, don't track or send anything - there's no point
|
||||
if !reward.amount.is_zero() {
|
||||
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]);
|
||||
response = response.add_message(return_tokens);
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
|
||||
// otherwise, we don't care
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
if proxy == vesting_contract {
|
||||
let msg = VestingContractExecuteMsg::TrackReward {
|
||||
amount: reward.clone(),
|
||||
address: owner.clone().into_string(),
|
||||
};
|
||||
let track_reward_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
response = response.add_message(track_reward_message);
|
||||
}
|
||||
}
|
||||
response = response.send_tokens(&info.sender, reward.clone())
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_withdraw_delegator_reward_event(
|
||||
&owner, &proxy, reward, mix_id,
|
||||
&info.sender,
|
||||
reward,
|
||||
mix_id,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1405,13 +1338,10 @@ pub mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod withdrawing_delegator_reward {
|
||||
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use crate::interval::pending_events;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
|
||||
use cosmwasm_std::{BankMsg, CosmosMsg, Decimal, Uint128};
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -1742,60 +1672,14 @@ pub mod tests {
|
||||
let accumulated_actual = truncate_reward_amount(accumulated_quad);
|
||||
assert_eq_with_leeway(total_claimed, accumulated_actual, Uint128::new(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id =
|
||||
test.add_dummy_mixnode("mix-owner1", Some(Uint128::new(1_000_000_000_000)));
|
||||
|
||||
let delegator = "delegator";
|
||||
|
||||
test.add_immediate_delegation_with_illegal_proxy(
|
||||
delegator,
|
||||
100_000_000u128,
|
||||
mix_id,
|
||||
illegal_proxy.clone(),
|
||||
);
|
||||
|
||||
// reward the node
|
||||
test.skip_to_next_epoch_end();
|
||||
test.force_change_rewarded_set(vec![mix_id]);
|
||||
test.start_epoch_transition();
|
||||
test.reward_with_distribution(mix_id, test_helpers::performance(100.0));
|
||||
|
||||
let res = try_withdraw_delegator_reward_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
mix_id,
|
||||
delegator.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod withdrawing_operator_reward {
|
||||
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128};
|
||||
|
||||
use crate::interval::pending_events;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
|
||||
use super::*;
|
||||
use crate::interval::pending_events;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::{Addr, BankMsg, CosmosMsg, Uint128};
|
||||
|
||||
#[test]
|
||||
fn can_only_be_done_if_bond_exists() {
|
||||
@@ -1908,42 +1792,6 @@ pub mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "mix-owner1";
|
||||
let mix_id = test.add_dummy_mixnode_with_illegal_proxy(
|
||||
owner,
|
||||
Some(Uint128::new(1_000_000_000_000)),
|
||||
illegal_proxy.clone(),
|
||||
);
|
||||
|
||||
// reward the node
|
||||
test.skip_to_next_epoch_end();
|
||||
test.force_change_rewarded_set(vec![mix_id]);
|
||||
test.start_epoch_transition();
|
||||
test.reward_with_distribution(mix_id, test_helpers::performance(100.0));
|
||||
|
||||
let res = try_withdraw_operator_reward_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
use crate::gateways::storage as gateways_storage;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
|
||||
use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Response, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixNodeBond};
|
||||
use nym_contracts_common::Percent;
|
||||
|
||||
// helper trait to attach `Msg` to a response if it's provided
|
||||
#[allow(dead_code)]
|
||||
@@ -26,131 +26,16 @@ impl<T> AttachOptionalMessage<T> for Response<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// another helper trait to remove some duplicate code and consolidate comments regarding
|
||||
// possible epoch progression halting behaviour
|
||||
pub(crate) trait VestingTracking
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn maybe_add_track_vesting_undelegation_message(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError>;
|
||||
|
||||
fn maybe_add_track_vesting_unbond_mixnode_message(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError>;
|
||||
|
||||
fn maybe_add_track_vesting_decrease_mixnode_pledge(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError>;
|
||||
pub(crate) trait AttachSendTokens {
|
||||
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self;
|
||||
}
|
||||
|
||||
impl VestingTracking for Response {
|
||||
fn maybe_add_track_vesting_undelegation_message(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
// if there's a proxy set (i.e. the vesting contract), send the track message
|
||||
if let Some(proxy) = proxy {
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
|
||||
|
||||
// Note: this can INTENTIONALLY cause epoch progression halt if the proxy is not the vesting contract
|
||||
// But this is fine, since this situation should have NEVER occurred in the first place
|
||||
// (as all 'on_behalf' methods, including 'DelegateToMixnodeOnBehalf' that got us here,
|
||||
// explicitly require the proxy to be the vesting contract)
|
||||
// 'fixing' it would require manually inspecting the problematic event, investigating
|
||||
// it's cause and manually (presumably via migration) clearing it.
|
||||
if proxy != vesting_contract {
|
||||
return Err(MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: proxy,
|
||||
vesting_contract,
|
||||
});
|
||||
}
|
||||
|
||||
let msg = VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
};
|
||||
|
||||
let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
Ok(self.add_message(track_undelegate_message))
|
||||
} else {
|
||||
// there's no proxy so nothing to do
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_add_track_vesting_unbond_mixnode_message(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
// if there's a proxy set (i.e. the vesting contract), send the track message
|
||||
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::TrackUnbondMixnode { owner, amount };
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
Ok(self.add_message(track_unbond_message))
|
||||
} else {
|
||||
// there's no proxy so nothing to do
|
||||
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)
|
||||
}
|
||||
impl<T> AttachSendTokens for Response<T> {
|
||||
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self {
|
||||
self.add_message(BankMsg::Send {
|
||||
to_address: to.as_ref().to_string(),
|
||||
amount: vec![amount],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,20 +43,6 @@ impl VestingTracking for Response {
|
||||
// api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
|
||||
// }
|
||||
|
||||
/// Attempts to construct a `BankMsg` to send specified tokens to the provided
|
||||
/// proxy address. If that's unavailable, the `BankMsg` will use the "owner" as the
|
||||
/// "to_address".
|
||||
pub(crate) fn send_to_proxy_or_owner(
|
||||
proxy: &Option<Addr>,
|
||||
owner: &Addr,
|
||||
amount: Vec<Coin>,
|
||||
) -> BankMsg {
|
||||
BankMsg::Send {
|
||||
to_address: proxy.as_ref().unwrap_or(owner).to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_pledge(
|
||||
mut pledge: Vec<Coin>,
|
||||
minimum_pledge: Coin,
|
||||
@@ -337,39 +208,6 @@ pub(crate) fn ensure_is_owner(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_proxy_match(
|
||||
actual: &Option<Addr>,
|
||||
expected: &Option<Addr>,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
if actual != expected {
|
||||
return Err(MixnetContractError::ProxyMismatch {
|
||||
existing: expected
|
||||
.as_ref()
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
incoming: actual
|
||||
.as_ref()
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_sent_by_vesting_contract(
|
||||
info: &MessageInfo,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let vesting_contract_address =
|
||||
crate::mixnet_contract_settings::storage::vesting_contract_address(storage)?;
|
||||
if info.sender != vesting_contract_address {
|
||||
Err(MixnetContractError::SenderIsNotVestingContract {
|
||||
received: info.sender.clone(),
|
||||
vesting_contract: vesting_contract_address,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> {
|
||||
if bond.is_unbonding {
|
||||
return Err(MixnetContractError::MixnodeIsUnbonding {
|
||||
@@ -431,3 +269,34 @@ pub(crate) fn decode_ed25519_identity_key(
|
||||
|
||||
Ok(public_key)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_profit_margin_within_range(
|
||||
storage: &dyn Storage,
|
||||
profit_margin: Percent,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let range = mixnet_params_storage::profit_margin_range(storage)?;
|
||||
if !range.within_range(profit_margin) {
|
||||
return Err(MixnetContractError::ProfitMarginOutsideRange {
|
||||
provided: profit_margin,
|
||||
range,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_operating_cost_within_range(
|
||||
storage: &dyn Storage,
|
||||
operating_cost: &Coin,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let range = mixnet_params_storage::interval_oprating_cost_range(storage)?;
|
||||
if !range.within_range(operating_cost.amount) {
|
||||
return Err(MixnetContractError::OperatingCostOutsideRange {
|
||||
denom: operating_cost.denom.clone(),
|
||||
provided: operating_cost.amount,
|
||||
range,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub(crate) fn valid_bond_gateway_msg(
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
|
||||
let msg = gateway_bonding_sign_payload(deps, sender, None, gateway.clone(), stake);
|
||||
let msg = gateway_bonding_sign_payload(deps, sender, gateway.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let identity_key = keypair.public_key().to_base58_string();
|
||||
|
||||
@@ -14,8 +14,7 @@ pub mod test_helpers {
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::delegations::transactions::try_delegate_to_mixnode;
|
||||
use crate::families::transactions::{try_create_family, try_join_family};
|
||||
use crate::gateways::storage as gateways_storage;
|
||||
use crate::gateways::transactions::{try_add_gateway, try_add_gateway_on_behalf};
|
||||
use crate::gateways::transactions::try_add_gateway;
|
||||
use crate::interval::transactions::{
|
||||
perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition,
|
||||
};
|
||||
@@ -27,9 +26,7 @@ pub mod test_helpers {
|
||||
};
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::mixnodes::storage::mixnode_bonds;
|
||||
use crate::mixnodes::transactions::{
|
||||
try_add_mixnode, try_add_mixnode_on_behalf, try_remove_mixnode,
|
||||
};
|
||||
use crate::mixnodes::transactions::{try_add_mixnode, try_remove_mixnode};
|
||||
use crate::rewards::queries::{
|
||||
query_pending_delegator_reward, query_pending_mixnode_operator_reward,
|
||||
};
|
||||
@@ -45,7 +42,7 @@ pub mod test_helpers {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::testing::MockApi;
|
||||
use cosmwasm_std::testing::MockQuerier;
|
||||
use cosmwasm_std::{coin, coins, Addr, Api, BankMsg, CosmosMsg, Storage};
|
||||
use cosmwasm_std::{coin, coins, Addr, BankMsg, CosmosMsg, Storage};
|
||||
use cosmwasm_std::{Coin, Order};
|
||||
use cosmwasm_std::{Decimal, Empty, MemoryStorage};
|
||||
use cosmwasm_std::{Deps, OwnedDeps};
|
||||
@@ -148,13 +145,6 @@ pub mod test_helpers {
|
||||
self.owner.clone()
|
||||
}
|
||||
|
||||
pub fn vesting_contract(&self) -> Addr {
|
||||
mixnet_params_storage::CONTRACT_STATE
|
||||
.load(self.deps().storage)
|
||||
.unwrap()
|
||||
.vesting_contract_address
|
||||
}
|
||||
|
||||
pub fn coin(&self, amount: u128) -> Coin {
|
||||
coin(amount, rewarding_denom(self.deps().storage).unwrap())
|
||||
}
|
||||
@@ -178,7 +168,6 @@ pub mod test_helpers {
|
||||
&mut self,
|
||||
family_owner_keys: &identity::KeyPair,
|
||||
member_node: IdentityKeyRef,
|
||||
vesting: bool,
|
||||
) -> MessageSignature {
|
||||
let identity = family_owner_keys.public_key().to_base58_string();
|
||||
|
||||
@@ -195,14 +184,7 @@ pub mod test_helpers {
|
||||
|
||||
let nonce = signing_storage::get_signing_nonce(self.deps().storage, owner).unwrap();
|
||||
|
||||
let proxy = if vesting {
|
||||
Some(self.vesting_contract())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let msg =
|
||||
construct_family_join_permit(nonce, family_head, proxy, member_node.to_owned());
|
||||
let msg = construct_family_join_permit(nonce, family_head, member_node.to_owned());
|
||||
|
||||
let sig_bytes = family_owner_keys
|
||||
.private_key()
|
||||
@@ -217,13 +199,11 @@ pub mod test_helpers {
|
||||
member: &str,
|
||||
member_keys: &identity::KeyPair,
|
||||
head_keys: &identity::KeyPair,
|
||||
vesting: bool,
|
||||
) {
|
||||
let member_identity = member_keys.public_key().to_base58_string();
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
|
||||
let join_permit =
|
||||
self.generate_family_join_permit(head_keys, &member_identity, vesting);
|
||||
let join_permit = self.generate_family_join_permit(head_keys, &member_identity);
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
|
||||
try_join_family(
|
||||
@@ -235,12 +215,13 @@ pub mod test_helpers {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_dummy_mixnode_with_new_family(
|
||||
&mut self,
|
||||
head: &str,
|
||||
label: &str,
|
||||
) -> (MixId, identity::KeyPair) {
|
||||
let (mix_id, keys) = self.add_dummy_mixnode_with_proxy_and_keypair(head, None);
|
||||
let (mix_id, keys) = self.add_dummy_mixnode_with_keypair(head, None);
|
||||
|
||||
try_create_family(self.deps_mut(), mock_info(head, &[]), label.to_string()).unwrap();
|
||||
(mix_id, keys)
|
||||
@@ -338,7 +319,7 @@ pub mod test_helpers {
|
||||
stake: Option<Uint128>,
|
||||
) -> MessageSignature {
|
||||
let stake = self.make_mix_pledge(stake);
|
||||
let msg = mixnode_bonding_sign_payload(self.deps(), owner, None, mixnode, stake);
|
||||
let msg = mixnode_bonding_sign_payload(self.deps(), owner, mixnode, stake);
|
||||
ed25519_sign_message(msg, key)
|
||||
}
|
||||
|
||||
@@ -359,13 +340,8 @@ pub mod test_helpers {
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
};
|
||||
|
||||
let msg = mixnode_bonding_sign_payload(
|
||||
self.deps(),
|
||||
owner,
|
||||
None,
|
||||
mixnode.clone(),
|
||||
stake.clone(),
|
||||
);
|
||||
let msg =
|
||||
mixnode_bonding_sign_payload(self.deps(), owner, mixnode.clone(), stake.clone());
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let info = mock_info(owner, &stake);
|
||||
@@ -389,156 +365,6 @@ pub mod test_helpers {
|
||||
(current_id_counter + 1, keypair)
|
||||
}
|
||||
|
||||
pub fn add_dummy_mixnode_with_proxy_and_keypair(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
) -> (MixId, identity::KeyPair) {
|
||||
let stake = self.make_mix_pledge(stake);
|
||||
|
||||
let proxy = self.vesting_contract();
|
||||
|
||||
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(),
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
};
|
||||
|
||||
let msg = mixnode_bonding_sign_payload(
|
||||
self.deps(),
|
||||
owner,
|
||||
Some(proxy.clone()),
|
||||
mixnode.clone(),
|
||||
stake.clone(),
|
||||
);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let info = mock_info(proxy.as_str(), &stake);
|
||||
let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER
|
||||
.may_load(self.deps().storage)
|
||||
.unwrap()
|
||||
.unwrap_or_default();
|
||||
|
||||
let env = self.env();
|
||||
try_add_mixnode_on_behalf(
|
||||
self.deps_mut(),
|
||||
env,
|
||||
info,
|
||||
mixnode,
|
||||
tests::fixtures::mix_node_cost_params_fixture(),
|
||||
owner.to_string(),
|
||||
owner_signature,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// newly added mixnode gets assigned the current counter + 1
|
||||
(current_id_counter + 1, keypair)
|
||||
}
|
||||
|
||||
pub fn add_dummy_mixnode_with_legal_proxy(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
) -> MixId {
|
||||
self.add_dummy_mixnode_with_proxy_and_keypair(owner, stake)
|
||||
.0
|
||||
}
|
||||
|
||||
pub fn set_illegal_mixnode_proxy(&mut self, mix_id: MixId, proxy: Addr) {
|
||||
let mut bond_details = mixnodes_storage::mixnode_bonds()
|
||||
.load(self.deps().storage, mix_id)
|
||||
.unwrap();
|
||||
bond_details.proxy = Some(proxy);
|
||||
mixnodes_storage::mixnode_bonds()
|
||||
.save(self.deps_mut().storage, mix_id, &bond_details)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_dummy_gateway_with_illegal_proxy(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
proxy: Addr,
|
||||
) -> IdentityKey {
|
||||
let gateway_identity = self.add_dummy_gateway_with_legal_proxy(owner, stake);
|
||||
self.set_illegal_gateway_proxy(&gateway_identity, proxy);
|
||||
gateway_identity
|
||||
}
|
||||
|
||||
pub fn set_illegal_gateway_proxy(&mut self, gateway_id: &str, proxy: Addr) {
|
||||
let mut gateway = gateways_storage::gateways()
|
||||
.load(self.deps().storage, gateway_id)
|
||||
.unwrap();
|
||||
gateway.proxy = Some(proxy);
|
||||
gateways_storage::gateways()
|
||||
.save(self.deps_mut().storage, gateway_id, &gateway)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_dummy_gateway_with_legal_proxy(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
) -> IdentityKey {
|
||||
let stake = match stake {
|
||||
Some(amount) => {
|
||||
let denom = rewarding_denom(self.deps().storage).unwrap();
|
||||
Coin { denom, amount }
|
||||
}
|
||||
None => minimum_mixnode_pledge(self.deps.as_ref().storage).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 proxy = self.vesting_contract();
|
||||
|
||||
let gateway = Gateway {
|
||||
identity_key,
|
||||
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
|
||||
let msg = gateway_bonding_sign_payload(
|
||||
self.deps(),
|
||||
owner,
|
||||
Some(proxy.clone()),
|
||||
gateway.clone(),
|
||||
vec![stake.clone()],
|
||||
);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let env = self.env();
|
||||
let info = mock_info(proxy.as_ref(), &[stake]);
|
||||
|
||||
try_add_gateway_on_behalf(
|
||||
self.deps_mut(),
|
||||
env,
|
||||
info,
|
||||
gateway,
|
||||
owner.to_string(),
|
||||
owner_signature,
|
||||
)
|
||||
.unwrap();
|
||||
keypair.public_key().to_base58_string()
|
||||
}
|
||||
|
||||
pub fn add_dummy_mixnode_with_illegal_proxy(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
proxy: Addr,
|
||||
) -> MixId {
|
||||
let mix_id = self.add_dummy_mixnode_with_legal_proxy(owner, stake);
|
||||
self.set_illegal_mixnode_proxy(mix_id, proxy);
|
||||
mix_id
|
||||
}
|
||||
|
||||
pub fn mixnode_with_signature(
|
||||
&mut self,
|
||||
sender: &str,
|
||||
@@ -555,8 +381,7 @@ pub mod test_helpers {
|
||||
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
};
|
||||
let msg =
|
||||
mixnode_bonding_sign_payload(self.deps(), sender, None, mixnode.clone(), stake);
|
||||
let msg = mixnode_bonding_sign_payload(self.deps(), sender, mixnode.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
(mixnode, owner_signature, keypair)
|
||||
@@ -579,8 +404,7 @@ pub mod test_helpers {
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
|
||||
let msg =
|
||||
gateway_bonding_sign_payload(self.deps(), sender, None, gateway.clone(), stake);
|
||||
let msg = gateway_bonding_sign_payload(self.deps(), sender, gateway.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
(gateway, owner_signature)
|
||||
@@ -625,87 +449,10 @@ pub mod test_helpers {
|
||||
Addr::unchecked(delegator),
|
||||
target,
|
||||
amount,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_immediate_delegation_with_legal_proxy(
|
||||
&mut self,
|
||||
delegator: &str,
|
||||
amount: impl Into<Uint128>,
|
||||
target: MixId,
|
||||
) {
|
||||
let denom = rewarding_denom(self.deps().storage).unwrap();
|
||||
let amount = Coin {
|
||||
denom,
|
||||
amount: amount.into(),
|
||||
};
|
||||
let env = self.env();
|
||||
let proxy = self.vesting_contract();
|
||||
pending_events::delegate(
|
||||
self.deps_mut(),
|
||||
&env,
|
||||
env.block.height,
|
||||
Addr::unchecked(delegator),
|
||||
target,
|
||||
amount,
|
||||
Some(proxy),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// to set illegal proxy we have to bypass "normal" flow and put the value
|
||||
// directly into the storage
|
||||
pub fn add_immediate_delegation_with_illegal_proxy(
|
||||
&mut self,
|
||||
delegator: &str,
|
||||
amount: impl Into<Uint128>,
|
||||
target: MixId,
|
||||
proxy: Addr,
|
||||
) {
|
||||
let denom = rewarding_denom(self.deps().storage).unwrap();
|
||||
let amount = Coin {
|
||||
denom,
|
||||
amount: amount.into(),
|
||||
};
|
||||
|
||||
let owner = self.deps.api.addr_validate(delegator).unwrap();
|
||||
let storage_key = Delegation::generate_storage_key(target, &owner, Some(&proxy));
|
||||
|
||||
let mut mix_rewarding = self.mix_rewarding(target);
|
||||
|
||||
let mut stored_delegation_amount = amount;
|
||||
|
||||
if let Some(existing_delegation) = delegations_storage::delegations()
|
||||
.may_load(&self.deps.storage, storage_key.clone())
|
||||
.unwrap()
|
||||
{
|
||||
let og_with_reward = mix_rewarding.undelegate(&existing_delegation).unwrap();
|
||||
stored_delegation_amount.amount += og_with_reward.amount;
|
||||
}
|
||||
|
||||
mix_rewarding
|
||||
.add_base_delegation(stored_delegation_amount.amount)
|
||||
.unwrap();
|
||||
|
||||
let delegation = Delegation::new(
|
||||
owner,
|
||||
target,
|
||||
mix_rewarding.total_unit_reward,
|
||||
stored_delegation_amount,
|
||||
self.env.block.height,
|
||||
Some(proxy),
|
||||
);
|
||||
|
||||
delegations_storage::delegations()
|
||||
.save(&mut self.deps.storage, storage_key, &delegation)
|
||||
.unwrap();
|
||||
rewards_storage::MIXNODE_REWARDING
|
||||
.save(&mut self.deps.storage, target, &mix_rewarding)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn add_delegation(
|
||||
&mut self,
|
||||
@@ -724,14 +471,8 @@ pub mod test_helpers {
|
||||
|
||||
pub fn remove_immediate_delegation(&mut self, delegator: &str, target: MixId) {
|
||||
let height = self.env.block.height;
|
||||
pending_events::undelegate(
|
||||
self.deps_mut(),
|
||||
height,
|
||||
Addr::unchecked(delegator),
|
||||
target,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
pending_events::undelegate(self.deps_mut(), height, Addr::unchecked(delegator), target)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn start_epoch_transition(&mut self) {
|
||||
@@ -1109,7 +850,6 @@ pub mod test_helpers {
|
||||
Addr::unchecked(format!("owner{}", i)),
|
||||
mix_id,
|
||||
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1205,7 +945,6 @@ pub mod test_helpers {
|
||||
pub fn mixnode_bonding_sign_payload(
|
||||
deps: Deps<'_>,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
mixnode: MixNode,
|
||||
stake: Vec<Coin>,
|
||||
) -> SignableMixNodeBondingMsg {
|
||||
@@ -1214,14 +953,13 @@ pub mod test_helpers {
|
||||
signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap();
|
||||
|
||||
let payload = MixnodeBondingPayload::new(mixnode, cost_params);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), stake, payload);
|
||||
SignableMixNodeBondingMsg::new(nonce, content)
|
||||
}
|
||||
|
||||
pub fn gateway_bonding_sign_payload(
|
||||
deps: Deps<'_>,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
gateway: Gateway,
|
||||
stake: Vec<Coin>,
|
||||
) -> SignableGatewayBondingMsg {
|
||||
@@ -1229,7 +967,7 @@ pub mod test_helpers {
|
||||
signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap();
|
||||
|
||||
let payload = GatewayBondingPayload::new(gateway);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), stake, payload);
|
||||
SignableGatewayBondingMsg::new(nonce, content)
|
||||
}
|
||||
|
||||
@@ -1258,6 +996,8 @@ pub mod test_helpers {
|
||||
epochs_in_interval: 720,
|
||||
epoch_duration: Duration::from_secs(60 * 60),
|
||||
initial_rewarding_params: initial_rewarding_params(),
|
||||
profit_margin: Default::default(),
|
||||
interval_operating_cost: Default::default(),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
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::support::helpers::{
|
||||
ensure_bonded, ensure_epoch_in_progress_state, ensure_no_pending_pledge_changes,
|
||||
};
|
||||
use cosmwasm_std::{wasm_execute, DepsMut, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{Delegation, MixId};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingExecuteMsg;
|
||||
|
||||
pub(crate) fn try_migrate_vested_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or(
|
||||
MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: info.sender.clone(),
|
||||
},
|
||||
)?;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
ensure_no_pending_pledge_changes(&mix_details.pending_changes)?;
|
||||
ensure_bonded(&mix_details.bond_information)?;
|
||||
|
||||
let Some(proxy) = &mix_details.bond_information.proxy else {
|
||||
return Err(MixnetContractError::NotAVestingMixnode);
|
||||
};
|
||||
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
if proxy != vesting_contract {
|
||||
return Err(MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: proxy.clone(),
|
||||
vesting_contract,
|
||||
});
|
||||
}
|
||||
|
||||
let mut updated_bond = mix_details.bond_information.clone();
|
||||
updated_bond.proxy = None;
|
||||
mixnodes_storage::mixnode_bonds().replace(
|
||||
deps.storage,
|
||||
mix_id,
|
||||
Some(&updated_bond),
|
||||
Some(&mix_details.bond_information),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_message(wasm_execute(
|
||||
vesting_contract,
|
||||
&VestingExecuteMsg::TrackMigratedMixnode {
|
||||
owner: info.sender.into_string(),
|
||||
},
|
||||
vec![],
|
||||
)?))
|
||||
}
|
||||
|
||||
pub(crate) fn try_migrate_vested_delegation(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
|
||||
let storage_key =
|
||||
Delegation::generate_storage_key(mix_id, &info.sender, Some(&vesting_contract));
|
||||
let Some(mut delegation) =
|
||||
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
|
||||
else {
|
||||
return Err(MixnetContractError::NotAVestingDelegation);
|
||||
};
|
||||
|
||||
// sanity check that's meant to blow up the contract
|
||||
assert_eq!(delegation.proxy, Some(vesting_contract.clone()));
|
||||
|
||||
// update the delegation and save it under the correct storage key
|
||||
delegation.proxy = None;
|
||||
let updated_storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
|
||||
delegations_storage::delegations().remove(deps.storage, storage_key)?;
|
||||
delegations_storage::delegations().save(deps.storage, updated_storage_key, &delegation)?;
|
||||
|
||||
Ok(Response::new().add_message(wasm_execute(
|
||||
vesting_contract,
|
||||
&VestingExecuteMsg::TrackMigratedDelegation {
|
||||
owner: info.sender.into_string(),
|
||||
mix_id,
|
||||
},
|
||||
vec![],
|
||||
)?))
|
||||
}
|
||||
@@ -691,6 +691,54 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id",
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
|
||||
@@ -669,6 +669,54 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id",
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
|
||||
@@ -70,55 +70,12 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, VestingContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::CreateFamily { label } => try_create_family(info, deps, label),
|
||||
ExecuteMsg::JoinFamily {
|
||||
join_permit,
|
||||
family_head,
|
||||
} => try_join_family(info, deps, join_permit, family_head),
|
||||
ExecuteMsg::LeaveFamily { family_head } => try_leave_family(info, deps, family_head),
|
||||
ExecuteMsg::KickFamilyMember { member } => try_kick_family_member(info, deps, member),
|
||||
ExecuteMsg::UpdateLockedPledgeCap { address, cap } => {
|
||||
try_update_locked_pledge_cap(address, cap, info, deps)
|
||||
}
|
||||
ExecuteMsg::TrackReward { amount, address } => {
|
||||
try_track_reward(deps, info, amount, &address)
|
||||
}
|
||||
ExecuteMsg::ClaimOperatorReward {} => try_claim_operator_reward(deps, info),
|
||||
ExecuteMsg::ClaimDelegatorReward { mix_id } => {
|
||||
try_claim_delegator_reward(deps, info, mix_id)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
|
||||
try_update_mixnode_config(new_config, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
|
||||
try_update_mixnode_cost_params(new_costs, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnetAddress { address } => {
|
||||
try_update_mixnet_address(address, info, deps)
|
||||
}
|
||||
ExecuteMsg::DelegateToMixnode {
|
||||
mix_id,
|
||||
amount,
|
||||
on_behalf_of,
|
||||
} => try_delegate_to_mixnode(mix_id, amount, on_behalf_of, info, env, deps),
|
||||
ExecuteMsg::UndelegateFromMixnode {
|
||||
mix_id,
|
||||
on_behalf_of,
|
||||
} => try_undelegate_from_mixnode(mix_id, on_behalf_of, info, deps),
|
||||
ExecuteMsg::CreateAccount {
|
||||
owner_address,
|
||||
staking_address,
|
||||
vesting_spec,
|
||||
cap,
|
||||
} => try_create_periodic_vesting_account(
|
||||
&owner_address,
|
||||
staking_address,
|
||||
vesting_spec,
|
||||
cap,
|
||||
info,
|
||||
env,
|
||||
deps,
|
||||
),
|
||||
ExecuteMsg::WithdrawVestedCoins { amount } => {
|
||||
try_withdraw_vested_coins(amount, env, info, deps)
|
||||
}
|
||||
@@ -127,47 +84,22 @@ pub fn execute(
|
||||
mix_id,
|
||||
amount,
|
||||
} => try_track_undelegation(&owner, mix_id, amount, info, deps),
|
||||
ExecuteMsg::BondMixnode {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature,
|
||||
amount,
|
||||
} => try_bond_mixnode(
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature,
|
||||
amount,
|
||||
info,
|
||||
env,
|
||||
deps,
|
||||
),
|
||||
ExecuteMsg::PledgeMore { amount } => try_pledge_more(deps, env, info, amount),
|
||||
ExecuteMsg::DecreasePledge { amount } => try_decrease_pledge(deps, info, amount),
|
||||
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
|
||||
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
|
||||
try_track_unbond_mixnode(&owner, amount, info, deps)
|
||||
}
|
||||
ExecuteMsg::TrackDecreasePledge { owner, amount } => {
|
||||
try_track_decrease_mixnode_pledge(&owner, amount, info, deps)
|
||||
}
|
||||
ExecuteMsg::BondGateway {
|
||||
gateway,
|
||||
owner_signature,
|
||||
amount,
|
||||
} => try_bond_gateway(gateway, owner_signature, amount, info, env, deps),
|
||||
ExecuteMsg::UnbondGateway {} => try_unbond_gateway(info, deps),
|
||||
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
|
||||
try_track_unbond_gateway(&owner, amount, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateGatewayConfig { new_config } => {
|
||||
try_update_gateway_config(new_config, info, deps)
|
||||
}
|
||||
ExecuteMsg::TransferOwnership { to_address } => {
|
||||
try_transfer_ownership(to_address, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateStakingAddress { to_address } => {
|
||||
try_update_staking_address(to_address, info, deps)
|
||||
ExecuteMsg::TrackMigratedMixnode { owner } => try_track_migrate_mixnode(&owner, info, deps),
|
||||
ExecuteMsg::TrackMigratedDelegation { owner, mix_id } => {
|
||||
try_track_migrate_delegation(&owner, mix_id, info, deps)
|
||||
}
|
||||
_ => Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ pub trait MixnodeBondingAccount {
|
||||
new_costs: MixNodeCostParams,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, VestingContractError>;
|
||||
fn try_track_migrated_mixnode(
|
||||
&self,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError>;
|
||||
}
|
||||
|
||||
pub trait GatewayBondingAccount {
|
||||
|
||||
@@ -44,4 +44,9 @@ pub trait DelegatingAccount {
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError>;
|
||||
fn track_migrated_delegation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError>;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ use mixnet_contract_common::{
|
||||
use vesting_contract_common::events::{
|
||||
new_ownership_transfer_event, new_periodic_vesting_account_event,
|
||||
new_staking_address_update_event, new_track_gateway_unbond_event,
|
||||
new_track_mixnode_pledge_decrease_event, new_track_mixnode_unbond_event,
|
||||
new_track_reward_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
|
||||
new_track_migrate_mixnode_event, new_track_mixnode_pledge_decrease_event,
|
||||
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
|
||||
new_vested_coins_withdraw_event,
|
||||
};
|
||||
use vesting_contract_common::{Account, PledgeCap, VestingContractError, VestingSpecification};
|
||||
|
||||
@@ -255,6 +256,35 @@ pub fn try_track_unbond_gateway(
|
||||
Ok(Response::new().add_event(new_track_gateway_unbond_event()))
|
||||
}
|
||||
|
||||
/// Track vesting mixnode being converted into the usage of liquid tokens. invoked by the mixnet contract after successful migration.
|
||||
pub fn try_track_migrate_mixnode(
|
||||
owner: &str,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, VestingContractError> {
|
||||
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
||||
return Err(VestingContractError::NotMixnetContract(info.sender));
|
||||
}
|
||||
let account = account_from_address(owner, deps.storage, deps.api)?;
|
||||
account.try_track_migrated_mixnode(deps.storage)?;
|
||||
Ok(Response::new().add_event(new_track_migrate_mixnode_event()))
|
||||
}
|
||||
|
||||
/// Track vesting delegation being converted into the usage of liquid tokens. invoked by the mixnet contract after successful migration.
|
||||
pub fn try_track_migrate_delegation(
|
||||
owner: &str,
|
||||
mix_id: MixId,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, VestingContractError> {
|
||||
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
||||
return Err(VestingContractError::NotMixnetContract(info.sender));
|
||||
}
|
||||
let account = account_from_address(owner, deps.storage, deps.api)?;
|
||||
account.track_migrated_delegation(mix_id, deps.storage)?;
|
||||
Ok(Response::new().add_event(new_track_migrate_mixnode_event()))
|
||||
}
|
||||
|
||||
/// Bond a mixnode, sends [mixnet_contract_common::ExecuteMsg::BondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
pub fn try_bond_mixnode(
|
||||
mix_node: MixNode,
|
||||
|
||||
@@ -125,4 +125,25 @@ impl DelegatingAccount for Account {
|
||||
self.save_balance(new_balance, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_migrated_delegation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError> {
|
||||
let delegation = self.total_delegations_for_mix(mix_id, storage)?;
|
||||
if delegation.is_zero() {
|
||||
return Err(VestingContractError::NoSuchDelegation(
|
||||
self.owner_address.clone(),
|
||||
mix_id,
|
||||
));
|
||||
}
|
||||
|
||||
// treat the tokens that were used for delegation as 'withdrawn'
|
||||
let current_withdrawn = self.load_withdrawn(storage)?;
|
||||
self.save_withdrawn(current_withdrawn + delegation, storage)?;
|
||||
|
||||
// remove the delegation data since it no longer belongs to the vesting contract
|
||||
self.remove_delegations_for_mix(mix_id, storage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,4 +221,24 @@ impl MixnodeBondingAccount for Account {
|
||||
.add_message(update_mixnode_costs_msg)
|
||||
.add_event(new_vesting_update_mixnode_cost_params_event()))
|
||||
}
|
||||
|
||||
fn try_track_migrated_mixnode(
|
||||
&self,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError> {
|
||||
let Some(pledge) = self.load_mixnode_pledge(storage)? else {
|
||||
return Err(VestingContractError::NoBondFound(
|
||||
self.owner_address().as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
// treat the tokens that were used for bonding as 'withdrawn'
|
||||
let current_withdrawn = self.load_withdrawn(storage)?;
|
||||
self.save_withdrawn(current_withdrawn + pledge.amount.amount, storage)?;
|
||||
|
||||
// don't change the balance as the tokens are left in the mixnet contract
|
||||
|
||||
// remove the pledge data since it no longer belongs to the vesting account
|
||||
self.remove_mixnode_pledge(storage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ pub fn populate_vesting_periods(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::contract::*;
|
||||
use crate::storage::*;
|
||||
|
||||
use crate::support::tests::helpers::vesting_account_percent_fixture;
|
||||
use crate::support::tests::helpers::{
|
||||
init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM,
|
||||
};
|
||||
use crate::traits::DelegatingAccount;
|
||||
use crate::traits::GatewayBondingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
use crate::vesting::account::StorableVestingAccountExt;
|
||||
use crate::vesting::populate_vesting_periods;
|
||||
use contracts_common::signing::MessageSignature;
|
||||
@@ -36,162 +36,56 @@ mod tests {
|
||||
fn test_account_creation() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("not_admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
|
||||
|
||||
let msg = ExecuteMsg::CreateAccount {
|
||||
owner_address: "owner".to_string(),
|
||||
staking_address: Some("staking".to_string()),
|
||||
vesting_spec: None,
|
||||
cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
|
||||
};
|
||||
// Try creating an account when not admin
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
assert!(response.is_err());
|
||||
|
||||
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 created_account = load_account(Addr::unchecked("owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
||||
assert_eq!(
|
||||
created_account.load_balance(&deps.storage).unwrap(),
|
||||
// One was liquidated
|
||||
Uint128::new(1_000_000_000_000)
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
// nothing is saved for "staking" account!
|
||||
let created_account_test_by_staking =
|
||||
load_account(Addr::unchecked("staking"), &deps.storage).unwrap();
|
||||
assert!(created_account_test_by_staking.is_none());
|
||||
|
||||
// but we can stake on its behalf!
|
||||
let stake_msg = ExecuteMsg::DelegateToMixnode {
|
||||
on_behalf_of: Some("owner".to_string()),
|
||||
mix_id: 42,
|
||||
amount: coin(500, TEST_COIN_DENOM),
|
||||
};
|
||||
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("staking", &[]),
|
||||
stake_msg,
|
||||
);
|
||||
assert!(response.is_ok());
|
||||
|
||||
assert_eq!(
|
||||
created_account.load_balance(&deps.storage).unwrap(),
|
||||
// One was liquidated
|
||||
Uint128::new(999_999_999_500)
|
||||
);
|
||||
|
||||
// Try create the same account again
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert!(response.is_err());
|
||||
|
||||
let account_again = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
assert_eq!(created_account.storage_key(), 1);
|
||||
assert_ne!(created_account.storage_key(), account_again.storage_key());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ownership_transfer() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let env = mock_env();
|
||||
let info = mock_info("owner", &[]);
|
||||
let account = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
let staker = account.staking_address().unwrap();
|
||||
let msg = ExecuteMsg::TransferOwnership {
|
||||
to_address: "new_owner".to_string(),
|
||||
};
|
||||
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap();
|
||||
let new_owner_account = load_account(Addr::unchecked("new_owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
||||
assert_eq!(
|
||||
new_owner_account.load_balance(&deps.storage),
|
||||
account.load_balance(&deps.storage)
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
// Check old account is gone
|
||||
let old_owner_account = load_account(Addr::unchecked("owner"), &deps.storage).unwrap();
|
||||
assert!(old_owner_account.is_none());
|
||||
|
||||
// Not the owner
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert!(response.is_err());
|
||||
|
||||
// can't stake on behalf of the original owner anymore, but we can do it for the new one!
|
||||
let stake_msg = ExecuteMsg::DelegateToMixnode {
|
||||
on_behalf_of: Some("owner".to_string()),
|
||||
mix_id: 42,
|
||||
amount: coin(500, TEST_COIN_DENOM),
|
||||
};
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info(staker.as_ref(), &[]),
|
||||
stake_msg,
|
||||
);
|
||||
assert!(response.is_err());
|
||||
|
||||
let new_stake_msg = ExecuteMsg::DelegateToMixnode {
|
||||
on_behalf_of: Some("new_owner".to_string()),
|
||||
mix_id: 42,
|
||||
amount: coin(500, TEST_COIN_DENOM),
|
||||
};
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info(staker.as_ref(), &[]),
|
||||
new_stake_msg,
|
||||
);
|
||||
assert!(response.is_ok());
|
||||
|
||||
let info = mock_info("new_owner", &[]);
|
||||
let msg = ExecuteMsg::UpdateStakingAddress {
|
||||
to_address: Some("new_staking".to_string()),
|
||||
};
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
|
||||
let msg = ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: Coin {
|
||||
amount: Uint128::new(1),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
};
|
||||
let info = mock_info("new_owner", &[]);
|
||||
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());
|
||||
assert!(response.is_ok());
|
||||
|
||||
let info = mock_info("owner", &[]);
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert!(response.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_staking_account() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let env = mock_env();
|
||||
let info = mock_info("staking", &[]);
|
||||
let msg = ExecuteMsg::TransferOwnership {
|
||||
to_address: "new_owner".to_string(),
|
||||
};
|
||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg);
|
||||
// Only owner can transfer
|
||||
assert!(response.is_err());
|
||||
|
||||
let msg = ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: Coin {
|
||||
amount: Uint128::new(1),
|
||||
denom: "nym".to_string(),
|
||||
},
|
||||
};
|
||||
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
|
||||
let response = execute(deps.as_mut(), env, info, msg);
|
||||
// Only owner can withdraw
|
||||
assert!(response.is_err());
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -213,31 +107,12 @@ mod tests {
|
||||
mock_info(original_staker.as_ref(), &[]),
|
||||
stake_msg.clone(),
|
||||
);
|
||||
assert!(response.is_ok());
|
||||
|
||||
let info = mock_info("owner", &[]);
|
||||
let msg = ExecuteMsg::UpdateStakingAddress {
|
||||
to_address: Some("new_staking".to_string()),
|
||||
};
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
|
||||
// the old staking account can't do any staking anymore!
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info(original_staker.as_ref(), &[]),
|
||||
stake_msg.clone(),
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
assert!(response.is_err());
|
||||
|
||||
// but the new one can
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("new_staking", &[]),
|
||||
stake_msg,
|
||||
);
|
||||
assert!(response.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -245,66 +120,27 @@ mod tests {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let amount1 = coin(1000000000, "unym");
|
||||
let amount2 = coin(100, "unym");
|
||||
let amount = coin(1000000000, "unym");
|
||||
|
||||
// create the accounts
|
||||
let msg1 = ExecuteMsg::CreateAccount {
|
||||
let msg = ExecuteMsg::CreateAccount {
|
||||
owner_address: "vesting1".to_string(),
|
||||
staking_address: None,
|
||||
vesting_spec: None,
|
||||
cap: None,
|
||||
};
|
||||
let res1 = execute(
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("admin", &[amount1.clone()]),
|
||||
msg1,
|
||||
);
|
||||
assert!(res1.is_ok());
|
||||
|
||||
let msg2 = ExecuteMsg::CreateAccount {
|
||||
owner_address: "vesting2".to_string(),
|
||||
staking_address: None,
|
||||
vesting_spec: None,
|
||||
cap: None,
|
||||
};
|
||||
let res2 = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("admin", &[amount2.clone()]),
|
||||
msg2,
|
||||
);
|
||||
assert!(res2.is_ok());
|
||||
|
||||
let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap();
|
||||
assert_eq!(vesting1, amount1);
|
||||
|
||||
let vesting2 = try_get_vesting_coins("vesting2", None, env.clone(), deps.as_ref()).unwrap();
|
||||
assert_eq!(vesting2, amount2);
|
||||
|
||||
let staking_address_change = ExecuteMsg::UpdateStakingAddress {
|
||||
to_address: Some("vesting1".to_string()),
|
||||
};
|
||||
let res = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("vesting2", &[]),
|
||||
staking_address_change,
|
||||
mock_info("admin", &[amount.clone()]),
|
||||
msg,
|
||||
);
|
||||
assert_eq!(
|
||||
Err(VestingContractError::StakingAccountExists(
|
||||
"vesting1".to_string()
|
||||
)),
|
||||
res
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
// ensure nothing has changed!
|
||||
let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap();
|
||||
assert_eq!(vesting1, amount1);
|
||||
|
||||
let vesting2 = try_get_vesting_coins("vesting2", None, env, deps.as_ref()).unwrap();
|
||||
assert_eq!(vesting2, amount2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -566,63 +402,13 @@ mod tests {
|
||||
};
|
||||
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
|
||||
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
let account = load_account(Addr::unchecked("owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Try delegating too much
|
||||
let err = account.try_delegate_to_mixnode(
|
||||
1,
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_001),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let ok = account.try_delegate_to_mixnode(
|
||||
1,
|
||||
Coin {
|
||||
amount: Uint128::new(90_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
// Fails due to delegation locked delegation cap
|
||||
let ok = account.try_delegate_to_mixnode(
|
||||
1,
|
||||
Coin {
|
||||
amount: Uint128::new(20_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_err());
|
||||
|
||||
let balance = account.load_balance(&deps.storage).unwrap();
|
||||
assert_eq!(balance, Uint128::new(910000000000));
|
||||
|
||||
// Try delegating too much againcalca
|
||||
let err = account.try_delegate_to_mixnode(
|
||||
1,
|
||||
Coin {
|
||||
amount: Uint128::new(500_000_000_001),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let total_delegations = account.total_delegations_for_mix(1, &deps.storage).unwrap();
|
||||
assert_eq!(Uint128::new(90_000_000_000), total_delegations);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -649,52 +435,24 @@ mod tests {
|
||||
amount: Uint128::new(40),
|
||||
},
|
||||
};
|
||||
// Try delegating too much
|
||||
let err = account.try_bond_mixnode(
|
||||
mix_node.clone(),
|
||||
cost_params.clone(),
|
||||
MessageSignature::from(vec![1, 2, 3]),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_001),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let ok = account.try_bond_mixnode(
|
||||
mix_node.clone(),
|
||||
cost_params.clone(),
|
||||
MessageSignature::from(vec![1, 2, 3]),
|
||||
Coin {
|
||||
let msg = ExecuteMsg::BondMixnode {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature: vec![1, 2, 3, 4].into(),
|
||||
amount: Coin {
|
||||
amount: Uint128::new(90_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
};
|
||||
let info = mock_info(account.owner_address.as_str(), &[]);
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
let balance = account.load_balance(&deps.storage).unwrap();
|
||||
assert_eq!(balance, Uint128::new(910_000_000_000));
|
||||
|
||||
// Try delegating too much again
|
||||
let err = account.try_bond_mixnode(
|
||||
mix_node,
|
||||
cost_params,
|
||||
MessageSignature::from(vec![1, 2, 3]),
|
||||
Coin {
|
||||
amount: Uint128::new(10_000_000_001),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let pledge = account.load_mixnode_pledge(&deps.storage).unwrap().unwrap();
|
||||
assert_eq!(Uint128::new(90_000_000_000), pledge.amount().amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -164,10 +164,9 @@
|
||||
<option value="https://nymtech.net/learn/papers">Academic Papers</option>
|
||||
<option value="">--------</option>
|
||||
<option value="https://nymtech.net/developers">Dev Portal</option>
|
||||
<option value="https://nymtech.net/docs/sdk/rust/rust.html">Rust SDK</option>
|
||||
<option value="https://nymtech.net/developers/sdk/rust/rust.html">Rust SDK</option>
|
||||
<option value="https://sdk.nymtech.net">Typescript SDK</option>
|
||||
<option value="">--------</option>
|
||||
<option value="">Setup Guides</option>
|
||||
<option value="https://nymtech.net/operators">Operators</option>
|
||||
<option value="https://nymtech.net/developers/nymvpn/intro.html">NymVPN Testing</option>
|
||||
</select>
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
|
||||
# Nodes
|
||||
- [Node Types (Previously Setup Guides)](nodes/overview.md)
|
||||
- [Mix Node](nodes/mixnode.md)
|
||||
- [Gateway](nodes/gateway.md)
|
||||
- [Network Requester](nodes/network-requester.md)
|
||||
- [Validator](nodes/validator.md)
|
||||
|
||||
# Wallet
|
||||
- [Desktop Wallet](wallet/desktop-wallet.md)
|
||||
|
||||
@@ -9,31 +9,31 @@ At a high level, our technologies include:
|
||||
* a **mixnet**, which encrypts and mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the **Loopix** design.
|
||||
* a privacy enhancing signature scheme called **Coconut**. Coconut allows a shift in thinking about resource access control, from an identity-based paradigm based on _who you are_ to a privacy-preserving paradigm based on _right to use_.
|
||||
* **Sphinx**, a way of transmitting armoured, layer-encrypted information packets which are indistinguishable from each other at a binary level.
|
||||
* the **Nyx** blockchain, a general-purpose CosmWasm-enabled smart contract platform, and the home of the smart contracts which keep track of the mixnet.
|
||||
* the **Nyx** blockchain, a general-purpose CosmWasm-enabled smart contract platform, and the home of the smart contracts which keep track of the mixnet.
|
||||
|
||||
The most important thing to note is that these technologies ensure privacy at two different levels of the stack: **network data transmission**, and **transactions**.
|
||||
|
||||
Here's an overview diagram of the different types of nodes making up the network:
|
||||
Here's an overview diagram of the different types of nodes making up the network:
|
||||
|
||||

|
||||
|
||||
Developers can think of the network as being comprised of **infrastructure nodes** and **clients** for interacting with this infrastructure via **P**rivacy-**e**nhanced **app**lications (PEApps).
|
||||
Developers can think of the network as being comprised of **infrastructure nodes** and **clients** for interacting with this infrastructure via **P**rivacy-**e**nhanced **app**lications (PEApps).
|
||||
|
||||
## Mixnet Infrastructure
|
||||
## Mixnet Infrastructure
|
||||
The mixnet - the different pieces of software that your traffic will pass through when using an privacy-enhanced app (PEApp) - is made up of several different types of nodes:
|
||||
|
||||
* **Mix Nodes** provide network security for network content _and_ metadata, making it impossible to see who is communicating with who, by performing packet-mixing on traffic travelling through the network.
|
||||
* **Mix Nodes** provide network security for network content _and_ metadata, making it impossible to see who is communicating with who, by performing packet-mixing on traffic travelling through the network.
|
||||
|
||||
* **Gateways** act as message storage for clients which may go offline and come back online again, and defend against denial of service attacks. The default gateway implementation included in the Nym platform code holds packets for later retrieval. For many applications (such as simple chat), this is usable out of the box, as it provides a place that potentially offline clients can retrieve packets from. The access token allows clients to pull messages from the gateway node.
|
||||
|
||||
* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the [network requester](../nodes/network-requester.md) binary.
|
||||
* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the network requester binary.
|
||||
|
||||
* **Nyx Blockchain Validators** secure the network with proof-of-stake Sybil defenses, determine which nodes are included within the network, and work together to create Coconut threshold credentials which provide anonymous access to data and resources. They also produce blocks and secure the Nyx Blockchain. Initially, this chain was used only to house the CosmWasm smart contracts keeping track of Nym's network topology, token vesting contracts, and the `NYM` token itself. In recent months, we've decided to expand the role of Nyx and instead expand its role by making it an open smart contract platform for anyone to upload CosmWasm smart contracts to. Validators also provide privacy-enhanced credentials based on the testimony of a set of decentralized, blockchain-based issuing authorities. Nym validators use the [Coconut](https://arxiv.org/abs/1802.07344) [signature scheme](https://en.wikipedia.org/wiki/Digital_signature) to issue credentials. This allows privacy apps to generate anonymous resource claims through decentralised authorities, then use them with Service Providers.
|
||||
|
||||
## Privacy-enhanced applications (PEApps)
|
||||
## Privacy-enhanced applications (PEApps)
|
||||
PEApps use a Nym client to connect to the network in order to get the available Network Topology for traffic routing, and send/receive packets to other users and services. Clients, in order to send traffic through the mixnet, connect to gateways. Since applications may go online and offline, a client's gateway provides a sort of mailbox where apps can receive their messages.
|
||||
|
||||
Nym clients connect to gateways. Messages are automatically piped to connected clients and deleted from the gateway's disk storage. If a client is offline when a message arrives, it will be stored for later retrieval. When the client connects, all messages will be delivered, and deleted from the gateway's disk.
|
||||
Nym clients connect to gateways. Messages are automatically piped to connected clients and deleted from the gateway's disk storage. If a client is offline when a message arrives, it will be stored for later retrieval. When the client connects, all messages will be delivered, and deleted from the gateway's disk.
|
||||
|
||||
When it starts up, a client registers itself with a gateway, and the gateway returns an access token. The access token plus the gateway's IP can then be used as a form of addressing for delivering packets.
|
||||
|
||||
@@ -46,4 +46,4 @@ Service Providers (SPs) may interact with external systems on behalf of a user.
|
||||
|
||||
There is also a special category of Service Provider, namely SPs that do not visibly interact with any external systems. You might think of these as crypto-utopiapps: they're doing something, but it's not possible from outside to say with any certainty what their function is, or who is interacting with them.
|
||||
|
||||
All apps talk with gateways using Sphinx packets and a small set of simple control messages. These messages are sent to gateways over websockets. Each app client has a long-lived relationship with its gateway; Nym defines messages for clients registering and authenticating with gateways, as well as sending encrypted Sphinx packets.
|
||||
All apps talk with gateways using Sphinx packets and a small set of simple control messages. These messages are sent to gateways over websockets. Each app client has a long-lived relationship with its gateway; Nym defines messages for clients registering and authenticating with gateways, as well as sending encrypted Sphinx packets.
|
||||
|
||||
@@ -8,7 +8,7 @@ Nym has two main codebases:
|
||||
- the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code _except_ for the validators.
|
||||
- the [Nym validators](https://github.com/nymtech/nyxd), written in Go.
|
||||
|
||||
> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator.md) instead.**
|
||||
> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](https://nymtech.net/operators/nodes/validator-setup.html) instead.**
|
||||
|
||||
## Prerequisites
|
||||
- Debian/Ubuntu: `pkg-config`, `build-essential`, `libssl-dev`, `curl`, `jq`, `git`
|
||||
@@ -54,18 +54,17 @@ cargo build --release # build your binaries with **mainnet** configuration
|
||||
|
||||
Quite a bit of stuff gets built. The key working parts are:
|
||||
|
||||
* [mix node](../nodes/mixnode.md): `nym-mixnode`
|
||||
* [gateway node](../nodes/gateway.md): `nym-gateway`
|
||||
* [websocket client](https://nymtech.net/developers/clients/websocket-client.md): `nym-client`
|
||||
* [socks5 client](https://nymtech.net/developers/clients/socks5-client.md): `nym-socks5-client`
|
||||
* [network requester](../nodes/network-requester.md): `nym-network-requester`
|
||||
* [nym-cli tool](../tools/nym-cli.md): `nym-cli`
|
||||
* [Nym Node](https://nymtech.net/operators/nodes/nym-node.html): `nym-node`
|
||||
* [Validator](https://nymtech.net/operators/nodes/validator-setup.html)
|
||||
* [websocket client](https://nymtech.net/docs/clients/websocket-client.html): `nym-client`
|
||||
* [socks5 client](https://nymtech.net/docs/clients/socks5-client.html): `nym-socks5-client`
|
||||
* [webassembly client](https://nymtech.net/docs/clients/webassembly-client.html): `webassembly-client`
|
||||
* [nym-cli tool](https://nymtech.net/docs/tools/nym-cli.html): `nym-cli`
|
||||
* [nym-api](https://nymtech.net/operators/nodes/nym-api.html): `nym-api`
|
||||
|
||||
[//]: # (* [nymvisor](https://nymtech.net/operators/nodes/nymvisor-upgrade.html): `nymvisor`)
|
||||
* [nymvisor](https://nymtech.net/operators/nodes/nymvisor-upgrade.html): `nymvisor`
|
||||
|
||||
The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages.
|
||||
* [Nym Wallet](../wallet/desktop-wallet.md)
|
||||
* [Network Explorer UI](../explorers/mixnet-explorer.md)
|
||||
* [Nym Wallet](https://nymtech.net/docs/wallet/desktop-wallet.html)
|
||||
* [Network Explorer UI](https://nymtech.net/docs/explorers/mixnet-explorer.html)
|
||||
|
||||
> You cannot build from GitHub's .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn't a Git repository). Check the code out from github using `git clone` instead.
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Node Types
|
||||
|
||||
> This section was previously the node setup guides. These have been migrated to their own [**Operator Guides**](https://nymtech.net/operators) book.
|
||||
>
|
||||
> For setup and maintenance guides, go to the Operators book linked above.
|
||||
>
|
||||
> This section is a little spartan for the moment, but we will be adding detailed information about how exactly each node functions, as well as references to any literature and technical specs in the near future.
|
||||
```admonish info
|
||||
We are working on a detailed description of how each component of Nym Mixnet and Nyx blockchain functions, as well as references to any literature and technical specs.
|
||||
|
||||
This section contains information on the different node types of the mixnet and blockchain.
|
||||
Meanwhile please refer to our [**Operators Guide**](https://nymtech.net/operators) book.
|
||||
```
|
||||
|
||||
Vendored
+1
-2
@@ -164,10 +164,9 @@
|
||||
<option value="https://nymtech.net/learn/papers">Academic Papers</option>
|
||||
<option value="">--------</option>
|
||||
<option value="https://nymtech.net/developers">Dev Portal</option>
|
||||
<option value="https://nymtech.net/docs/sdk/rust/rust.html">Rust SDK</option>
|
||||
<option value="https://nymtech.net/developers/sdk/rust/rust.html">Rust SDK</option>
|
||||
<option value="https://sdk.nymtech.net">Typescript SDK</option>
|
||||
<option value="">--------</option>
|
||||
<option value="">Setup Guides</option>
|
||||
<option value="https://nymtech.net/operators">Operators</option>
|
||||
<option value="https://nymtech.net/developers/nymvpn/intro.html">NymVPN Testing</option>
|
||||
</select>
|
||||
|
||||
@@ -2,6 +2,216 @@
|
||||
|
||||
This page displays a full list of all the changes during our release cycle from [`v2024.3-eclipse`](https://github.com/nymtech/nym/blob/nym-binaries-v2024.3-eclipse/CHANGELOG.md) onwards. Operators can find here the newest updates together with links to relevant documentation. The list is sorted so that the newest changes appear first.
|
||||
|
||||
## `v2024.8-wispa`
|
||||
|
||||
- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.8-wispa)
|
||||
- [Release CHANGELOG.md](https://github.com/nymtech/nym/blob/nym-binaries-v2024.8-wispa/CHANGELOG.md)
|
||||
- [`nym-node`](nodes/nym-node.md) version `1.1.5`
|
||||
|
||||
~~~admonish example collapsible=true title='CHANGELOG.md'
|
||||
- add event parsing to support cosmos_sdk > 0.50 ([#4697])
|
||||
- Fix NR config compatibility ([#4690])
|
||||
- Remove UserAgent constructor since it's weakly typed ([#4689])
|
||||
- [bugfix]: Node_api_check CLI looked over roles on blacklisted nodes ([#4687])
|
||||
- Add mixnodes to self describing api cache ([#4684])
|
||||
- Move and whole bump of crates to workspace and upgrade some ([#4680])
|
||||
- Remove code that refers to removed nym-network-statistics ([#4679])
|
||||
- Remove nym-network-statistics ([#4678])
|
||||
- Create UserAgent that can be passed from the binary to the nym api client ([#4677])
|
||||
- Add authenticator ([#4667])
|
||||
|
||||
[#4697]: https://github.com/nymtech/nym/pull/4697
|
||||
[#4690]: https://github.com/nymtech/nym/pull/4690
|
||||
[#4689]: https://github.com/nymtech/nym/pull/4689
|
||||
[#4687]: https://github.com/nymtech/nym/pull/4687
|
||||
[#4684]: https://github.com/nymtech/nym/pull/4684
|
||||
[#4680]: https://github.com/nymtech/nym/pull/4680
|
||||
[#4679]: https://github.com/nymtech/nym/pull/4679
|
||||
[#4678]: https://github.com/nymtech/nym/pull/4678
|
||||
[#4677]: https://github.com/nymtech/nym/pull/4677
|
||||
[#4667]: https://github.com/nymtech/nym/pull/4667
|
||||
~~~
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* [Default construct NodeRole](https://github.com/nymtech/nym/pull/4721): To preserve compatibility with newer clients interacting with older `nym-api`
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the changes in the `nym-api-requests/src/models.rs` file.
|
||||
2. Verified that the `NymNodeDescription` struct includes the new `role` field with a default value set by `default_node_role`.
|
||||
3. Checked the implementation of the `default_node_role` function to ensure it returns `NodeRole::Inactive`.
|
||||
4. Ran the updated code in the sandbox environment.
|
||||
5. Monitored the sandbox environment for any issues or errors related to the changes.
|
||||
|
||||
|
||||
**Notes (if any):**
|
||||
The test was successful. No issues were flagged during the testing in the sandbox environment. The new default value for `NodeRole` ensures backward compatibility without causing disruptions.
|
||||
~~~
|
||||
|
||||
* [Default construct NodeRole for backwards compatibility (apply [\#4721](https://github.com/nymtech/nym/pull/4721) on develop)](https://github.com/nymtech/nym/pull/4722)
|
||||
* [Add upgrades to `nym-node` for `authenticator` changes](https://github.com/nymtech/nym/pull/4703)
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the changes in the `gateway/src/error.rs` and `gateway/src/node/mod.rs` files.
|
||||
2. Verified the new error enum `AuthenticatorStartupFailure` was added to `GatewayError`.
|
||||
3. Confirmed the implementation of the `StartedAuthenticator` struct and its usage in the `start_authenticator` function.
|
||||
4. Ran the updated code in the canary environment.
|
||||
5. Monitored the canary environment for any issues or errors related to the changes.
|
||||
~~~
|
||||
|
||||
* [Add event parsing to support `cosmos_sdk` > `0.50`](https://github.com/nymtech/nym/pull/4697)
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the changes in `common/client-libs/validator-client/src/nyxd/cosmwasm_client/client_traits/signing_client.rs`, `logs.rs`, `types.rs`, and `nym-api/src/coconut/tests/mod.rs` files.
|
||||
2. Verified the addition of event parsing in the relevant functions and structs.
|
||||
3. Ensured that the `find_attribute` function correctly parses event attributes.
|
||||
4. Ran the updated code in the sandbox environment.
|
||||
5. Broadcasted transactions on the sandbox network to test the changes.
|
||||
6. Monitored the sandbox network for any malformed responses or errors after the test chain upgrade.
|
||||
~~~
|
||||
|
||||
* [Send bandwidth status messages when connecting](https://github.com/nymtech/nym/pull/4691): When connecting to the gateway we get received the available bandwidth left. Emit a status messages for this, for consumption by the application layer.
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the changes in `common/bandwidth-controller/src/event.rs`, `common/bandwidth-controller/src/lib.rs`, and `common/client-libs/gateway-client/src/client.rs` files.
|
||||
2. Verified the implementation of `BandwidthStatusMessage` enum for emitting status messages.
|
||||
3. Ensured `GatewayClient` is updated to send bandwidth status messages when connecting.
|
||||
4. Deployed the updated code on the canary environment.
|
||||
5. Connected to the gateway and checked for the emission of bandwidth status messages.
|
||||
6. Verified that the messages were correctly parsed and consumed by the application layer.
|
||||
7. Ran the VPN client to observe the parsed events.
|
||||
~~~
|
||||
|
||||
* [Fix NR config compatibility](https://github.com/nymtech/nym/pull/4690): Recently we deleted the old statistics service provider. This fixes some issues where old configs didn't work with the latest changes.
|
||||
- Make NR able to read config with old keys in
|
||||
- Remove deleted config keys from NR template
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the changes in the `service-providers/network-requester/src/config/mod.rs` and `service-providers/network-requester/src/config/template.rs` files.
|
||||
2. Ensured `NetworkRequester` config is able to read old keys for compatibility.
|
||||
3. Removed old and deleted config keys from the `NetworkRequester` template.
|
||||
4. Compiled the project to verify no issues or warnings appeared.
|
||||
5. Ran all tests to ensure that the changes did not affect the functionality.
|
||||
6. Validated that no leftover code from the old statistics service provider caused any issues.
|
||||
~~~
|
||||
|
||||
* [Remove `UserAgent` constructor since it's weakly typed](https://github.com/nymtech/nym/pull/4689):
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the changes in `common/http-api-client/src/user_agent.rs` file.
|
||||
2. Verified the removal of the `UserAgent` constructor and ensured that all instances of `UserAgent::new` are updated accordingly.
|
||||
3. Checked the implementation of `UserAgent` struct using `BinaryBuildInformation` and `BinaryBuildInformationOwned`.
|
||||
4. Deployed the updated code across different environments (QA, sandbox, and canary).
|
||||
5. Ran tests to ensure that the `UserAgent` struct functions correctly without the constructor.
|
||||
~~~
|
||||
|
||||
* [Add mixnodes to self describing api cache](https://github.com/nymtech/nym/pull/4684):
|
||||
- Abstracts getting the self describing info a bit
|
||||
- Adds mixnodes to the cache refresher as well
|
||||
- Adds `role` field to the `NodeDescription` struct, to be able to distinguish between mixnodes and gateways
|
||||
- Switched to using `NodeStatusCache` instead of `ContractCache`
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Called the new `/mixnodes/described` endpoint as well as the existing `/gateways/described` endpoint and verified that the data returned for each was correct based on the settings that different nodes have when they are setup.
|
||||
|
||||
For gateway endpoint, the “role” for now does not differentiate between entry and exit gateways, this will be implemented in the future.
|
||||
~~~
|
||||
|
||||
* [Move and whole bump of crates to workspace and upgrade some](https://github.com/nymtech/nym/pull/4680):
|
||||
- Fix cargo warning for `default_features`
|
||||
- Move dirs 4.0 to workspace
|
||||
- Use workspace `base64` dep
|
||||
- Move `rand_chacha` and `x25519-dalek` to workspace
|
||||
- Use workspace `ed25519-dalek` dep
|
||||
- Move `itertools` to workspace deps and upgrade
|
||||
- Move a few partial deps to workspace while preserving versions
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the changes to move and upgrade crates to the workspace.
|
||||
2. Verified the updated dependencies:
|
||||
- Moved `dirs` to version 4.0 in the workspace.
|
||||
- Updated the `base64` dependency to use the workspace version.
|
||||
- Moved `rand_chacha` and `x25519-dalek` to the workspace.
|
||||
- Updated `ed25519-dalek` to use the workspace version.
|
||||
- Moved and upgraded `itertools` in the workspace.
|
||||
- Moved other partial dependencies to the workspace while preserving their versions.
|
||||
3. Ensured the `Cargo.toml` files across the project reflect these changes correctly.
|
||||
4. Compiled the entire project to check for any issues or warnings.
|
||||
5. Verified that all tests pass successfully after the changes.
|
||||
~~~
|
||||
|
||||
* [Remove `nym-network-statistics`](https://github.com/nymtech/nym/pull/4678): Remove `nym-network-statistics` service provider that is no longer used.
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the project to identify all references to `nym-network-statistics`.
|
||||
2. Removed all code and dependencies associated with `nym-network-statistics`.
|
||||
3. Ensured that no references to `nym-network-statistics` remain in the codebase, including comments, imports, and configuration files.
|
||||
4. Compiled the project to check for any issues or warnings.
|
||||
5. Ran all tests to ensure the removal did not affect the functionality of the project.
|
||||
~~~
|
||||
|
||||
|
||||
* [Remove code that refers to removed `nym-network-statistics`](https://github.com/nymtech/nym/pull/4679): Follow up to [\#4678](https://github.com/nymtech/nym/pull/4678) where all code interacting with it is removed.
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. Reviewed the project to identify all references to `nym-network-statistics`.
|
||||
2. Removed all code and dependencies associated with `nym-network-statistics`.
|
||||
3. Ensured that no references to `nym-network-statistics` remain in the codebase, including comments, imports, and configuration files.
|
||||
4. Compiled the project to check for any issues or warnings.
|
||||
5. Ran all tests to ensure the removal did not affect the functionality of the project.
|
||||
~~~
|
||||
|
||||
* [Create `UserAgent` that can be passed from the binary to the `nym-api` client](https://github.com/nymtech/nym/pull/4677):
|
||||
- Support setting `UserAgent` for the validator client
|
||||
- Support setting `UserAgent` in the SDK `MixnetClient`
|
||||
- Set `UserAgent` when getting the list of gateways and topology in
|
||||
- `nym-client`
|
||||
- `nym-socks5-client`
|
||||
- Standalone `ip-packet-router`
|
||||
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Used the nym-vpn-cli to test this, and we can visibly see the `UserAgent`, no issues with the comments mentioned above.
|
||||
|
||||
Example of the user agent sent:
|
||||
`nym-client/1.1.36/x86_64-unknown-linux-gnu/e18bb70`
|
||||
|
||||
<img width="1435" alt="image" src="https://github.com/nymtech/nym/assets/60836166/5d4cc76f-84e6-45cb-9102-adc2b58a25d9">
|
||||
|
||||
Connected with no problems
|
||||
~~~
|
||||
|
||||
* [Add `authenticator`](https://github.com/nymtech/nym/pull/4667)
|
||||
|
||||
### Bugfix
|
||||
|
||||
* [`Node_api_check.py` CLI looked over roles on blacklisted nodes](https://github.com/nymtech/nym/pull/4687): Removing/correcting this redundant function which results in unwanted error print, will resolve in the program not looking up the `roles` endpoint for blacklisted GWs, instead just ignores the role description and still return all other endpoints.
|
||||
|
||||
### Operators Guide updates
|
||||
|
||||
* [Create a guide to backup and restore `nym-node`](https://nymtech.net/operators/nodes/maintenance.html#backup-a-node), PR [\#4720](https://github.com/nymtech/nym/pull/4720)
|
||||
* [Add manual IPv6 ifup/down network configuration](https://nymtech.net/operators/troubleshooting/vps-isp.html#network-configuration), PR [\#4651](https://github.com/nymtech/nym/pull/4651)
|
||||
* [Extend ISP list](https://nymtech.net/operators/legal/isp-list.html)
|
||||
* [Add SSL cert bot block to WSS setup](https://nymtech.net/operators/nodes/proxy-configuration.html#web-secure-socket-setup), [PR here](https://github.com/nymtech/nym/commits/develop/): WSS setup fully works!
|
||||
* [Correct `HTTP API port` in bonding page](https://nymtech.net/operators/nodes/bonding.html#bond-via-the-desktop-wallet-recommended) , [PR \#4707](https://github.com/nymtech/nym/pull/4707): Change `HTTP API port` to `8080` on every `nym-node` by opening `config.toml` and making sure that your binding addresses and ports are as in the block below. Then go to desktop wallet and open the box called `Show advanced options` and make sure all your ports are set correctly (usually this means to change `HTTP api port` to `8080` for `mixnode` mode).
|
||||
~~~admonish example collapsible=true title='snap of binding addresses and ports in `config.toml`'
|
||||
```toml
|
||||
[host]
|
||||
public_ips = [
|
||||
'<YOUR_PUBLIC_IPv4>'
|
||||
]
|
||||
|
||||
[mixnet]
|
||||
bind_address = '0.0.0.0:1789'
|
||||
|
||||
[http]
|
||||
bind_address = '0.0.0.0:8080'
|
||||
|
||||
[mixnode]
|
||||
[mixnode.verloc]
|
||||
bind_address = '0.0.0.0:1790'
|
||||
|
||||
[entry_gateway]
|
||||
bind_address = '0.0.0.0:9000'
|
||||
```
|
||||
~~~
|
||||
|
||||
* [Comment our deprecated node pages in `/docs`](https://github.com/nymtech/nym/pull/4727)
|
||||
- Fixes [issue \#4632](https://github.com/nymtech/nym/issues/4632)
|
||||
* [Remove redundant syntax from the setup guide](https://github.com/nymtech/nym/pull/4682)
|
||||
|
||||
---
|
||||
|
||||
## `v2024.7-doubledecker`
|
||||
|
||||
- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.7-doubledecker)
|
||||
@@ -42,7 +252,7 @@ This page displays a full list of all the changes during our release cycle from
|
||||
|
||||
### Features
|
||||
|
||||
- [Remove the `nym-mixnode` and `nym-gateway` binaries from the CI upload builds action](https://github.com/nymtech/nym/pull/4693):
|
||||
- [Remove the `nym-mixnode` and `nym-gateway` binaries from the CI upload builds action](https://github.com/nymtech/nym/pull/4693)
|
||||
- [Add an early return in `parse_raw_str_logs` for empty raw log strings.](https://github.com/nymtech/nym/pull/4686): This accommodates for the v50 + chain upgrade.
|
||||
- [Bump braces from `3.0.2` to `3.0.3` in `/wasm/mix-fetch/internal-dev`](https://github.com/nymtech/nym/pull/4672): Version update of [braces](https://github.com/micromatch/braces)
|
||||
- [Bump braces from `3.0.2` to `3.0.3` in `/clients/native/examples/js-examples/websocket`](https://github.com/nymtech/nym/pull/4663): Version update of [braces](https://github.com/micromatch/braces).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user