Compare commits

..

59 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu eaa0f055af Move const to wireguard types 2024-08-06 14:56:33 +00:00
Bogdan-Ștefan Neacşu ad507c6a12 Use a more proper timeout value 2024-08-06 14:56:24 +00:00
Jon Häggblad e5d68a5e7f Don't set NYM_VPN_API to default (#4740) 2024-07-31 11:45:36 +02:00
Tommy Verrall 7ddd819ff3 Merge pull request #4739 from nymtech/tommy/add-wireguard-publish-binaries
Update publish-nym-binaries.yml
2024-07-30 11:41:43 +02:00
Tommy Verrall 83b416d12d amend build all binaries command 2024-07-30 11:38:07 +02:00
Tommy Verrall b9c775c3ae Update publish-nym-binaries.yml
add wireguard to builds
2024-07-30 11:27:50 +02:00
mx 6f669866e9 Max/doc link fix (#4737)
* fix broken link in header dropdown

---------

Co-authored-by: serinko <97586125+serinko@users.noreply.github.com>
Co-authored-by: mfahampshire <mfahampshire@pm.me>
2024-07-30 08:48:14 +00:00
Tommy Verrall 4e61fefec8 Merge pull request #4736 from nymtech/jon/nym-vpn-api-env
Add NYM_VPN_API to network config
2024-07-30 10:04:55 +02:00
Jon Häggblad b4514ecd83 update for wallet 2024-07-29 23:50:52 +02:00
Jon Häggblad 4f6902525e restore explorer-api 2024-07-29 23:30:09 +02:00
Jon Häggblad 881139e36f Add nym_vpn_api_url 2024-07-29 23:30:09 +02:00
Jon Häggblad 32e2557456 Fix tokio error in 1.39 (#4730)
* Fix tokio error in 1.39

Fix the error generated by tokio 1.39

72 | /             tokio::select! {
173 | |                 daemon_res = &mut fused_runner => {
174 | |                     warn!("the daemon has terminated by itself - was it a short lived command?");
175 | |                     let exit_status = daemon_res?;
...   |
179 | |                 event = &mut self.upgrade_plan_watcher.next() => {
    | |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use
...   |
201 | |                 }
202 | |             }
    | |             -
    | |             |
    | |_____________temporary value is freed at the end of this statement
    |               borrow later used here

and

62 | /         select! {
63 | |             connection_message = &mut mix_receiver.next() => {
   | |                                       ^^^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use
64 | |                 if let Some(connection_message) = connection_message {
65 | |                     if deal_with_message(connection_message, &mut writer, &local_destination_address, &remote_source_address, connection_id).await {
...  |
86 | |             }
87 | |         }
   | |         -
   | |         |
   | |_________temporary value is freed at the end of this statement
   |           borrow later used here

* Upgrade to tokio 1.39.1

* Simpler attempt

* Revert fixes and instead bump to tokio 1.39.2

* update

* bump msrv for nym-node-tester-wasm
2024-07-29 20:45:26 +02:00
Jon Häggblad 8b44820e51 Re-export RecipientFormattingError in nym sdk (#4735) 2024-07-29 19:20:26 +02:00
import this 5e6417f837 clarify syntax - PR ready (#4734) 2024-07-29 13:51:31 +00:00
Jędrzej Stuczyński dfb2a2f380 Merge pull request #4716 from nymtech/feature/vesting-purge-plus-ranged-cost-params
Feature/vesting purge plus ranged cost params
2024-07-26 18:01:29 +01:00
fmtabbara d1de751850 fix ci 2024-07-26 17:28:24 +01:00
Jędrzej Stuczyński ecee6ca863 chore: cargo fmt 2024-07-26 15:08:38 +01:00
fmtabbara 31ea3f92e2 update bonding oc and pm validation 2024-07-26 15:08:38 +01:00
fmtabbara f19c934fae finish migrate vested bonded node work 2024-07-26 15:08:38 +01:00
Mark Sinclair 10d6f20de7 wip: add profit margin and cost params into validation from mixnet contract via MainContext 2024-07-26 15:08:38 +01:00
Mark Sinclair 96b33bfbe4 Regenerate TS types 2024-07-26 15:08:38 +01:00
Mark Sinclair 444c787d0a Add kind prop to vesting contract migration modal 2024-07-26 15:08:38 +01:00
Mark Sinclair 61fcd4ac69 Dialog and mock for migrating vesting contract delegations 2024-07-26 15:08:38 +01:00
Jędrzej Stuczyński b76802e6eb exposed tauri operations for vesting migrations 2024-07-26 15:08:38 +01:00
Mark Sinclair 7d351029a4 Fix dependency issue 2024-07-26 15:08:37 +01:00
Jędrzej Stuczyński 4ee445c119 cargo fmt 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 61ddeea495 fixed post-rebasing imports 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 7b802033b3 missing test fixture 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński b484f47369 fix nym-cli 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 66979df10c update contract schema 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 82f161fb91 added associated [hacky] wallet types 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 9d0fd681d4 introducing allowed range of operator interval operating cost 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński c2ab47a102 profit margin range validation 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 8704c21621 normalise node's profit margin during rewarding 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 03ffb25bf9 introduced the concept of allowed profit margin ranges 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 70db1ad062 fixed vesting contract tests 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 952ed9b642 fixed wallet vesting-related tests 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński f57fe79686 updated contract schema 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 9179f1c351 exposed migration commands to nym-cli + clippy 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński c4f7a1e09d implemented migration into non-vesting mixnodes/delegations 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 701012a968 ensure no pending proxy events when migrating 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 9767f72b8f removed all on_behalf mixnet contract methods 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 7b10d92ca4 Merge pull request #4731 from nymtech/chore/1.80-lints
chore: fix 1.80 lint issues
2024-07-26 11:51:23 +01:00
Jędrzej Stuczyński 2c6e5eb673 cherry-pick: fix build issues 2024-07-26 11:11:52 +01:00
Jon Häggblad 02fde4e530 Handle clients with different versions in IPR (#4723)
* Add signable_request function

* Export key type in function signature

* Cargo.lock

* Track client version and respond using it

* Internally use v7 and then down convert if needed

* Local response type

* Streamline

* Strong type for client version

* Remove commented out code

* rustfmt

* Ignore sign verification fail for v6
2024-07-24 15:35:59 +02:00
import this cc25fc1f32 [DOCs/operators]: Changelog for v2024.8 wispa & guide syntax edits (#4728)
* changelog for release v2024.8-wispa

* clarify syntax

* typo fix
2024-07-24 12:38:25 +00:00
benedetta davico c971e486b5 Merge pull request #4726 from nymtech/release/2024.8-wispa
Release/2024.8 wispa into develop
2024-07-24 12:48:57 +02:00
import this 96a9eb6f6a [DOCs/docs]: Commnet out extra stubs (#4727)
* commnet out stubs

* fix broken links - ready to merge
2024-07-24 11:58:14 +02:00
benedetta davico 9eeb61ea0a Merge branch 'develop' into release/2024.8-wispa 2024-07-24 10:56:03 +02:00
John Smith 08042c61ad [DOCs/operators]: Update troubleshooting/vps-isp.md with manual IPv6 configuration (#4651)
* Update vps-isp.md

Added an extra diagnostic step, which helped me to debug lack of routability.

* Update vps-isp.md

Implementing serinko's comments

* Update vps-isp.md

Changed possibly to possible and added how to find IPv6 Gateway.

* Update vps-isp.md

Fixed ifup/ifdown link
2024-07-24 08:53:49 +00:00
Stefano Piermatteo 36c74f30e5 [DOCs/operators]: Syntax fix in setup.md (#4682) 2024-07-24 08:37:33 +00:00
Tommy Verrall 4956d13bdc fix conflicts 2024-07-23 17:32:49 +02:00
benedettadavico d9f6c0723e updating versions 2024-07-23 15:37:04 +02:00
Tommy Verrall 52f5656190 Merge pull request #4721 from nymtech/jon/node-role-default
Default construct NodeRole
2024-07-22 15:09:27 +02:00
Jon Häggblad 21cd90f238 Default construct NodeRole for backwards compatibility 2024-07-22 14:59:18 +02:00
Bogdan-Ștefan Neacşu 72e243042e Add upgrades to nym-node for authenticator changes (#4703)
* Add iterative upgrades to nym-node

* Authenticator correct configuration

* Add info log

* Enable auth opts on entry gw

* Move ephemeral config from exit_gateway

* Fix fmt

* Fix clippy

* Pass custom transceiver for authenticator

* Fix non-linux build

* Feature gate wg_api

* Change naming from semver to simple incremental

* Move opts unwrap inside the mutable function

* Remove unneeded authenticator_description
2024-07-12 12:02:22 +02:00
benedettadavico c2ad4e5bb4 Update changelog and bump versions 2024-07-10 11:01:20 +02:00
Tommy Verrall 5f7f5ef92d Merge pull request #4699 from nymtech/release/2024.7-doubledecker
Release/2024.7 doubledecker
2024-07-10 10:46:03 +02:00
benedetta davico 008afe7a85 Merge pull request #4676 from nymtech/release/2024.6-chomp
Release/2024.6-chomp to master
2024-06-27 11:47:55 +02:00
158 changed files with 3151 additions and 3890 deletions
+6 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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![
@@ -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);
}
@@ -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)
}
@@ -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 {
@@ -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
View File
@@ -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
+17
View File
@@ -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)]
+2
View File
@@ -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() {
+1
View File
@@ -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";
-1
View File
@@ -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,
+4
View File
@@ -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]
+2
View File
@@ -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,
+3 -1
View File
@@ -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",
+7
View File
@@ -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;
+13 -6
View File
@@ -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"
+92
View File
@@ -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"
+56 -117
View File
@@ -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),
},
},
};
+2 -73
View File
@@ -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)
}
}
+13 -154
View File
@@ -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(())
+15 -285
View File
@@ -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(())
+28 -253
View File
@@ -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
}
)
}
}
+41 -441
View File
@@ -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();
+4 -10
View File
@@ -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 {
+8 -10
View File
@@ -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);
+17 -57
View File
@@ -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 {
+1
View File
@@ -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
+1 -14
View File
@@ -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(())
+36 -576
View File
@@ -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,
}
)
}
}
}
+49
View File
@@ -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(())
}
+42 -194
View File
@@ -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)]
+42 -173
View File
@@ -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();
+18 -278
View File
@@ -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", &[]);
+95
View File
@@ -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": {
+48
View File
@@ -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": {
+6 -74
View File
@@ -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>;
}
+32 -2
View File
@@ -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)
}
}
+54 -296
View File
@@ -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]
+1 -2
View File
@@ -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>
-4
View File
@@ -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:
![Nym Platform](../images/nym-platform-dark.png)
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.
+10 -11
View File
@@ -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]&#40;https://nymtech.net/operators/nodes/nymvisor-upgrade.html&#41;: `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.
+4 -6
View File
@@ -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.
```
+1 -2
View File
@@ -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>
+211 -1
View File
@@ -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