Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9eeb61ea0a | |||
| 36c74f30e5 | |||
| fd1d437211 | |||
| 4956d13bdc | |||
| 6478736654 | |||
| d9f6c0723e | |||
| f86050d916 | |||
| 52f5656190 | |||
| 21cd90f238 | |||
| 4e51188d35 | |||
| 22eb199936 | |||
| a2fc1bbc96 | |||
| 621599692f | |||
| 3ad3837c87 | |||
| 4d745e3b7e | |||
| 3a053b8dd6 | |||
| 1f144690da | |||
| eec1895acc | |||
| 72e243042e | |||
| 99864cb7a9 | |||
| 3155728119 | |||
| c253b22f69 | |||
| 66f3a3e9a8 | |||
| c2ad4e5bb4 | |||
| 5f7f5ef92d | |||
| 008afe7a85 |
+24
-1
@@ -4,6 +4,30 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.8-wispa] (2024-07-10)
|
||||
|
||||
- add event parsing to support cosmos_sdk > 0.50 ([#4697])
|
||||
- Fix NR config compatibility ([#4690])
|
||||
- Remove UserAgent constructor since it's weakly typed ([#4689])
|
||||
- [bugfix]: Node_api_check CLI looked over roles on blacklisted nodes ([#4687])
|
||||
- Add mixnodes to self describing api cache ([#4684])
|
||||
- Move and whole bump of crates to workspace and upgrade some ([#4680])
|
||||
- Remove code that refers to removed nym-network-statistics ([#4679])
|
||||
- Remove nym-network-statistics ([#4678])
|
||||
- Create UserAgent that can be passed from the binary to the nym api client ([#4677])
|
||||
- Add authenticator ([#4667])
|
||||
|
||||
[#4697]: https://github.com/nymtech/nym/pull/4697
|
||||
[#4690]: https://github.com/nymtech/nym/pull/4690
|
||||
[#4689]: https://github.com/nymtech/nym/pull/4689
|
||||
[#4687]: https://github.com/nymtech/nym/pull/4687
|
||||
[#4684]: https://github.com/nymtech/nym/pull/4684
|
||||
[#4680]: https://github.com/nymtech/nym/pull/4680
|
||||
[#4679]: https://github.com/nymtech/nym/pull/4679
|
||||
[#4678]: https://github.com/nymtech/nym/pull/4678
|
||||
[#4677]: https://github.com/nymtech/nym/pull/4677
|
||||
[#4667]: https://github.com/nymtech/nym/pull/4667
|
||||
|
||||
## [2024.7-doubledecker] (2024-07-04)
|
||||
|
||||
- Add an early return in `parse_raw_str_logs` for empty raw log strings. ([#4686])
|
||||
@@ -513,7 +537,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#3187]: https://github.com/nymtech/nym/issues/3187
|
||||
[#3203]: https://github.com/nymtech/nym/pull/3203
|
||||
[#3199]: https://github.com/nymtech/nym/pull/3199
|
||||
>>>>>>> master
|
||||
|
||||
## [v1.1.13] (2023-03-15)
|
||||
|
||||
|
||||
Generated
+10
-8
@@ -2093,7 +2093,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.36"
|
||||
version = "1.1.37"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.5.4",
|
||||
@@ -3849,7 +3849,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.40"
|
||||
version = "1.1.41"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4057,7 +4057,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.38"
|
||||
version = "1.1.39"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -4136,7 +4136,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 +4950,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.38"
|
||||
version = "1.1.39"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -5001,7 +5001,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
@@ -5264,7 +5264,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.37"
|
||||
version = "1.1.38"
|
||||
dependencies = [
|
||||
"bs58 0.5.1",
|
||||
"clap 4.5.4",
|
||||
@@ -5742,6 +5742,7 @@ name = "nym-wireguard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"defguard_wireguard_rs",
|
||||
"ip_network",
|
||||
@@ -5750,6 +5751,7 @@ dependencies = [
|
||||
"nym-network-defaults",
|
||||
"nym-task",
|
||||
"nym-wireguard-types",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"x25519-dalek",
|
||||
@@ -5777,7 +5779,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
|
||||
@@ -52,7 +52,7 @@ References for developers:
|
||||
|
||||
You can chat to us in two places:
|
||||
* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat)
|
||||
* The various developer channels on [Discord](https://discord.gg/nymproject)
|
||||
* The various developer channels on [Discord](https://nymtech.net/go/discord)
|
||||
|
||||
### Tokenomics & Rewards
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.37"
|
||||
version = "1.1.38"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.37"
|
||||
version = "1.1.38"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::{GatewayClient, InitMessage};
|
||||
use nym_wireguard_types::{GatewayClient, InitMessage, PeerPublicKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
@@ -57,6 +57,19 @@ impl AuthenticatorRequest {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
@@ -67,4 +80,5 @@ impl AuthenticatorRequest {
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(GatewayClient),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::registration::RegistrationData;
|
||||
use nym_wireguard_types::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
@@ -33,10 +33,31 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(reply_to: Recipient, request_id: u64) -> Self {
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registred_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
@@ -64,6 +85,7 @@ impl AuthenticatorResponse {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +94,7 @@ impl AuthenticatorResponse {
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -85,4 +108,12 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
}
|
||||
|
||||
@@ -683,24 +683,6 @@ 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,
|
||||
@@ -946,12 +928,6 @@ 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,7 +437,6 @@ 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)]
|
||||
@@ -561,9 +560,6 @@ 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,42 +0,0 @@
|
||||
// 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,7 +7,6 @@ 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;
|
||||
@@ -36,6 +35,4 @@ 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,7 +96,6 @@ 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![
|
||||
@@ -112,7 +111,6 @@ 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![
|
||||
|
||||
+13
-2
@@ -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;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -39,6 +39,10 @@ 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,
|
||||
}
|
||||
@@ -70,8 +74,15 @@ 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, coin, gateway);
|
||||
let payload = construct_gateway_bonding_sign_payload(nonce, address, proxy, coin, gateway);
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -5,21 +5,33 @@ 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 = client
|
||||
.create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to 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")
|
||||
};
|
||||
|
||||
info!("Family creation result: {:?}", res);
|
||||
}
|
||||
|
||||
+16
-3
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::DataWrapper;
|
||||
use crate::utils::{account_id_to_cw_addr, 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;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -18,6 +18,10 @@ 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)]
|
||||
@@ -64,9 +68,18 @@ 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, args.member.to_base58_string());
|
||||
let payload = construct_family_join_permit(nonce, head, proxy, args.member.to_base58_string());
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ 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 {
|
||||
@@ -15,6 +16,10 @@ 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,
|
||||
@@ -25,10 +30,17 @@ pub async fn join_family(args: Args, client: SigningClient) {
|
||||
|
||||
let family_head = FamilyHead::new(args.family_head.to_base58_string());
|
||||
|
||||
let res = client
|
||||
.join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family");
|
||||
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")
|
||||
};
|
||||
|
||||
info!("Family join result: {:?}", res);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,17 @@ 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) {
|
||||
@@ -20,10 +25,17 @@ pub async fn leave_family(args: Args, client: SigningClient) {
|
||||
|
||||
let family_head = FamilyHead::new(args.family_head.to_base58_string());
|
||||
|
||||
let res = client
|
||||
.leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family");
|
||||
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")
|
||||
};
|
||||
|
||||
info!("Family leave result: {:?}", res);
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
+13
-2
@@ -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;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::CosmWasmCoin;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -52,6 +52,10 @@ 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,
|
||||
}
|
||||
@@ -96,9 +100,16 @@ 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, coin, mixnode, cost_params);
|
||||
construct_mixnode_bonding_sign_payload(nonce, address, proxy, coin, mixnode, cost_params);
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ 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;
|
||||
@@ -53,6 +52,4 @@ 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,6 +218,7 @@ where
|
||||
#[derive(Serialize)]
|
||||
pub struct ContractMessageContent<T> {
|
||||
pub sender: Addr,
|
||||
pub proxy: Option<Addr>,
|
||||
pub funds: Vec<Coin>,
|
||||
pub data: T,
|
||||
}
|
||||
@@ -232,17 +233,25 @@ where
|
||||
}
|
||||
|
||||
impl<T> ContractMessageContent<T> {
|
||||
pub fn new(sender: Addr, funds: Vec<Coin>, data: T) -> Self {
|
||||
pub fn new(sender: Addr, proxy: Option<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,6 +65,7 @@ impl Delegation {
|
||||
cumulative_reward_ratio: Decimal,
|
||||
amount: Coin,
|
||||
height: u64,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
amount.amount <= TOKEN_SUPPLY,
|
||||
@@ -77,7 +78,7 @@ impl Delegation {
|
||||
cumulative_reward_ratio,
|
||||
amount,
|
||||
height,
|
||||
proxy: None,
|
||||
proxy,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,11 +76,21 @@ 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),
|
||||
@@ -229,17 +239,6 @@ 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,
|
||||
}
|
||||
|
||||
impl MixnetContractError {
|
||||
|
||||
@@ -103,6 +103,7 @@ 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
|
||||
|
||||
@@ -162,6 +163,7 @@ 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,
|
||||
@@ -169,34 +171,58 @@ 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, mix_id: MixId) -> Event {
|
||||
pub fn new_delegation_on_unbonded_node_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<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, amount: &Coin, mix_id: MixId) -> Event {
|
||||
pub fn new_pending_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<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, amount: Coin, mix_id: MixId) -> Event {
|
||||
pub fn new_withdraw_operator_reward_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<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, amount: Coin, mix_id: MixId) -> Event {
|
||||
pub fn new_withdraw_delegator_reward_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<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())
|
||||
}
|
||||
@@ -252,43 +278,59 @@ pub fn new_pending_rewarding_params_update_event(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_undelegation_event(created_at: BlockHeight, delegator: &Addr, mix_id: MixId) -> Event {
|
||||
pub fn new_undelegation_event(
|
||||
created_at: BlockHeight,
|
||||
delegator: &Addr,
|
||||
proxy: &Option<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, mix_id: MixId) -> Event {
|
||||
pub fn new_pending_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<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,
|
||||
@@ -299,6 +341,7 @@ 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())
|
||||
}
|
||||
@@ -337,6 +380,7 @@ 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 {
|
||||
@@ -344,33 +388,43 @@ 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, update: &GatewayConfigUpdate) -> Event {
|
||||
pub fn new_gateway_config_update_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<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,6 +3,7 @@
|
||||
|
||||
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};
|
||||
@@ -83,10 +84,10 @@ impl FamilyHead {
|
||||
}
|
||||
|
||||
impl Family {
|
||||
pub fn new(head: FamilyHead, label: String) -> Self {
|
||||
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: String) -> Self {
|
||||
Family {
|
||||
head,
|
||||
proxy: None,
|
||||
proxy: proxy.map(|p| p.to_string()),
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +55,19 @@ pub struct GatewayBond {
|
||||
}
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn new(pledge_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
|
||||
pub fn new(
|
||||
pledge_amount: Coin,
|
||||
owner: Addr,
|
||||
block_height: u64,
|
||||
gateway: Gateway,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
GatewayBond {
|
||||
pledge_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
proxy: None,
|
||||
proxy,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -518,6 +518,7 @@ impl MixNodeBond {
|
||||
original_pledge: Coin,
|
||||
layer: Layer,
|
||||
mix_node: MixNode,
|
||||
proxy: Option<Addr>,
|
||||
bonding_height: u64,
|
||||
) -> Self {
|
||||
MixNodeBond {
|
||||
@@ -526,7 +527,7 @@ impl MixNodeBond {
|
||||
original_pledge,
|
||||
layer,
|
||||
mix_node,
|
||||
proxy: None,
|
||||
proxy,
|
||||
bonding_height,
|
||||
is_unbonding: false,
|
||||
}
|
||||
|
||||
@@ -269,12 +269,6 @@ pub enum ExecuteMsg {
|
||||
owner: String,
|
||||
},
|
||||
|
||||
// vesting migration:
|
||||
MigrateVestedMixNode {},
|
||||
MigrateVestedDelegation {
|
||||
mix_id: MixId,
|
||||
},
|
||||
|
||||
// testing-only
|
||||
#[cfg(feature = "contract-testing")]
|
||||
TestingResolveAllPendingEvents {
|
||||
@@ -387,9 +381,6 @@ 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,7 +38,6 @@ 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,
|
||||
@@ -56,7 +55,6 @@ 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,
|
||||
@@ -111,23 +109,6 @@ impl PendingEpochEventKind {
|
||||
kind: self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_delegate(owner: Addr, mix_id: MixId, amount: Coin) -> Self {
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_undelegate(owner: Addr, mix_id: MixId) -> Self {
|
||||
PendingEpochEventKind::Undelegate {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
|
||||
|
||||
+1
@@ -47,6 +47,7 @@ impl SimulatedNode {
|
||||
self.rewarding_details.total_unit_reward,
|
||||
delegation,
|
||||
42,
|
||||
None,
|
||||
);
|
||||
|
||||
self.delegations.insert(delegator, delegation);
|
||||
|
||||
@@ -37,12 +37,13 @@ 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, vec![pledge], payload);
|
||||
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
|
||||
|
||||
SignableMessage::new(nonce, content)
|
||||
}
|
||||
@@ -67,11 +68,12 @@ 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, vec![pledge], payload);
|
||||
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
|
||||
|
||||
SignableMessage::new(nonce, content)
|
||||
}
|
||||
@@ -80,14 +82,17 @@ 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, member_node: IdentityKey) -> Self {
|
||||
pub fn new(family_head: FamilyHead, proxy: Option<Addr>, member_node: IdentityKey) -> Self {
|
||||
Self {
|
||||
family_head,
|
||||
proxy,
|
||||
member_node,
|
||||
}
|
||||
}
|
||||
@@ -102,9 +107,10 @@ 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, member_node);
|
||||
let payload = FamilyJoinPermit::new(family_head, proxy, 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
|
||||
|
||||
@@ -167,11 +167,3 @@ 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,14 +136,6 @@ 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 {
|
||||
@@ -179,10 +171,6 @@ impl ExecuteMsg {
|
||||
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
|
||||
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
|
||||
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
|
||||
ExecuteMsg::TrackMigratedMixnode { .. } => "VestingExecuteMsg::TrackMigratedMixnode",
|
||||
ExecuteMsg::TrackMigratedDelegation { .. } => {
|
||||
"VestingExecuteMsg::TrackMigratedDelegation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ impl PendingEpochEventData {
|
||||
mix_id,
|
||||
amount,
|
||||
proxy,
|
||||
..
|
||||
} => Ok(PendingEpochEventData::Delegate {
|
||||
owner: owner.into_string(),
|
||||
mix_id,
|
||||
@@ -95,7 +94,6 @@ impl PendingEpochEventData {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy,
|
||||
..
|
||||
} => Ok(PendingEpochEventData::Undelegate {
|
||||
owner: owner.into_string(),
|
||||
mix_id,
|
||||
|
||||
@@ -27,12 +27,15 @@ pub type HmacSha256 = Hmac<Sha256>;
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub enum ClientMessage {
|
||||
Initial(InitMessage),
|
||||
Final(GatewayClient),
|
||||
Query(PeerPublicKey),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -60,6 +63,19 @@ pub struct RegistrationData {
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: u64,
|
||||
pub suspended: bool,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
||||
@@ -12,6 +12,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
defguard_wireguard_rs = { workspace = true }
|
||||
# The latest version on crates.io at the time of writing this (6.0.0) has a
|
||||
@@ -24,5 +25,6 @@ nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-wireguard-types = { path = "../wireguard-types" }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tokio-stream = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("peers in wireguard don't match with in-memory ")]
|
||||
PeerMismatch,
|
||||
|
||||
#[error("{0}")]
|
||||
Defguard(#[from] defguard_wireguard_rs::error::WireguardInterfaceError),
|
||||
}
|
||||
+25
-14
@@ -1,3 +1,6 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
// #![warn(clippy::pedantic)]
|
||||
// #![warn(clippy::expect_used)]
|
||||
@@ -6,13 +9,14 @@
|
||||
use dashmap::DashMap;
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi};
|
||||
use nym_crypto::asymmetric::encryption::KeyPair;
|
||||
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry};
|
||||
use peer_controller::PeerControlMessage;
|
||||
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry, PeerPublicKey};
|
||||
use peer_controller::PeerControlRequest;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver};
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||
|
||||
const WG_TUN_NAME: &str = "nymwg";
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod peer_controller;
|
||||
|
||||
pub struct WgApiWrapper {
|
||||
@@ -39,14 +43,14 @@ pub struct WireguardGatewayData {
|
||||
config: Config,
|
||||
keypair: Arc<KeyPair>,
|
||||
client_registry: Arc<GatewayClientRegistry>,
|
||||
peer_tx: mpsc::UnboundedSender<PeerControlMessage>,
|
||||
peer_tx: UnboundedSender<PeerControlRequest>,
|
||||
}
|
||||
|
||||
impl WireguardGatewayData {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
keypair: Arc<KeyPair>,
|
||||
) -> (Self, mpsc::UnboundedReceiver<PeerControlMessage>) {
|
||||
) -> (Self, UnboundedReceiver<PeerControlRequest>) {
|
||||
let (peer_tx, peer_rx) = mpsc::unbounded_channel();
|
||||
(
|
||||
WireguardGatewayData {
|
||||
@@ -75,20 +79,26 @@ impl WireguardGatewayData {
|
||||
let mut peer = Peer::new(Key::new(client.pub_key.to_bytes()));
|
||||
peer.allowed_ips
|
||||
.push(IpAddrMask::new(client.private_ip, 32));
|
||||
let msg = PeerControlMessage::AddPeer(peer);
|
||||
let msg = PeerControlRequest::AddPeer(peer);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
|
||||
pub fn remove_peer(&self, client: &GatewayClient) -> Result<(), Error> {
|
||||
let key = Key::new(client.pub_key().to_bytes());
|
||||
let msg = PeerControlMessage::RemovePeer(key);
|
||||
let msg = PeerControlRequest::RemovePeer(key);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
|
||||
pub fn query_bandwidth(&self, peer_public_key: PeerPublicKey) -> Result<(), Error> {
|
||||
let key = Key::new(peer_public_key.to_bytes());
|
||||
let msg = PeerControlRequest::QueryBandwidth(key);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WireguardData {
|
||||
pub inner: WireguardGatewayData,
|
||||
pub peer_rx: UnboundedReceiver<PeerControlMessage>,
|
||||
pub peer_rx: UnboundedReceiver<PeerControlRequest>,
|
||||
}
|
||||
|
||||
/// Start wireguard device
|
||||
@@ -96,6 +106,7 @@ pub struct WireguardData {
|
||||
pub async fn start_wireguard(
|
||||
task_client: nym_task::TaskClient,
|
||||
wireguard_data: WireguardData,
|
||||
control_tx: UnboundedSender<peer_controller::PeerControlResponse>,
|
||||
) -> Result<std::sync::Arc<WgApiWrapper>, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||
use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi};
|
||||
@@ -135,13 +146,13 @@ pub async fn start_wireguard(
|
||||
wg_api.configure_peer_routing(&[catch_all_peer])?;
|
||||
|
||||
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
|
||||
let mut controller = PeerController::new(wg_api.clone(), wireguard_data.peer_rx);
|
||||
let mut controller = PeerController::new(
|
||||
wg_api.clone(),
|
||||
interface_config.peers,
|
||||
wireguard_data.peer_rx,
|
||||
control_tx,
|
||||
);
|
||||
tokio::spawn(async move { controller.run(task_client).await });
|
||||
|
||||
Ok(wg_api)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub async fn start_wireguard() {
|
||||
todo!("WireGuard is currently only supported on Linux");
|
||||
}
|
||||
|
||||
@@ -1,91 +1,182 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use defguard_wireguard_rs::{
|
||||
host::{Host, Peer},
|
||||
key::Key,
|
||||
WGApi, WireguardInterfaceApi,
|
||||
};
|
||||
use chrono::{Timelike, Utc};
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, WireguardInterfaceApi};
|
||||
use nym_wireguard_types::registration::{RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY};
|
||||
use std::time::SystemTime;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::WgApiWrapper;
|
||||
|
||||
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60); // 1 hour
|
||||
// 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 PeerControlMessage {
|
||||
pub enum PeerControlRequest {
|
||||
AddPeer(Peer),
|
||||
RemovePeer(Key),
|
||||
QueryBandwidth(Key),
|
||||
}
|
||||
|
||||
pub enum PeerControlResponse {
|
||||
AddPeer {
|
||||
success: bool,
|
||||
},
|
||||
RemovePeer {
|
||||
success: bool,
|
||||
},
|
||||
QueryBandwidth {
|
||||
bandwidth_data: Option<RemainingBandwidthData>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct PeerController {
|
||||
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
|
||||
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
|
||||
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
active_peers: HashMap<Key, Peer>,
|
||||
suspended_peers: HashMap<Key, Peer>,
|
||||
last_seen_bandwidth: HashMap<Key, u64>,
|
||||
}
|
||||
|
||||
impl PeerController {
|
||||
pub fn new(
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
|
||||
peers: Vec<Peer>,
|
||||
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
|
||||
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
|
||||
) -> Self {
|
||||
let timeout_check_interval = tokio_stream::wrappers::IntervalStream::new(
|
||||
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
|
||||
);
|
||||
let active_peers = peers
|
||||
.into_iter()
|
||||
.map(|peer| (peer.public_key.clone(), peer))
|
||||
.collect();
|
||||
|
||||
PeerController {
|
||||
wg_api,
|
||||
peer_rx,
|
||||
request_rx,
|
||||
response_tx,
|
||||
timeout_check_interval,
|
||||
active_peers,
|
||||
suspended_peers: HashMap::new(),
|
||||
last_seen_bandwidth: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_stale_peers(wg_api: &WGApi, host: Host) {
|
||||
let current_timestamp = SystemTime::now();
|
||||
for (key, peer) in host.peers.iter() {
|
||||
if let Some(timestamp) = peer.last_handshake {
|
||||
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
|
||||
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
|
||||
if let Err(e) = wg_api.remove_peer(key) {
|
||||
log::error!("Could not remove stale peer: {:?}", e);
|
||||
} else {
|
||||
log::debug!("Removed stale peer {:?}", key);
|
||||
}
|
||||
}
|
||||
fn check_stale_peer(&self, peer: &Peer, current_timestamp: SystemTime) -> Result<bool, Error> {
|
||||
if let Some(timestamp) = peer.last_handshake {
|
||||
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
|
||||
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
|
||||
self.wg_api.inner.remove_peer(&peer.public_key)?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn check_suspend_peer(&mut self, peer: &Peer) -> Result<(), Error> {
|
||||
let prev_peer = self
|
||||
.active_peers
|
||||
.get(&peer.public_key)
|
||||
.ok_or(Error::PeerMismatch)?;
|
||||
let data_usage =
|
||||
(peer.rx_bytes + peer.tx_bytes).saturating_sub(prev_peer.rx_bytes + prev_peer.tx_bytes);
|
||||
if data_usage > BANDWIDTH_CAP_PER_DAY {
|
||||
self.wg_api.inner.remove_peer(&peer.public_key)?;
|
||||
let (moved_key, moved_peer) = self
|
||||
.active_peers
|
||||
.remove_entry(&peer.public_key)
|
||||
.ok_or(Error::PeerMismatch)?;
|
||||
self.suspended_peers.insert(moved_key, moved_peer);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_peers(&mut self) -> Result<(), Error> {
|
||||
// Add 10 seconds to cover edge cases. At worst, we give ten free seconds worth of bandwidth
|
||||
// by resetting the bandwidth twice
|
||||
let reset = Utc::now().num_seconds_from_midnight() as u64
|
||||
<= DEFAULT_PEER_TIMEOUT_CHECK.as_secs() + 10;
|
||||
|
||||
if reset {
|
||||
for (_, peer) in self.suspended_peers.drain() {
|
||||
self.wg_api.inner.configure_peer(&peer)?;
|
||||
}
|
||||
}
|
||||
let host = self.wg_api.inner.read_interface_data()?;
|
||||
self.last_seen_bandwidth = host
|
||||
.peers
|
||||
.iter()
|
||||
.map(|(key, peer)| (key.clone(), peer.rx_bytes + peer.tx_bytes))
|
||||
.collect();
|
||||
if reset {
|
||||
self.active_peers = host.peers;
|
||||
} else {
|
||||
let current_timestamp = SystemTime::now();
|
||||
for peer in host.peers.values() {
|
||||
if !self.check_stale_peer(peer, current_timestamp)? {
|
||||
self.check_suspend_peer(peer)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self, mut task_client: nym_task::TaskClient) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.timeout_check_interval.next() => {
|
||||
match self.wg_api.inner.read_interface_data() {
|
||||
Ok(host) => Self::remove_stale_peers(&self.wg_api.inner, host),
|
||||
Err(e) => { log::error!("Could not read peer data: {:?}", e); },
|
||||
if let Err(e) = self.check_peers() {
|
||||
log::error!("Error while periodically checking peers: {:?}", e);
|
||||
}
|
||||
}
|
||||
_ = task_client.recv() => {
|
||||
log::trace!("PeerController handler: Received shutdown");
|
||||
break;
|
||||
}
|
||||
msg = self.peer_rx.recv() => {
|
||||
msg = self.request_rx.recv() => {
|
||||
match msg {
|
||||
Some(PeerControlMessage::AddPeer(peer)) => {
|
||||
if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
|
||||
Some(PeerControlRequest::AddPeer(peer)) => {
|
||||
let success = if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
|
||||
log::error!("Could not configure peer: {:?}", e);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
self.active_peers.insert(peer.public_key.clone(), peer);
|
||||
true
|
||||
};
|
||||
self.response_tx.send(PeerControlResponse::AddPeer { success }).ok();
|
||||
}
|
||||
Some(PeerControlMessage::RemovePeer(peer_pubkey)) => {
|
||||
if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
|
||||
Some(PeerControlRequest::RemovePeer(peer_pubkey)) => {
|
||||
let success = if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
|
||||
log::error!("Could not remove peer: {:?}", e);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
self.active_peers.remove(&peer_pubkey);
|
||||
self.suspended_peers.remove(&peer_pubkey);
|
||||
true
|
||||
};
|
||||
self.response_tx.send(PeerControlResponse::RemovePeer { success }).ok();
|
||||
}
|
||||
Some(PeerControlRequest::QueryBandwidth(peer_pubkey)) => {
|
||||
let msg = if self.suspended_peers.contains_key(&peer_pubkey) {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: 0, suspended: true }) }
|
||||
} else if let Some(&consumed_bandwidth) = self.last_seen_bandwidth.get(&peer_pubkey) {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: BANDWIDTH_CAP_PER_DAY - consumed_bandwidth, suspended: false })}
|
||||
} else {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: None }
|
||||
};
|
||||
self.response_tx.send(msg).ok();
|
||||
}
|
||||
None => {
|
||||
log::trace!("PeerController [main loop]: stopping since channel closed");
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -12,33 +12,27 @@ 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,7 +26,6 @@ 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,
|
||||
@@ -70,15 +69,14 @@ 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())
|
||||
@@ -93,6 +91,7 @@ impl TestSetup {
|
||||
app,
|
||||
rng: test_rng(),
|
||||
mixnet_contract: contracts.mixnet_contract_address,
|
||||
vesting_contract: contracts.vesting_contract_address,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +99,10 @@ 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
|
||||
@@ -206,6 +209,7 @@ impl TestSetup {
|
||||
pub fn valid_mixnode_with_sig(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
cost_params: MixNodeCostParams,
|
||||
stake: Coin,
|
||||
) -> (MixNode, MessageSignature) {
|
||||
@@ -235,7 +239,8 @@ impl TestSetup {
|
||||
};
|
||||
|
||||
let payload = MixnodeBondingPayload::new(mixnode.clone(), cost_params);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), vec![stake], payload);
|
||||
let content =
|
||||
ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload);
|
||||
let sign_payload = SignableMixNodeBondingMsg::new(signing_nonce, content);
|
||||
let plaintext = sign_payload.to_plaintext().unwrap();
|
||||
let signature = keypair.private_key().sign(plaintext);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod decrease_mixnode_pledge;
|
||||
mod support;
|
||||
|
||||
@@ -1146,42 +1146,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
||||
@@ -1029,42 +1029,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
||||
@@ -118,6 +118,44 @@ 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(
|
||||
@@ -194,23 +232,62 @@ 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 {
|
||||
@@ -223,22 +300,52 @@ 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 {
|
||||
@@ -249,37 +356,16 @@ 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => {
|
||||
crate::rewards::transactions::try_withdraw_delegator_reward_on_behalf(
|
||||
deps, info, mix_id, owner,
|
||||
)
|
||||
}
|
||||
|
||||
// testing-only
|
||||
@@ -521,15 +607,13 @@ pub fn query(
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(
|
||||
mut deps: DepsMut<'_>,
|
||||
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
|
||||
|
||||
@@ -302,7 +302,10 @@ mod tests {
|
||||
|
||||
mod delegator_delegations {
|
||||
use super::*;
|
||||
use cosmwasm_std::Addr;
|
||||
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};
|
||||
|
||||
#[test]
|
||||
fn obeys_limits() {
|
||||
@@ -450,10 +453,25 @@ 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)
|
||||
@@ -472,6 +490,59 @@ mod tests {
|
||||
.delegations
|
||||
.into_iter()
|
||||
.all(|d| d.owner == Addr::unchecked("delegator35")));
|
||||
|
||||
let with_proxy_full =
|
||||
query_delegator_delegations_paged(test.deps(), with_proxy.into(), None, None)
|
||||
.unwrap();
|
||||
assert_eq!(with_proxy_full.delegations.len(), 125);
|
||||
|
||||
// all delegations have correct owner
|
||||
assert!(with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.all(|d| d.owner == Addr::unchecked(with_proxy)));
|
||||
|
||||
// and we have 100 delegations without proxy and 25 with
|
||||
let no_proxy = with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_none())
|
||||
.count();
|
||||
assert_eq!(no_proxy, 100);
|
||||
let proxy = with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_some())
|
||||
.count();
|
||||
assert_eq!(proxy, 25);
|
||||
|
||||
assert!(with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_some())
|
||||
.all(|d| d.proxy.as_ref().unwrap() == vesting_contract));
|
||||
|
||||
// now make sure that if we do it in paged manner, we'll get exactly the same result
|
||||
let per_page = Some(15);
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = query_delegator_delegations_paged(
|
||||
test.deps(),
|
||||
with_proxy.into(),
|
||||
start_after,
|
||||
per_page,
|
||||
)
|
||||
.unwrap();
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(with_proxy_full.delegations, delegations)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - 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, validate_delegation_stake};
|
||||
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
|
||||
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 mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_pending_delegation_event, new_pending_undelegation_event,
|
||||
@@ -19,6 +21,30 @@ 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)?;
|
||||
@@ -26,7 +52,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(
|
||||
info.funds,
|
||||
amount,
|
||||
contract_state.params.minimum_mixnode_delegation,
|
||||
contract_state.rewarding_denom,
|
||||
)?;
|
||||
@@ -41,9 +67,14 @@ 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(&info.sender, &delegation, mix_id);
|
||||
let cosmos_event = new_pending_delegation_event(&delegate, &proxy, &delegation, mix_id);
|
||||
|
||||
let epoch_event = PendingEpochEventKind::new_delegate(info.sender, mix_id, delegation);
|
||||
let epoch_event = PendingEpochEventKind::Delegate {
|
||||
owner: delegate,
|
||||
mix_id,
|
||||
amount: delegation,
|
||||
proxy,
|
||||
};
|
||||
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
|
||||
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
@@ -54,12 +85,35 @@ 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, &info.sender, None);
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &delegate, proxy.as_ref());
|
||||
|
||||
if storage::delegations()
|
||||
.may_load(deps.storage, storage_key)?
|
||||
@@ -67,15 +121,19 @@ pub(crate) fn try_remove_delegation_from_mixnode(
|
||||
{
|
||||
return Err(MixnetContractError::NoMixnodeDelegationFound {
|
||||
mix_id,
|
||||
address: info.sender.into_string(),
|
||||
proxy: None,
|
||||
address: delegate.into_string(),
|
||||
proxy: proxy.map(Addr::into_string),
|
||||
});
|
||||
}
|
||||
|
||||
// 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(&info.sender, mix_id);
|
||||
let cosmos_event = new_pending_undelegation_event(&delegate, &proxy, mix_id);
|
||||
|
||||
let epoch_event = PendingEpochEventKind::new_undelegate(info.sender, mix_id);
|
||||
let epoch_event = PendingEpochEventKind::Undelegate {
|
||||
owner: delegate,
|
||||
mix_id,
|
||||
proxy,
|
||||
};
|
||||
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
|
||||
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
@@ -93,7 +151,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, Addr, Decimal};
|
||||
use cosmwasm_std::{coin, Decimal};
|
||||
use mixnet_contract_common::{EpochState, EpochStatus};
|
||||
|
||||
#[test]
|
||||
@@ -310,17 +368,65 @@ 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::new_delegate(Addr::unchecked(owner), mix_id, amount1,)
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked(owner),
|
||||
mix_id,
|
||||
amount: amount1,
|
||||
proxy: None
|
||||
}
|
||||
);
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,5 +573,40 @@ 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::Deps;
|
||||
use cosmwasm_std::{Addr, Deps};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::{construct_family_join_permit, IdentityKeyRef};
|
||||
@@ -13,6 +13,7 @@ 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> {
|
||||
@@ -31,7 +32,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, member.to_owned());
|
||||
let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned());
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage::{
|
||||
@@ -7,20 +7,41 @@ 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;
|
||||
use cosmwasm_std::{DepsMut, MessageInfo, Response};
|
||||
use crate::support::helpers::{ensure_bonded, ensure_sent_by_vesting_contract};
|
||||
use cosmwasm_std::{Addr, 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(crate) fn try_create_family(
|
||||
pub 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, &info.sender)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -39,19 +60,43 @@ pub(crate) fn try_create_family(
|
||||
return Err(MixnetContractError::FamilyWithLabelExists(label));
|
||||
}
|
||||
|
||||
let family = Family::new(family_head, label);
|
||||
let family = Family::new(family_head, proxy, label);
|
||||
save_family(&family, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub(crate) fn try_join_family(
|
||||
pub 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, &info.sender)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -71,6 +116,7 @@ pub(crate) fn try_join_family(
|
||||
verify_family_join_permit(
|
||||
deps.as_ref(),
|
||||
family_head.clone(),
|
||||
proxy,
|
||||
existing_bond.identity(),
|
||||
join_permit,
|
||||
)?;
|
||||
@@ -82,13 +128,33 @@ pub(crate) fn try_join_family(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub(crate) fn try_leave_family(
|
||||
pub 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, &info.sender)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -112,13 +178,32 @@ pub(crate) fn try_leave_family(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub(crate) fn try_head_kick_member(
|
||||
pub fn try_head_kick_member(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let head_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
_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)?;
|
||||
|
||||
// make sure we're still in the mixnet
|
||||
ensure_bonded(&head_bond)?;
|
||||
@@ -236,7 +321,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);
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
@@ -260,7 +345,7 @@ mod test {
|
||||
);
|
||||
|
||||
let new_join_permit =
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
@@ -288,4 +373,189 @@ 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,6 +12,7 @@ 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,
|
||||
@@ -21,7 +22,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, pledge, gateway);
|
||||
let msg = construct_gateway_bonding_sign_payload(nonce, sender, proxy, pledge, gateway);
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::helpers::must_get_gateway_bond_by_owner;
|
||||
@@ -6,8 +6,10 @@ 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, validate_pledge};
|
||||
use cosmwasm_std::{BankMsg, DepsMut, Env, MessageInfo, Response};
|
||||
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 mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_gateway_bonding_event, new_gateway_config_update_event, new_gateway_unbonding_event,
|
||||
@@ -15,28 +17,72 @@ 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;
|
||||
|
||||
// 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 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(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
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(info.funds, minimum_pledge)?;
|
||||
let pledge = validate_pledge(pledge, minimum_pledge)?;
|
||||
|
||||
// if the client has an active bonded mixnode or gateway, don't allow bonding
|
||||
ensure_no_existing_bond(&info.sender, deps.storage)?;
|
||||
ensure_no_existing_bond(&owner, 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 != info.sender {
|
||||
if existing_bond.owner != owner {
|
||||
return Err(MixnetContractError::DuplicateGateway {
|
||||
owner: existing_bond.owner,
|
||||
});
|
||||
@@ -46,62 +92,105 @@ 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(),
|
||||
info.sender.clone(),
|
||||
owner.clone(),
|
||||
proxy.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, info.sender.clone())?;
|
||||
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
|
||||
|
||||
let gateway_identity = gateway.identity_key.clone();
|
||||
let bond = GatewayBond::new(
|
||||
pledge.clone(),
|
||||
info.sender.clone(),
|
||||
owner.clone(),
|
||||
env.block.height,
|
||||
gateway,
|
||||
proxy.clone(),
|
||||
);
|
||||
|
||||
storage::gateways().save(deps.storage, bond.identity(), &bond)?;
|
||||
|
||||
Ok(Response::new().add_event(new_gateway_bonding_event(
|
||||
&info.sender,
|
||||
&owner,
|
||||
&proxy,
|
||||
&pledge,
|
||||
&gateway_identity,
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_gateway(
|
||||
pub fn try_remove_gateway_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_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, info.sender.clone())?
|
||||
.item(deps.storage, owner.clone())?
|
||||
{
|
||||
Some(record) => record.1,
|
||||
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner: info.sender }),
|
||||
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner }),
|
||||
};
|
||||
|
||||
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: info.sender.to_string(),
|
||||
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
|
||||
amount: vec![gateway_bond.pledge_amount()],
|
||||
};
|
||||
|
||||
// remove the bond
|
||||
storage::gateways().remove(deps.storage, 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(),
|
||||
)))
|
||||
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(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn try_update_gateway_config(
|
||||
@@ -109,9 +198,36 @@ pub(crate) fn try_update_gateway_config(
|
||||
info: MessageInfo,
|
||||
new_config: GatewayConfigUpdate,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
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);
|
||||
let owner = info.sender;
|
||||
_try_update_gateway_config(deps, new_config, owner, None)
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -138,10 +254,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_mixnode_pledge;
|
||||
use crate::support::tests::fixtures::{good_gateway_pledge, good_mixnode_pledge};
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use cosmwasm_std::Uint128;
|
||||
use mixnet_contract_common::ExecuteMsg;
|
||||
|
||||
#[test]
|
||||
@@ -276,12 +392,42 @@ pub mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(1, updated_nonce);
|
||||
|
||||
try_remove_gateway(test.deps_mut(), info.clone()).unwrap();
|
||||
_try_remove_gateway(test.deps_mut(), Addr::unchecked(sender), None).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();
|
||||
@@ -349,6 +495,7 @@ 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,
|
||||
));
|
||||
@@ -363,6 +510,33 @@ 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();
|
||||
@@ -387,6 +561,22 @@ 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());
|
||||
@@ -401,4 +591,39 @@ pub mod tests {
|
||||
assert_eq!(bond.gateway.location, update.location);
|
||||
assert_eq!(bond.gateway.version, update.version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_gateway_config_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
let update = GatewayConfigUpdate {
|
||||
host: "1.1.1.1:1234".to_string(),
|
||||
mix_port: 1234,
|
||||
clients_port: 1235,
|
||||
location: "at home".to_string(),
|
||||
version: "v1.2.3".to_string(),
|
||||
};
|
||||
|
||||
let res = try_update_gateway_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
update,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::interval::storage;
|
||||
use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_details_by_id};
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::rewards::storage as rewards_storage;
|
||||
use crate::support::helpers::AttachSendTokens;
|
||||
use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking};
|
||||
|
||||
pub(crate) trait ContractExecutableEvent {
|
||||
// note: the error only means a HARD error like we failed to read from storage.
|
||||
@@ -40,6 +40,7 @@ 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
|
||||
@@ -55,9 +56,20 @@ 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()
|
||||
.send_tokens(&owner, amount.clone())
|
||||
.add_event(new_delegation_on_unbonded_node_event(&owner, mix_id));
|
||||
.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,
|
||||
)?;
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
@@ -72,7 +84,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, None);
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let old_delegation = if let Some(existing_delegation) =
|
||||
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
|
||||
{
|
||||
@@ -94,6 +106,7 @@ pub(crate) fn delegate(
|
||||
let cosmos_event = new_delegation_event(
|
||||
created_at,
|
||||
&owner,
|
||||
&proxy,
|
||||
&new_delegation_amount,
|
||||
mix_id,
|
||||
mix_rewarding.total_unit_reward,
|
||||
@@ -105,6 +118,7 @@ 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
|
||||
@@ -124,10 +138,11 @@ 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, None);
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
|
||||
None => return Ok(Response::default()),
|
||||
Some(delegation) => delegation,
|
||||
@@ -140,9 +155,18 @@ 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()
|
||||
.send_tokens(&owner, tokens_to_return.clone())
|
||||
.add_event(new_undelegation_event(created_at, &owner, mix_id));
|
||||
.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,
|
||||
)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -173,15 +197,25 @@ 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()
|
||||
.send_tokens(owner, tokens.clone())
|
||||
.add_event(new_mixnode_unbonding_event(created_at, mix_id));
|
||||
.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,
|
||||
)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -277,8 +311,12 @@ 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,
|
||||
@@ -290,8 +328,14 @@ pub(crate) fn decrease_pledge(
|
||||
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
|
||||
|
||||
let response = Response::new()
|
||||
.send_tokens(owner, decrease_by.clone())
|
||||
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by));
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by))
|
||||
.maybe_add_track_vesting_decrease_mixnode_pledge(
|
||||
deps.storage,
|
||||
proxy.clone(),
|
||||
owner.clone().to_string(),
|
||||
decrease_by,
|
||||
)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -305,11 +349,13 @@ impl ContractExecutableEvent for PendingEpochEventData {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
..
|
||||
} => delegate(deps, env, self.created_at, owner, mix_id, amount),
|
||||
PendingEpochEventKind::Undelegate { owner, mix_id, .. } => {
|
||||
undelegate(deps, self.created_at, owner, mix_id)
|
||||
}
|
||||
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),
|
||||
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
|
||||
increase_pledge(deps, self.created_at, mix_id, amount)
|
||||
}
|
||||
@@ -426,25 +472,33 @@ impl ContractExecutableEvent for PendingIntervalEventData {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
|
||||
use mixnet_contract_common::Percent;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
use crate::support::tests::test_helpers;
|
||||
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
|
||||
use cosmwasm_std::Decimal;
|
||||
use mixnet_contract_common::Percent;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::*;
|
||||
|
||||
// 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 super::*;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
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 cosmwasm_std::coin;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn returns_the_tokens_if_mixnode_has_unbonded() {
|
||||
@@ -469,6 +523,7 @@ mod tests {
|
||||
Addr::unchecked(owner1),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -494,6 +549,7 @@ mod tests {
|
||||
Addr::unchecked(owner2),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key =
|
||||
@@ -532,6 +588,7 @@ mod tests {
|
||||
Addr::unchecked(owner1),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -557,6 +614,7 @@ mod tests {
|
||||
Addr::unchecked(owner2),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key =
|
||||
@@ -592,6 +650,7 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin_new,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -666,6 +725,7 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin_new,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -737,6 +797,7 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(get_bank_send_msg(&res).is_none());
|
||||
@@ -755,14 +816,118 @@ 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 super::*;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn doesnt_return_any_tokens_if_it_doesnt_exist() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -770,7 +935,7 @@ mod tests {
|
||||
|
||||
let owner = Addr::unchecked("delegator");
|
||||
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id).unwrap();
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None).unwrap();
|
||||
assert!(get_bank_send_msg(&res).is_none());
|
||||
}
|
||||
|
||||
@@ -785,7 +950,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);
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None);
|
||||
assert!(matches!(
|
||||
res,
|
||||
Err(MixnetContractError::InconsistentState { .. })
|
||||
@@ -831,7 +996,8 @@ mod tests {
|
||||
|
||||
let expected_return = delegation + truncated_reward.u128();
|
||||
|
||||
let res = undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id).unwrap();
|
||||
let res =
|
||||
undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id, None).unwrap();
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
|
||||
assert_eq!(receiver, owner);
|
||||
assert_eq!(sent_amount[0].amount.u128(), expected_return);
|
||||
@@ -849,19 +1015,117 @@ 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 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 cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
|
||||
|
||||
use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::mixnodes::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
|
||||
@@ -886,10 +1150,12 @@ 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(),
|
||||
mock_info(owner, &change.clone()),
|
||||
change.clone(),
|
||||
Addr::unchecked(owner),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -904,11 +1170,12 @@ 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();
|
||||
|
||||
@@ -996,6 +1263,79 @@ 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)]
|
||||
@@ -1275,10 +1615,12 @@ mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod decreasing_pledge {
|
||||
use super::*;
|
||||
use cosmwasm_std::{BankMsg, CosmosMsg, Uint128};
|
||||
use cosmwasm_std::{to_binary, BankMsg, CosmosMsg, Uint128, WasmMsg};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
||||
// this should have never happened so hard error MUST be thrown here
|
||||
@@ -1357,6 +1699,64 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_tokens_back_to_the_proxy_if_bonded_with_vesting() {
|
||||
let mut test = TestSetup::new();
|
||||
let owner = "mix-owner";
|
||||
let mix_id = test.add_dummy_mixnode_with_legal_proxy(owner, None);
|
||||
test.set_pending_pledge_change(mix_id, None);
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let amount = test.coin(12345);
|
||||
let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
|
||||
|
||||
assert_eq!(res.messages.len(), 2);
|
||||
assert_eq!(
|
||||
res.messages[0].msg,
|
||||
CosmosMsg::Bank(BankMsg::Send {
|
||||
to_address: vesting_contract.to_string(),
|
||||
amount: vec![amount],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attaches_vesting_track_message() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id_no_proxy = test.add_dummy_mixnode("mix-owner1", None);
|
||||
test.set_pending_pledge_change(mix_id_no_proxy, None);
|
||||
|
||||
let mix_id_proxy = test.add_dummy_mixnode_with_legal_proxy("mix-owner2", None);
|
||||
test.set_pending_pledge_change(mix_id_proxy, None);
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let amount = test.coin(12345);
|
||||
let res_no_proxy =
|
||||
decrease_pledge(test.deps_mut(), 123, mix_id_no_proxy, amount.clone()).unwrap();
|
||||
|
||||
// nothing was attached (apart from bank message tested in `returns_tokens_back_to_the_owner`)
|
||||
// because it wasn't done with proxy!
|
||||
assert_eq!(res_no_proxy.messages.len(), 1);
|
||||
|
||||
let res_proxy =
|
||||
decrease_pledge(test.deps_mut(), 123, mix_id_proxy, amount.clone()).unwrap();
|
||||
assert_eq!(res_proxy.messages.len(), 2);
|
||||
assert_eq!(
|
||||
res_proxy.messages[1].msg,
|
||||
CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: vesting_contract.to_string(),
|
||||
msg: to_binary(&VestingContractExecuteMsg::TrackDecreasePledge {
|
||||
owner: "mix-owner2".to_string(),
|
||||
amount,
|
||||
})
|
||||
.unwrap(),
|
||||
funds: vec![],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_any_events_in_between_is_equivalent_to_pledging_the_same_amount_immediately() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
@@ -159,8 +159,11 @@ mod tests {
|
||||
}
|
||||
|
||||
fn push_dummy_epoch_action(test: &mut TestSetup) {
|
||||
let dummy_action =
|
||||
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let env = test.env();
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
}
|
||||
@@ -568,8 +571,11 @@ mod tests {
|
||||
);
|
||||
|
||||
// it exists
|
||||
let dummy_action =
|
||||
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let env = test.env();
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action.clone()).unwrap();
|
||||
let expected = PendingEpochEventResponse {
|
||||
|
||||
@@ -222,10 +222,11 @@ mod tests {
|
||||
let env = test.env();
|
||||
|
||||
for _ in 0..500 {
|
||||
let dummy_action = PendingEpochEventKind::new_undelegate(
|
||||
Addr::unchecked("foomp"),
|
||||
test.rng.next_u32(),
|
||||
);
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
|
||||
assert_eq!(expected, id);
|
||||
@@ -234,10 +235,11 @@ mod tests {
|
||||
test.execute_all_pending_events();
|
||||
|
||||
for _ in 0..10 {
|
||||
let dummy_action = PendingEpochEventKind::new_undelegate(
|
||||
Addr::unchecked("foomp"),
|
||||
test.rng.next_u32(),
|
||||
);
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
|
||||
assert_eq!(expected, id);
|
||||
|
||||
@@ -373,14 +373,18 @@ 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::new_undelegate(Addr::unchecked("foomp"), i as MixId);
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: i as MixId,
|
||||
proxy: None,
|
||||
};
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -402,7 +406,7 @@ mod tests {
|
||||
mod performing_pending_epoch_actions {
|
||||
use super::*;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use cosmwasm_std::{coin, coins, BankMsg, Empty, SubMsg};
|
||||
use cosmwasm_std::{coin, coins, wasm_execute, BankMsg, Empty, SubMsg};
|
||||
use mixnet_contract_common::events::{
|
||||
new_active_set_update_event, new_delegation_on_unbonded_node_event,
|
||||
new_undelegation_event,
|
||||
@@ -491,6 +495,7 @@ 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);
|
||||
@@ -504,15 +509,17 @@ 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::new_delegate(
|
||||
Addr::unchecked("foomp"),
|
||||
123,
|
||||
coin(123, TEST_COIN_DENOM),
|
||||
);
|
||||
let non_existent_delegation = PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
proxy: None,
|
||||
};
|
||||
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 {
|
||||
@@ -520,6 +527,33 @@ 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)
|
||||
@@ -527,12 +561,16 @@ 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::new_undelegate(delegator.clone(), legit_mix);
|
||||
let legit_undelegate = PendingEpochEventKind::Undelegate {
|
||||
owner: delegator.clone(),
|
||||
mix_id: legit_mix,
|
||||
proxy: None,
|
||||
};
|
||||
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 {
|
||||
@@ -545,9 +583,9 @@ mod tests {
|
||||
let mut expected = Response::new().add_events(expected_events);
|
||||
expected.messages = expected_messages;
|
||||
assert_eq!(res, expected);
|
||||
assert_eq!(executed, 3);
|
||||
assert_eq!(executed, 4);
|
||||
assert_eq!(
|
||||
3,
|
||||
4,
|
||||
storage::LAST_PROCESSED_EPOCH_EVENT
|
||||
.load(test.deps().storage)
|
||||
.unwrap()
|
||||
@@ -1292,15 +1330,17 @@ mod tests {
|
||||
let mut expected_messages: Vec<SubMsg<Empty>> = Vec::new();
|
||||
|
||||
// epoch event
|
||||
let non_existent_delegation = PendingEpochEventKind::new_delegate(
|
||||
Addr::unchecked("foomp"),
|
||||
123,
|
||||
coin(123, TEST_COIN_DENOM),
|
||||
);
|
||||
let non_existent_delegation = PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
proxy: None,
|
||||
};
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
|
||||
.unwrap();
|
||||
expected_events.push(new_delegation_on_unbonded_node_event(
|
||||
&Addr::unchecked("foomp"),
|
||||
&None,
|
||||
123,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
|
||||
@@ -19,4 +19,3 @@ mod support;
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
mod testing;
|
||||
mod vesting_migration;
|
||||
|
||||
@@ -97,6 +97,7 @@ 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)?;
|
||||
@@ -104,7 +105,15 @@ 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, env.block.height);
|
||||
let mixnode_bond = MixNodeBond::new(
|
||||
mix_id,
|
||||
owner,
|
||||
pledge,
|
||||
layer,
|
||||
mixnode,
|
||||
proxy,
|
||||
env.block.height,
|
||||
);
|
||||
|
||||
// save mixnode bond data
|
||||
// note that this implicitly checks for uniqueness on identity key, sphinx key and owner
|
||||
@@ -402,6 +411,7 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
owner.clone(),
|
||||
None,
|
||||
pledge.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -434,6 +444,7 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
Addr::unchecked("different-owner"),
|
||||
None,
|
||||
pledge.clone(),
|
||||
);
|
||||
assert!(res.is_err());
|
||||
@@ -446,6 +457,7 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
owner,
|
||||
None,
|
||||
pledge.clone(),
|
||||
);
|
||||
assert!(res.is_err());
|
||||
@@ -459,6 +471,7 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params,
|
||||
Addr::unchecked("different-owner"),
|
||||
None,
|
||||
pledge,
|
||||
);
|
||||
assert!(res.is_err());
|
||||
|
||||
@@ -12,6 +12,7 @@ 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,
|
||||
@@ -22,7 +23,8 @@ 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, pledge, mixnode, cost_params);
|
||||
let msg =
|
||||
construct_mixnode_bonding_sign_payload(nonce, sender, proxy, pledge, mixnode, cost_params);
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{coin, Coin, DepsMut, Env, MessageInfo, Response, Storage};
|
||||
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
|
||||
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_mixnode_bonding_event, new_mixnode_config_update_event,
|
||||
@@ -24,7 +25,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, validate_pledge,
|
||||
ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
|
||||
validate_pledge,
|
||||
};
|
||||
|
||||
use super::storage;
|
||||
@@ -59,24 +61,74 @@ pub fn assign_mixnode_layer(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
// 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_mixnode(
|
||||
pub fn try_add_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mixnode: MixNode,
|
||||
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(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
mixnode: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
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_mixnode_pledge(deps.storage)?;
|
||||
let pledge = validate_pledge(info.funds, minimum_pledge)?;
|
||||
let pledge = validate_pledge(pledge, 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(&info.sender, deps.storage)?;
|
||||
ensure_no_existing_bond(&owner, 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
|
||||
@@ -85,7 +137,8 @@ pub(crate) fn try_add_mixnode(
|
||||
// check if this sender actually owns the mixnode by checking the signature
|
||||
verify_mixnode_bonding_signature(
|
||||
deps.as_ref(),
|
||||
info.sender.clone(),
|
||||
owner.clone(),
|
||||
proxy.clone(),
|
||||
pledge.clone(),
|
||||
mixnode.clone(),
|
||||
cost_params.clone(),
|
||||
@@ -93,7 +146,7 @@ pub(crate) 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, info.sender.clone())?;
|
||||
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
|
||||
|
||||
let node_identity = mixnode.identity_key.clone();
|
||||
let (node_id, layer) = save_new_mixnode(
|
||||
@@ -101,12 +154,14 @@ pub(crate) fn try_add_mixnode(
|
||||
env,
|
||||
mixnode,
|
||||
cost_params,
|
||||
info.sender.clone(),
|
||||
owner.clone(),
|
||||
proxy.clone(),
|
||||
pledge.clone(),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(new_mixnode_bonding_event(
|
||||
&info.sender,
|
||||
&owner,
|
||||
&proxy,
|
||||
&pledge,
|
||||
&node_identity,
|
||||
node_id,
|
||||
@@ -119,19 +174,43 @@ pub fn try_increase_pledge(
|
||||
env: Env,
|
||||
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 })?;
|
||||
_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 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(info.funds, coin(1, rewarding_denom))?;
|
||||
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
|
||||
|
||||
let cosmos_event = new_pending_pledge_increase_event(mix_id, &pledge_increase);
|
||||
|
||||
@@ -153,14 +232,39 @@ pub fn try_decrease_pledge(
|
||||
info: MessageInfo,
|
||||
decrease_by: Coin,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
|
||||
_try_decrease_pledge(deps, env, decrease_by, info.sender, None)
|
||||
}
|
||||
|
||||
pub fn try_decrease_pledge_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
decrease_by: Coin,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_decrease_pledge(deps, env, decrease_by, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn _try_decrease_pledge(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
decrease_by: Coin,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
|
||||
let mut pending_changes = mix_details.pending_changes;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
// decreasing pledge is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
|
||||
ensure_bonded(&mix_details.bond_information)?;
|
||||
ensure_no_pending_pledge_changes(&pending_changes)?;
|
||||
|
||||
@@ -208,12 +312,34 @@ pub fn try_decrease_pledge(
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_mixnode(
|
||||
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(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
_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 pending_changes = storage::PENDING_MIXNODE_CHANGES
|
||||
.may_load(deps.storage, existing_bond.mix_id)?
|
||||
.unwrap_or_default();
|
||||
@@ -222,12 +348,15 @@ 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(
|
||||
@@ -246,6 +375,7 @@ 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,
|
||||
)),
|
||||
@@ -257,13 +387,39 @@ pub(crate) fn try_update_mixnode_config(
|
||||
info: MessageInfo,
|
||||
new_config: MixNodeConfigUpdate,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
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)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||
|
||||
let cfg_update_event =
|
||||
new_mixnode_config_update_event(existing_bond.mix_id, &info.sender, &new_config);
|
||||
new_mixnode_config_update_event(existing_bond.mix_id, &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.mix_node.host = new_config.host;
|
||||
updated_bond.mix_node.mix_port = new_config.mix_port;
|
||||
@@ -286,18 +442,45 @@ 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, &info.sender)?;
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
|
||||
|
||||
// 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)?;
|
||||
|
||||
let cosmos_event = new_mixnode_pending_cost_params_update_event(
|
||||
existing_bond.mix_id,
|
||||
&info.sender,
|
||||
&owner,
|
||||
&proxy,
|
||||
&new_costs,
|
||||
);
|
||||
|
||||
@@ -314,7 +497,7 @@ pub(crate) fn try_update_mixnode_cost_params(
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{Addr, Order, StdResult, Uint128};
|
||||
use cosmwasm_std::{Order, StdResult, Uint128};
|
||||
|
||||
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, ExecuteMsg, LayerDistribution, Percent};
|
||||
@@ -497,6 +680,38 @@ 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![
|
||||
@@ -546,6 +761,23 @@ 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());
|
||||
@@ -567,6 +799,35 @@ 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();
|
||||
@@ -647,7 +908,22 @@ 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());
|
||||
@@ -667,6 +943,41 @@ 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![
|
||||
@@ -731,7 +1042,23 @@ 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(),
|
||||
@@ -772,6 +1099,40 @@ 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();
|
||||
@@ -879,6 +1240,69 @@ 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();
|
||||
@@ -1033,6 +1457,35 @@ 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)]
|
||||
@@ -1093,6 +1546,73 @@ 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();
|
||||
@@ -1289,5 +1809,38 @@ pub mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let stake = Uint128::new(100_000_000_000);
|
||||
let decrease = test.coin(1000);
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(stake), illegal_proxy.clone());
|
||||
|
||||
let res = try_decrease_pledge_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
decrease,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,2 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::interval::storage as interval_storage;
|
||||
use cosmwasm_std::{DepsMut, Order, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::PendingEpochEventKind;
|
||||
|
||||
fn ensure_no_pending_proxy_events(storage: &dyn Storage) -> Result<(), MixnetContractError> {
|
||||
let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(storage)?;
|
||||
let last_inserted = interval_storage::EPOCH_EVENT_ID_COUNTER.load(storage)?;
|
||||
|
||||
// no pending events
|
||||
if last_executed == last_inserted {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for maybe_event in
|
||||
interval_storage::PENDING_EPOCH_EVENTS.range(storage, None, None, Order::Ascending)
|
||||
{
|
||||
let (id, event_data) = maybe_event?;
|
||||
match event_data.kind {
|
||||
PendingEpochEventKind::Delegate { proxy, .. } => {
|
||||
if proxy.is_some() {
|
||||
return Err(MixnetContractError::FailedMigration {
|
||||
comment: format!(
|
||||
"there is a pending vesting contract delegation with id {id}"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
PendingEpochEventKind::Undelegate { proxy, .. } => {
|
||||
if proxy.is_some() {
|
||||
return Err(MixnetContractError::FailedMigration {
|
||||
comment: format!(
|
||||
"there is a pending vesting contract undelegation with id {id}"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> {
|
||||
ensure_no_pending_proxy_events(deps.storage)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
|
||||
use crate::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 cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response};
|
||||
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_active_set_update_event, new_mix_rewarding_event,
|
||||
@@ -27,6 +16,22 @@ 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<'_>,
|
||||
@@ -135,16 +140,37 @@ pub(crate) fn try_withdraw_operator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// we need to grab all of the node's details, so we'd known original pledge alongside
|
||||
_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
|
||||
// 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, info.sender.clone())?.ok_or(
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?.ok_or(
|
||||
MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: info.sender.clone(),
|
||||
owner: owner.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)?;
|
||||
@@ -152,13 +178,26 @@ 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() {
|
||||
response = response.send_tokens(&info.sender, reward.clone())
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_withdraw_operator_reward_event(
|
||||
&info.sender,
|
||||
reward,
|
||||
mix_id,
|
||||
&owner, &proxy, reward, mix_id,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -166,15 +205,37 @@ 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, &info.sender, None);
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
|
||||
None => {
|
||||
return Err(MixnetContractError::NoMixnodeDelegationFound {
|
||||
mix_id,
|
||||
address: info.sender.into_string(),
|
||||
proxy: None,
|
||||
address: owner.into_string(),
|
||||
proxy: proxy.map(Addr::into_string),
|
||||
});
|
||||
}
|
||||
Some(delegation) => delegation,
|
||||
@@ -196,18 +257,33 @@ 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() {
|
||||
response = response.send_tokens(&info.sender, reward.clone())
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_withdraw_delegator_reward_event(
|
||||
&info.sender,
|
||||
reward,
|
||||
mix_id,
|
||||
&owner, &proxy, reward, mix_id,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1329,11 +1405,14 @@ pub mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod withdrawing_delegator_reward {
|
||||
use crate::interval::pending_events;
|
||||
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
|
||||
use cosmwasm_std::{BankMsg, CosmosMsg, Decimal, Uint128};
|
||||
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 super::*;
|
||||
|
||||
#[test]
|
||||
@@ -1663,14 +1742,60 @@ 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 super::*;
|
||||
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 cosmwasm_std::{Addr, BankMsg, CosmosMsg, Uint128};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_only_be_done_if_bond_exists() {
|
||||
@@ -1783,6 +1908,42 @@ 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)]
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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::{Addr, BankMsg, Coin, CosmosMsg, Response, Storage};
|
||||
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixNodeBond};
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
// helper trait to attach `Msg` to a response if it's provided
|
||||
#[allow(dead_code)]
|
||||
@@ -24,16 +26,131 @@ impl<T> AttachOptionalMessage<T> for Response<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait AttachSendTokens {
|
||||
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self;
|
||||
// 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>;
|
||||
}
|
||||
|
||||
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],
|
||||
})
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +158,20 @@ impl<T> AttachSendTokens for Response<T> {
|
||||
// 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,
|
||||
@@ -206,6 +337,39 @@ 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 {
|
||||
|
||||
@@ -22,7 +22,7 @@ pub(crate) fn valid_bond_gateway_msg(
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
|
||||
let msg = gateway_bonding_sign_payload(deps, sender, gateway.clone(), stake);
|
||||
let msg = gateway_bonding_sign_payload(deps, sender, None, gateway.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let identity_key = keypair.public_key().to_base58_string();
|
||||
|
||||
@@ -14,7 +14,8 @@ 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::transactions::try_add_gateway;
|
||||
use crate::gateways::storage as gateways_storage;
|
||||
use crate::gateways::transactions::{try_add_gateway, try_add_gateway_on_behalf};
|
||||
use crate::interval::transactions::{
|
||||
perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition,
|
||||
};
|
||||
@@ -26,7 +27,9 @@ 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_remove_mixnode};
|
||||
use crate::mixnodes::transactions::{
|
||||
try_add_mixnode, try_add_mixnode_on_behalf, try_remove_mixnode,
|
||||
};
|
||||
use crate::rewards::queries::{
|
||||
query_pending_delegator_reward, query_pending_mixnode_operator_reward,
|
||||
};
|
||||
@@ -42,7 +45,7 @@ pub mod test_helpers {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::testing::MockApi;
|
||||
use cosmwasm_std::testing::MockQuerier;
|
||||
use cosmwasm_std::{coin, coins, Addr, BankMsg, CosmosMsg, Storage};
|
||||
use cosmwasm_std::{coin, coins, Addr, Api, BankMsg, CosmosMsg, Storage};
|
||||
use cosmwasm_std::{Coin, Order};
|
||||
use cosmwasm_std::{Decimal, Empty, MemoryStorage};
|
||||
use cosmwasm_std::{Deps, OwnedDeps};
|
||||
@@ -145,6 +148,13 @@ 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())
|
||||
}
|
||||
@@ -168,6 +178,7 @@ 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();
|
||||
|
||||
@@ -184,7 +195,14 @@ pub mod test_helpers {
|
||||
|
||||
let nonce = signing_storage::get_signing_nonce(self.deps().storage, owner).unwrap();
|
||||
|
||||
let msg = construct_family_join_permit(nonce, family_head, member_node.to_owned());
|
||||
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 sig_bytes = family_owner_keys
|
||||
.private_key()
|
||||
@@ -199,11 +217,13 @@ 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);
|
||||
let join_permit =
|
||||
self.generate_family_join_permit(head_keys, &member_identity, vesting);
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
|
||||
try_join_family(
|
||||
@@ -215,13 +235,12 @@ 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_keypair(head, None);
|
||||
let (mix_id, keys) = self.add_dummy_mixnode_with_proxy_and_keypair(head, None);
|
||||
|
||||
try_create_family(self.deps_mut(), mock_info(head, &[]), label.to_string()).unwrap();
|
||||
(mix_id, keys)
|
||||
@@ -319,7 +338,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, mixnode, stake);
|
||||
let msg = mixnode_bonding_sign_payload(self.deps(), owner, None, mixnode, stake);
|
||||
ed25519_sign_message(msg, key)
|
||||
}
|
||||
|
||||
@@ -340,8 +359,13 @@ pub mod test_helpers {
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
};
|
||||
|
||||
let msg =
|
||||
mixnode_bonding_sign_payload(self.deps(), owner, mixnode.clone(), stake.clone());
|
||||
let msg = mixnode_bonding_sign_payload(
|
||||
self.deps(),
|
||||
owner,
|
||||
None,
|
||||
mixnode.clone(),
|
||||
stake.clone(),
|
||||
);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let info = mock_info(owner, &stake);
|
||||
@@ -365,6 +389,156 @@ 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,
|
||||
@@ -381,7 +555,8 @@ 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, mixnode.clone(), stake);
|
||||
let msg =
|
||||
mixnode_bonding_sign_payload(self.deps(), sender, None, mixnode.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
(mixnode, owner_signature, keypair)
|
||||
@@ -404,7 +579,8 @@ pub mod test_helpers {
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
|
||||
let msg = gateway_bonding_sign_payload(self.deps(), sender, gateway.clone(), stake);
|
||||
let msg =
|
||||
gateway_bonding_sign_payload(self.deps(), sender, None, gateway.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
(gateway, owner_signature)
|
||||
@@ -449,10 +625,87 @@ 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,
|
||||
@@ -471,8 +724,14 @@ 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)
|
||||
.unwrap();
|
||||
pending_events::undelegate(
|
||||
self.deps_mut(),
|
||||
height,
|
||||
Addr::unchecked(delegator),
|
||||
target,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn start_epoch_transition(&mut self) {
|
||||
@@ -850,6 +1109,7 @@ pub mod test_helpers {
|
||||
Addr::unchecked(format!("owner{}", i)),
|
||||
mix_id,
|
||||
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -945,6 +1205,7 @@ pub mod test_helpers {
|
||||
pub fn mixnode_bonding_sign_payload(
|
||||
deps: Deps<'_>,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
mixnode: MixNode,
|
||||
stake: Vec<Coin>,
|
||||
) -> SignableMixNodeBondingMsg {
|
||||
@@ -953,13 +1214,14 @@ 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), stake, payload);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, 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 {
|
||||
@@ -967,7 +1229,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), stake, payload);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
|
||||
SignableGatewayBondingMsg::new(nonce, content)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// 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,54 +691,6 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id",
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
|
||||
@@ -669,54 +669,6 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id",
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
|
||||
@@ -70,12 +70,55 @@ 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)
|
||||
}
|
||||
@@ -84,22 +127,47 @@ 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::TrackMigratedMixnode { owner } => try_track_migrate_mixnode(&owner, info, deps),
|
||||
ExecuteMsg::TrackMigratedDelegation { owner, mix_id } => {
|
||||
try_track_migrate_delegation(&owner, mix_id, 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)
|
||||
}
|
||||
_ => Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,10 +61,6 @@ 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,9 +44,4 @@ pub trait DelegatingAccount {
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError>;
|
||||
fn track_migrated_delegation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError>;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,8 @@ use mixnet_contract_common::{
|
||||
use vesting_contract_common::events::{
|
||||
new_ownership_transfer_event, new_periodic_vesting_account_event,
|
||||
new_staking_address_update_event, new_track_gateway_unbond_event,
|
||||
new_track_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,
|
||||
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};
|
||||
|
||||
@@ -256,35 +255,6 @@ 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,25 +125,4 @@ 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,24 +221,4 @@ impl MixnodeBondingAccount for Account {
|
||||
.add_message(update_mixnode_costs_msg)
|
||||
.add_event(new_vesting_update_mixnode_cost_params_event()))
|
||||
}
|
||||
|
||||
fn try_track_migrated_mixnode(
|
||||
&self,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError> {
|
||||
let Some(pledge) = self.load_mixnode_pledge(storage)? else {
|
||||
return Err(VestingContractError::NoBondFound(
|
||||
self.owner_address().as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
// treat the tokens that were used for bonding as 'withdrawn'
|
||||
let current_withdrawn = self.load_withdrawn(storage)?;
|
||||
self.save_withdrawn(current_withdrawn + pledge.amount.amount, storage)?;
|
||||
|
||||
// don't change the balance as the tokens are left in the mixnet contract
|
||||
|
||||
// remove the pledge data since it no longer belongs to the vesting account
|
||||
self.remove_mixnode_pledge(storage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ pub fn populate_vesting_periods(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::contract::*;
|
||||
|
||||
use crate::storage::*;
|
||||
use crate::support::tests::helpers::vesting_account_percent_fixture;
|
||||
use crate::support::tests::helpers::{
|
||||
init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM,
|
||||
};
|
||||
use crate::traits::DelegatingAccount;
|
||||
use crate::traits::GatewayBondingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
use crate::vesting::account::StorableVestingAccountExt;
|
||||
use crate::vesting::populate_vesting_periods;
|
||||
use contracts_common::signing::MessageSignature;
|
||||
@@ -36,56 +36,162 @@ 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 _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
||||
let created_account = load_account(Addr::unchecked("owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
created_account.load_balance(&deps.storage).unwrap(),
|
||||
// One was liquidated
|
||||
Uint128::new(1_000_000_000_000)
|
||||
);
|
||||
|
||||
// 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 env = mock_env();
|
||||
let mut 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());
|
||||
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();
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
new_owner_account.load_balance(&deps.storage),
|
||||
account.load_balance(&deps.storage)
|
||||
);
|
||||
|
||||
// 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 env = mock_env();
|
||||
let mut 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);
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
// 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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -107,12 +213,31 @@ mod tests {
|
||||
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_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!(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]
|
||||
@@ -120,27 +245,66 @@ mod tests {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let amount = coin(1000000000, "unym");
|
||||
let amount1 = coin(1000000000, "unym");
|
||||
let amount2 = coin(100, "unym");
|
||||
|
||||
// create the accounts
|
||||
let msg = ExecuteMsg::CreateAccount {
|
||||
let msg1 = ExecuteMsg::CreateAccount {
|
||||
owner_address: "vesting1".to_string(),
|
||||
staking_address: None,
|
||||
vesting_spec: None,
|
||||
cap: None,
|
||||
};
|
||||
let response = execute(
|
||||
let res1 = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("admin", &[amount.clone()]),
|
||||
msg,
|
||||
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,
|
||||
);
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
Err(VestingContractError::StakingAccountExists(
|
||||
"vesting1".to_string()
|
||||
)),
|
||||
res
|
||||
);
|
||||
|
||||
// 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]
|
||||
@@ -402,13 +566,63 @@ 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);
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
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,
|
||||
);
|
||||
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]
|
||||
@@ -435,24 +649,52 @@ 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 msg = ExecuteMsg::BondMixnode {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature: vec![1, 2, 3, 4].into(),
|
||||
amount: Coin {
|
||||
let ok = account.try_bond_mixnode(
|
||||
mix_node.clone(),
|
||||
cost_params.clone(),
|
||||
MessageSignature::from(vec![1, 2, 3]),
|
||||
Coin {
|
||||
amount: Uint128::new(90_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
};
|
||||
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()
|
||||
})
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
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]
|
||||
|
||||
@@ -46,4 +46,4 @@ If you have successfully completed KYC on Synaps earlier and provided your Nym w
|
||||
|
||||
Using the Nym network is free for now, but user fees will be introduced in the future and those will be paid in NYM tokens, so set them aside! And until then, you can contribute to running our decentralized infrastructure and earn mix mining rewards by setting up Nym mix nodes and bonding your tokens to them. You don't need to run nodes to contribute to the security and performance of the Nym network though. You can pledge your trust in someone else's node by delegating your NYM tokens to it, and receive a share of their mix mining rewards.
|
||||
|
||||
You can find out more about how staking works in [this](https://www.youtube.com/watch?v=PcNGcTwlm0I) video from our Chief Scientist Claudia Diaz and if you need help setting up your mix node, choosing one to delegate your tokens to, or anything else, our community is there to help on [Discord](https://discord.gg/nymproject) and [Telegram](https://t.me/nymchan).
|
||||
You can find out more about how staking works in [this](https://www.youtube.com/watch?v=PcNGcTwlm0I) video from our Chief Scientist Claudia Diaz and if you need help setting up your mix node, choosing one to delegate your tokens to, or anything else, our community is there to help on [Discord](https://nymtech.net/go/discord) and [Telegram](https://t.me/nymchan).
|
||||
|
||||
@@ -39,5 +39,5 @@ Follow this [video](https://youtu.be/quj8H2qeOwY?t=97) to see the steps on Teleg
|
||||
|
||||
**Now your Telegram runs over NymConnect.**
|
||||
|
||||
NymConnect is currently available for several applications and service providers. Support for more apps is on the way. For any bug reports or feedback please reach out to us on Telegram or our [Discord](http://discord.gg/FaTJb8q8) server.
|
||||
NymConnect is currently available for several applications and service providers. Support for more apps is on the way. For any bug reports or feedback please reach out to us on Telegram or our [Discord](https://nymtech.net/go/discord) server.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Since these docs autogenerate command output and import docs from binaries in `t
|
||||
Changes merged to `master` will be autodeployed to the production site.
|
||||
|
||||
### Contributing a new translation
|
||||
To contribute tranlsations in a new language, please get in touch via [Matrix](https://matrix.to/#/#general:nymtech.chat) or [Discord](https://discord.gg/nymproject).
|
||||
To contribute tranlsations in a new language, please get in touch via [Matrix](https://matrix.to/#/#general:nymtech.chat) or [Discord](https://nymtech.net/go/discord).
|
||||
|
||||
### Variables
|
||||
There are some variables that are shared across the entire docs site, such as the current latest software version.
|
||||
|
||||
@@ -36,7 +36,7 @@ Create an account on Sandbox using the nym-cli:
|
||||
|
||||
```./nym-cli --config-env-file <path-to>sandbox.env account create```
|
||||
|
||||
You will need `nymt` funds sent to this account. Get in touch via Nym [Telegram](https://t.me/nymchan) or [Discord](https://discord.gg/nymproject) and we can send them to you.
|
||||
You will need `nymt` funds sent to this account. Get in touch via Nym [Telegram](https://t.me/nymchan) or [Discord](https://nymtech.net/go/discord) and we can send them to you.
|
||||
|
||||
Next, you init the nym-client with the enabled credentials mode set to true:
|
||||
|
||||
|
||||
@@ -42,17 +42,17 @@ 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).
|
||||
- [Bump braces from `3.0.2` to `3.0.3` in `/nym-api/tests`](https://github.com/nymtech/nym/pull/4659): Version update of [braces](https://github.com/micromatch/braces).
|
||||
- [Bump braces from `3.0.2` to `3.0.3` in `/docker/typescript_client/upload_contract`](https://github.com/nymtech/nym/pull/4658): Version update of [braces](https://github.com/micromatch/braces).
|
||||
- [Bump `ws` from `8.13.0` to `8.17.1` in `/wasm/client/internal-dev-node`](https://github.com/nymtech/nym/pull/4665): Version update of [`ws`](https://github.com/websockets/ws).
|
||||
- [Bump `ws` from `8.14.2` to `8.17.1` in `/sdk/typescript/packages/nodejs-client`](https://github.com/nymtech/nym/pull/4662): Version update of [`ws`](https://github.com/websockets/ws).
|
||||
- [Bump `ws` from `8.13.0` to `8.17.1` in `/wasm/client/internal-dev-node`](https://github.com/nymtech/nym/pull/4665): Version update of [`ws`](https://github.com/websockets/ws).
|
||||
- [Bump `ws` from `8.14.2` to `8.17.1` in `/sdk/typescript/packages/nodejs-client`](https://github.com/nymtech/nym/pull/4662): Version update of [`ws`](https://github.com/websockets/ws).
|
||||
- [Add expiry returned on import](https://github.com/nymtech/nym/pull/4670): We need to return the expiry on import for desktop daemon `nym-vpnd`.
|
||||
- [New clippy lints](https://github.com/nymtech/nym/pull/4660)
|
||||
- [Remove `nym-connect` directory](https://github.com/nymtech/nym/pull/4643): Since the `nym-vpn` has superseded `nym-connect`, remove `nym-connect` from the repo.
|
||||
- [Remove `nym-connect` directory](https://github.com/nymtech/nym/pull/4643): Since the `nym-vpn` has superseded `nym-connect`, remove `nym-connect` from the repo.
|
||||
- [Remove old PR template](https://github.com/nymtech/nym/pull/4639)
|
||||
|
||||
### Bugfix
|
||||
@@ -62,7 +62,7 @@ This page displays a full list of all the changes during our release cycle from
|
||||
### Operators Guide updates
|
||||
|
||||
- [Node description guide](nodes/configuration.md#node-description): Steps to add self-description to `nym-node` and query this information from any node.
|
||||
- [Web Secure Socket (WSS) guide and reverse proxy update](/nodes/proxy-configuration.md), PR [here](https://github.com/nymtech/nym/pull/4694): A guide to setup `nym-node` in a secure fashion, using WSS via Nginx and Certbot. Landing page (reversed proxy) is updated and simplified.
|
||||
- [Web Secure Socket (WSS) guide and reverse proxy update](nodes/proxy-configuration.md), PR [here](https://github.com/nymtech/nym/pull/4694): A guide to setup `nym-node` in a secure fashion, using WSS via Nginx and Certbot. Landing page (reversed proxy) is updated and simplified.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -8,3 +8,19 @@
|
||||
[HostSailor](https://hostsailor.com),"USA","Yes, based on ticket","Yes","The IPv6 setup needs custom research and is not documented","05/2024"
|
||||
[Misaka](https://www.misaka.io/),"South Africa","Yes, native support","No","Very Expensive","05/2024"
|
||||
[IsHosting](https://ishosting.com/en),"Brazil, Netherlands","Yes, based on ticket","Yes","Expensive","05/2024"
|
||||
[AlexHost](https://alexhost.com),"Moldova, Bulgaria, Sweden, Netherlands","Yes, on by default","Yes","They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR)","07/2024"
|
||||
[iHostArt](https://ihostart.com),"Romania","Yes, on by default","Yes","Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware.","07/2024"
|
||||
[Incognet](https://incognet.io),"Netherlands and USA","Yes, on by default","Yes","They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits","07/2024"
|
||||
[vSys Host](https://vsys.host),"Ukraine, Netherlands, USA","Yes, on by default","Yes","Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check.","07/2024"
|
||||
[LiteServer](https://liteserver.nl),"Netherlands","Yes, on by default","Yes","Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal","07/2024"
|
||||
[TerraHost](https://terrahost.no),"Norway","Yes, on by default","Yes","Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser","07/2024"
|
||||
[Mevspace](https://mevspace.com),"Poland","Yes, on by default","Yes","Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff","07/2024"
|
||||
[Hostiko](https://hostiko.com.ua),"Ukraine, Germany","Yes, on by default","Yes","Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket.","07/2024"
|
||||
[Hostslick](https://hostslick.com),"Netherlands, Germany","Yes, on by default","Yes","Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node","07/2024"
|
||||
[RDP](https://rdp.sh),"Netherlands, USA, Poland","Yes, on by default","Yes","German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node.","07/2024"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -11,7 +11,7 @@ This document presents an initiative to further support Nym’s mission of allow
|
||||
All the technical changes on the side of Nym nodes - ***Project Smoosh*** - are described in the [FAQ section](../archive/faq/smoosh-faq.md).
|
||||
|
||||
```admonish warning
|
||||
Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://discord.gg/nymproject), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences.
|
||||
Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://nymtech.net/go/discord), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences.
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -28,16 +28,29 @@ You can bond your `nym-node` via the Desktop wallet.
|
||||
- To find out your `nym-node` details, run `./nym-node bonding-information --id <ID>`
|
||||
- To get a correct host address, run `echo "$(curl -4 https://ifconfig.me)"`
|
||||
|
||||
2. Open the box called `Show advanced options` and make sure all your ports are set correctly, like the values in this table:
|
||||
|
||||
2. Enter the `Amount`, `Operating cost` and `Profit margin` and press `Next`.
|
||||
| Node type | Port name | Correct port value |
|
||||
| :-- | :-- | :-- |
|
||||
| Mixnode | Mix port | `1789` |
|
||||
| Mixnode | Verloc port | `1790` |
|
||||
| Mixnode | HTTP api port (picture below) | `8080` |
|
||||
| Gateway (entry & exit) | Mix port | `1789` |
|
||||
| Gateway (entry & exit) | Client WS API port | `9000` |
|
||||
|
||||
3. You will be asked to run a `sign` command with your `nym-node` - copy and paste the long signature as the value of `--contract-msg` and run it.
|
||||
- If you bonding `nym-node --mode mixnode` through *Bond mixnode* desktop wallet menu, change manually *HTTP api port* value from deprecated `8000` to `8080` - a generic `nym-node` HTTP port (for all modes).
|
||||
|
||||

|
||||
|
||||
3. Enter the `Amount`, `Operating cost` and `Profit margin` and press `Next`.
|
||||
|
||||
4. You will be asked to run a `sign` command with your `nym-node` - copy and paste the long signature as the value of `--contract-msg` and run it.
|
||||
|
||||
```
|
||||
./nym-node sign --contract-msg <PAYLOAD_GENERATED_BY_THE_WALLET>
|
||||
```
|
||||
|
||||
4. Copy the resulting signature string and paste it into the wallet nodal, press `Next` and confirm the transaction:
|
||||
5. Copy the resulting signature string and paste it into the wallet nodal, press `Next` and confirm the transaction:
|
||||
|
||||
```sh
|
||||
# This is just an example, copy the one from your process
|
||||
@@ -46,9 +59,10 @@ You can bond your `nym-node` via the Desktop wallet.
|
||||
```
|
||||
|
||||

|
||||
|
||||
*This image is just an example, copy-paste your own base58-encoded signature*
|
||||
|
||||
5. Your node will now be bonded and ready to recieve traffic, latest at the beginning of the next epoch (at most 1 hour)
|
||||
6. Your node will now be bonded and ready to recieve traffic, latest at the beginning of the next epoch (at most 1 hour)
|
||||
|
||||
|
||||
If everything worked, you'll see your node running on the either the [Sandbox testnet network explorer](https://sandbox-explorer.nymtech.net) or the [mainnet network explorer](https://explorer.nymtech.net), depending on which network you're running.
|
||||
|
||||
@@ -200,9 +200,58 @@ systemctl status <NODE>.service
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
This lets your operating system know it's ok to reload the service configuration. Then restart your `<NODE>`.
|
||||
This lets your operating system know it's OK to reload the service configuration. Then restart your `<NODE>`.
|
||||
|
||||
|
||||
## Backup a node
|
||||
|
||||
Anything can happen to the server on which your node is running. To back up your `nym-node` keys and configuration protects the operators against the negative impact of unexpected events. To restart your node on another server, two essential pieces are needed:
|
||||
|
||||
1. Node keys to initialise the same node on a new VPS
|
||||
2. Access to the bonding Nym account (wallet seeds) to edit the IP on smart contract
|
||||
|
||||
Assuming that everyone access their wallets from local machine and does *not* store their seeds on VPS, point \#2 should be a given. To backup your `nym-node` keys and configuration in the easiest way possible, copy the entire config directory `.nym` from your VPS to your local desktop, using a special copy command `scp`:
|
||||
|
||||
1. Create a directory where you want to store your backup
|
||||
```sh
|
||||
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
2. Copy configuration folder `.nym` from your VPS to your newly created backup directory:
|
||||
|
||||
```sh
|
||||
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes/<YOUR_NODE_ID> <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
3. The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup directory.
|
||||
|
||||
## Restoring a node
|
||||
|
||||
In case your VPS shut down and you have a [backup](#backup-a-node) of your node keys and access to your bonding wallet, you can easily restore your node on another server without losing your delegation.
|
||||
|
||||
1. On VPS: Do all [preliminary steps](preliminary-steps.md) needed to run a `nym-node`.
|
||||
|
||||
2. On VPS: Create a `.nym/nym-nodes` configuration folder:
|
||||
```sh
|
||||
mkdir -pv ~/.nym/nym-nodes
|
||||
```
|
||||
|
||||
3. From machine where your node is backed up (usually local desktop): Copy the folder with your node keys and configuration to the newly created folder on your VPS using `scp` command. Make sure to grab the entire `nym-node` configuration folder, which is called after your local `nym-node` ID, the `-r` flag will take care of all sub-directories and their content:
|
||||
```sh
|
||||
scp -r <LOCAL_NODE_CONFIGURATION_FOLDER> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:~/.nym/nym-nodes/
|
||||
```
|
||||
|
||||
4. The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the target directory `.nym/nym-nodes` on your VPS.
|
||||
|
||||
|
||||
5. Configure your node on the new VPS:
|
||||
|
||||
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = ['<YOUR_PUBLIC_IP>',]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
|
||||
* Try to run the node and see if everything works.
|
||||
* Setup the [systemd](#systemd) automation (don't forget to add the [terms and conditions flag](setup.md#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
|
||||
|
||||
6. And finally change the node smart contract info via the wallet interface. Open Nym Wallet, go to *Bonding*, open *Gateway Settings* or *Mixnode Settings* and change *Host* value to the new `nym-node` IP address. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks, returning zero performance.
|
||||
|
||||
## Moving a node
|
||||
|
||||
In case of a need to move a Nym Node from one machine to another and avoiding to lose the delegation, here are few steps how to do it.
|
||||
@@ -224,11 +273,20 @@ mkdir ~/.nym/nym-nodes
|
||||
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:~/.nym/nym-nodes/
|
||||
```
|
||||
|
||||
**On new/target machine**
|
||||
**On new/target VPS**
|
||||
|
||||
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP.
|
||||
* Setup the [systemd](#systemd) automation, reload the daemon and run the service, or just simply run the node if you don't use automation
|
||||
* Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks.
|
||||
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = ['<YOUR_PUBLIC_IP>',]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
|
||||
* Try to run the node and see if everything works.
|
||||
* Setup the [systemd](#systemd) automation (don't forget to add the [terms and conditions flag](setup.md#terms--conditions)) to `ExecStart` command, reload the daemon and run the service. If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic:
|
||||
```sh
|
||||
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/etc/systemd/system/nym-node.service <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:/etc/systemd/system/nym-node.service
|
||||
```
|
||||
|
||||
**In your desktop wallet**
|
||||
|
||||
* Change the node smart contract info via the wallet interface. Open Nym Wallet, go to *Bonding*, open *Gateway Settings* or *Mixnode Settings* and change *Host* value to the new `nym-node` IP address. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks, returning zero performance.
|
||||
|
||||
Make sure to stop the old node.
|
||||
|
||||
## Rename node local ID
|
||||
|
||||
@@ -282,12 +340,12 @@ less ~/.nym/nym-nodes/default-nym-node/config/config.toml
|
||||
## Ports
|
||||
All `<NODE>`-specific port configuration can be found in `$HOME/.nym/<NODE>/<YOUR_ID>/config/config.toml`. If you do edit any port configs, remember to restart your client and node processes.
|
||||
|
||||
### Nym Node: Minode mode port reference
|
||||
### Nym Node: Mixnode mode port reference
|
||||
| Default port | Use |
|
||||
| ------------ | ------------------------- |
|
||||
| `1789` | Listen for Mixnet traffic |
|
||||
| `1790` | Listen for VerLoc traffic |
|
||||
| `8000` | Metrics http API endpoint |
|
||||
| `8080` | Metrics http API endpoint |
|
||||
|
||||
|
||||
### Nym Node: Gateway modes port reference
|
||||
|
||||
@@ -592,15 +592,28 @@ server {
|
||||
|
||||
# WSS configuration
|
||||
server {
|
||||
listen ${port_value};
|
||||
listen ${port_value} ssl http2;
|
||||
listen [::]:${port_value} ssl http2;
|
||||
|
||||
server_name ${host_name};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/${host_name}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${host_name}/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location / {
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header X-Forwarded-For \$remote_addr;
|
||||
@@ -699,9 +712,9 @@ We made the landing page customization directory in [*Preliminary steps*](#preli
|
||||
3. Configure your site to work with `nginx`. Open a new text file `/etc/nginx/sites-available/<HOSTNAME>` and paste the block below. Don't forget to insert your correct values.
|
||||
~~~admonish example collapsible=true title="Site configuration `/etc/nginx/sites-available/<HOSTNAME>`"
|
||||
```bash
|
||||
#################################################################
|
||||
# EXCHANGE ALL <HOSTNAME>, <WSS_PORT> AND <PATH_TO> VARIABLES ! #
|
||||
#################################################################
|
||||
#####################################################
|
||||
# EXCHANGE ALL <HOSTNAME> & <WSS_PORT> VARIABLES ! #
|
||||
####################################################
|
||||
|
||||
# Reversed proxy configuration for landing page
|
||||
server {
|
||||
@@ -744,15 +757,27 @@ server {
|
||||
|
||||
# WSS configuration
|
||||
server {
|
||||
listen <WSS_PORT>;
|
||||
listen <WSS_PORT> ssl http2;
|
||||
listen [::]:<WSS_PORT> ssl http2;
|
||||
|
||||
server_name <HOSTNAME>;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/<HOSTNAME>/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/<HOSTNAME>/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location / {
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
|
||||
@@ -230,11 +230,6 @@ Run the node with custom `--id` without initialising:
|
||||
./nym-node run --id <ID> --deny-init --mode mixnode --accept-operator-terms-and-conditions
|
||||
```
|
||||
|
||||
Run the node with custom `--id` without initialising:
|
||||
```sh
|
||||
./nym-node run --id <ID> --deny-init --mode entry-gateway --accept-operator-terms-and-conditions
|
||||
```
|
||||
|
||||
### Migrate
|
||||
|
||||
```admonish caution
|
||||
|
||||
@@ -11,6 +11,6 @@ If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog
|
||||
|
||||
## Where can I get more help?
|
||||
|
||||
The fastest way to reach one of us or get a help from the community, visit our [Telegram Node Setup Help Chat](https://t.me/nymchan_help_chat) or head to our [Discord](https://discord.gg/nymproject).
|
||||
The fastest way to reach one of us or get a help from the community, visit our [Telegram Node Setup Help Chat](https://t.me/nymchan_help_chat) or head to our [Discord](https://nymtech.net/go/discord).
|
||||
|
||||
For more tech heavy question join our [Matrix core community channel](https://matrix.to/#/#general:nymtech.chat), where you can meet other builders and Nym core team members.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.36"
|
||||
version = "1.1.37"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import { TwitterIcon } from '../icons/socials/TwitterIcon'
|
||||
import { DiscordIcon } from '../icons/socials/DiscordIcon'
|
||||
|
||||
// socials
|
||||
export const TELEGRAM_LINK = 'https://t.me/nymchan'
|
||||
export const TWITTER_LINK = 'https://twitter.com/nymproject'
|
||||
export const GITHUB_LINK = 'https://github.com/nymtech'
|
||||
export const DISCORD_LINK = 'https://discord.gg/nymproject'
|
||||
export const TELEGRAM_LINK = 'https://nymtech.net/go/telegram';
|
||||
export const TWITTER_LINK = 'https://nymtech.net/go/x';
|
||||
export const GITHUB_LINK = 'https://nymtech.net/go/github';
|
||||
export const DISCORD_LINK = 'https://nymtech.net/go/discord';
|
||||
|
||||
export const Socials: FCWithChildren<{ isFooter?: boolean }> = ({
|
||||
isFooter = false,
|
||||
|
||||
@@ -7,10 +7,10 @@ import { TwitterIcon } from '../icons/socials/TwitterIcon';
|
||||
import { DiscordIcon } from '../icons/socials/DiscordIcon';
|
||||
|
||||
// socials
|
||||
export const TELEGRAM_LINK = 'https://t.me/nymchan';
|
||||
export const TWITTER_LINK = 'https://twitter.com/nymproject';
|
||||
export const GITHUB_LINK = 'https://github.com/nymtech';
|
||||
export const DISCORD_LINK = 'https://discord.gg/nymproject';
|
||||
export const TELEGRAM_LINK = 'https://nymtech.net/go/telegram';
|
||||
export const TWITTER_LINK = 'https://nymtech.net/go/x';
|
||||
export const GITHUB_LINK = 'https://nymtech.net/go/github';
|
||||
export const DISCORD_LINK = 'https://nymtech.net/go/discord';
|
||||
|
||||
export const Socials: FCWithChildren<{ isFooter?: boolean }> = ({ isFooter }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::commands::upgrade_helpers;
|
||||
use log::{error, info};
|
||||
use nym_config::{save_formatted_config_to_file, OptionalSet};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
@@ -122,8 +121,6 @@ pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), Ga
|
||||
}
|
||||
|
||||
pub(crate) fn try_load_current_config(id: &str) -> Result<Config, GatewayError> {
|
||||
upgrade_helpers::try_upgrade_config(id)?;
|
||||
|
||||
Config::read_from_default_path(id).map_err(|err| {
|
||||
error!(
|
||||
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::Cli;
|
||||
use anyhow::bail;
|
||||
use clap::CommandFactory;
|
||||
use clap::Subcommand;
|
||||
use log::warn;
|
||||
use log::{error, warn};
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use std::io::IsTerminal;
|
||||
use std::time::Duration;
|
||||
@@ -17,7 +18,6 @@ pub(crate) mod run;
|
||||
pub(crate) mod setup_ip_packet_router;
|
||||
pub(crate) mod setup_network_requester;
|
||||
pub(crate) mod sign;
|
||||
mod upgrade_helpers;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
@@ -55,6 +55,12 @@ pub(crate) enum Commands {
|
||||
pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> {
|
||||
let bin_name = "nym-gateway";
|
||||
|
||||
if !args.force_run {
|
||||
let msg = "standalone gateways have been deprecated - please migrate to a `nym-node` via `nym-node migrate gateways` command";
|
||||
error!("{msg}");
|
||||
bail!("{msg}")
|
||||
}
|
||||
|
||||
warn!("standalone gateways have been deprecated - please consider migrating it to a `nym-node` via `nym-node migrate gateway` command");
|
||||
if std::io::stdout().is_terminal() {
|
||||
// if user is running it in terminal session,
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use log::info;
|
||||
use nym_gateway::config::old_config_v1_1_20::ConfigV1_1_20;
|
||||
use nym_gateway::config::old_config_v1_1_28::ConfigV1_1_28;
|
||||
use nym_gateway::config::old_config_v1_1_29::ConfigV1_1_29;
|
||||
use nym_gateway::config::old_config_v1_1_31::ConfigV1_1_31;
|
||||
use nym_gateway::config::old_config_v1_1_36::ConfigV1_1_36;
|
||||
use nym_gateway::config::{default_config_filepath, Config};
|
||||
use nym_gateway::error::GatewayError;
|
||||
|
||||
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, GatewayError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.20 (which is incompatible with the current, i.e. 1.1.21+)
|
||||
let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.20 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_28 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_29 = updated_step1.into();
|
||||
let updated_step3: ConfigV1_1_31 = updated_step2.into();
|
||||
let updated_step4: ConfigV1_1_36 = updated_step3.into();
|
||||
let updated: Config = updated_step4.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_28_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.28 (which is incompatible with the current, i.e. 1.1.29+)
|
||||
let Ok(old_config) = ConfigV1_1_28::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.28 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_29 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_31 = updated_step1.into();
|
||||
let updated_step3: ConfigV1_1_36 = updated_step2.into();
|
||||
let updated: Config = updated_step3.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_29_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.29 (which is incompatible with the current, i.e. 1.1.30+)
|
||||
let Ok(old_config) = ConfigV1_1_29::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.29 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_31 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_36 = updated_step1.into();
|
||||
let updated: Config = updated_step2.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_31_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.31 (which is incompatible with the current, i.e. 1.1.32+)
|
||||
let Ok(old_config) = ConfigV1_1_31::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.31 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_36 = old_config.into();
|
||||
let updated: Config = updated_step1.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_36_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.36 (which is incompatible with the current, i.e. 1.1.37+)
|
||||
let Ok(old_config) = ConfigV1_1_36::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.36 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn try_upgrade_config(id: &str) -> Result<(), GatewayError> {
|
||||
if try_upgrade_v1_1_20_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_28_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_29_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_31_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_36_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -22,11 +22,6 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub use crate::config::persistence::paths::GatewayPaths;
|
||||
|
||||
pub mod old_config_v1_1_20;
|
||||
pub mod old_config_v1_1_28;
|
||||
pub mod old_config_v1_1_29;
|
||||
pub mod old_config_v1_1_31;
|
||||
pub mod old_config_v1_1_36;
|
||||
pub mod persistence;
|
||||
mod template;
|
||||
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::old_config_v1_1_28::{
|
||||
ConfigV1_1_28, DebugV1_1_28, GatewayPathsV1_1_28, GatewayV1_1_28, KeysPathsV1_1_28,
|
||||
LoggingSettingsV1_1_28,
|
||||
};
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_validator_client::nyxd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||
const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
const DEFAULT_MIX_LISTENING_PORT: u16 = 1789;
|
||||
const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000;
|
||||
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
/// returns a `0.0.0.0` / INADDR_ANY
|
||||
fn bind_all_address() -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
pub struct ConfigV1_1_20 {
|
||||
gateway: GatewayV1_1_20,
|
||||
|
||||
#[serde(default)]
|
||||
logging: LoggingV1_1_20,
|
||||
#[serde(default)]
|
||||
debug: DebugV1_1_20,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_20> for ConfigV1_1_28 {
|
||||
fn from(value: ConfigV1_1_20) -> Self {
|
||||
ConfigV1_1_28 {
|
||||
gateway: GatewayV1_1_28 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
storage_paths: GatewayPathsV1_1_28 {
|
||||
keys: KeysPathsV1_1_28 {
|
||||
private_identity_key_file: value.gateway.private_identity_key_file,
|
||||
public_identity_key_file: value.gateway.public_identity_key_file,
|
||||
private_sphinx_key_file: value.gateway.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.gateway.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.gateway.persistent_storage,
|
||||
},
|
||||
logging: value.logging.into(),
|
||||
debug: value.debug.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_20 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
// unless this is run on some esoteric system, it should not fail thus the expect is fine
|
||||
#[allow(clippy::expect_used)]
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("gateways")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_20 {
|
||||
version: String,
|
||||
id: String,
|
||||
|
||||
#[serde(default)]
|
||||
only_coconut_credentials: bool,
|
||||
#[serde(default = "bind_all_address")]
|
||||
listening_address: IpAddr,
|
||||
announce_address: String,
|
||||
mix_port: u16,
|
||||
clients_port: u16,
|
||||
private_identity_key_file: PathBuf,
|
||||
public_identity_key_file: PathBuf,
|
||||
private_sphinx_key_file: PathBuf,
|
||||
public_sphinx_key_file: PathBuf,
|
||||
enabled_statistics: bool,
|
||||
statistics_service_url: Url,
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
nym_api_urls: Vec<Url>,
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
nyxd_urls: Vec<Url>,
|
||||
cosmos_mnemonic: bip39::Mnemonic,
|
||||
nym_root_directory: PathBuf,
|
||||
persistent_storage: PathBuf,
|
||||
wallet_address: Option<nyxd::AccountId>,
|
||||
}
|
||||
|
||||
impl Default for GatewayV1_1_20 {
|
||||
fn default() -> Self {
|
||||
// allow usage of `expect` here as our default mainnet values should have been well-formed.
|
||||
#[allow(clippy::expect_used)]
|
||||
GatewayV1_1_20 {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: "".to_string(),
|
||||
only_coconut_credentials: false,
|
||||
listening_address: bind_all_address(),
|
||||
announce_address: "127.0.0.1".to_string(),
|
||||
mix_port: DEFAULT_MIX_LISTENING_PORT,
|
||||
clients_port: DEFAULT_CLIENT_LISTENING_PORT,
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
private_sphinx_key_file: Default::default(),
|
||||
public_sphinx_key_file: Default::default(),
|
||||
enabled_statistics: false,
|
||||
statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS)
|
||||
.expect("Invalid default statistics service URL"),
|
||||
nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")],
|
||||
nyxd_urls: vec![Url::from_str(NYXD_URL).expect("Invalid default nyxd URL")],
|
||||
cosmos_mnemonic: bip39::Mnemonic::generate(24)
|
||||
.expect("failed to generate fresh mnemonic"),
|
||||
nym_root_directory: ConfigV1_1_20::default_root_directory(),
|
||||
persistent_storage: Default::default(),
|
||||
wallet_address: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct LoggingV1_1_20 {}
|
||||
|
||||
impl From<LoggingV1_1_20> for LoggingSettingsV1_1_28 {
|
||||
fn from(_value: LoggingV1_1_20) -> Self {
|
||||
LoggingSettingsV1_1_28 {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
struct DebugV1_1_20 {
|
||||
#[serde(with = "humantime_serde")]
|
||||
packet_forwarding_initial_backoff: Duration,
|
||||
#[serde(with = "humantime_serde")]
|
||||
packet_forwarding_maximum_backoff: Duration,
|
||||
#[serde(with = "humantime_serde")]
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
#[serde(with = "humantime_serde")]
|
||||
presence_sending_delay: Duration,
|
||||
stored_messages_filename_length: u16,
|
||||
message_retrieval_limit: i64,
|
||||
use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl From<DebugV1_1_20> for DebugV1_1_28 {
|
||||
fn from(value: DebugV1_1_20) -> Self {
|
||||
DebugV1_1_28 {
|
||||
packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.presence_sending_delay,
|
||||
stored_messages_filename_length: value.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.use_legacy_framed_packet_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_20 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_20 {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
// TODO: remember to change it in one of future releases!!
|
||||
use_legacy_framed_packet_version: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::old_config_v1_1_29::{
|
||||
ConfigV1_1_29, DebugV1_1_29, GatewayPathsV1_1_29, GatewayV1_1_29, KeysPathsV1_1_29,
|
||||
LoggingSettingsV1_1_29,
|
||||
};
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_GATEWAYS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to gateways's config file.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KeysPathsV1_1_28 {
|
||||
pub private_identity_key_file: PathBuf,
|
||||
pub public_identity_key_file: PathBuf,
|
||||
pub private_sphinx_key_file: PathBuf,
|
||||
pub public_sphinx_key_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPathsV1_1_28 {
|
||||
pub keys: KeysPathsV1_1_28,
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettingsV1_1_28 {}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_28 {
|
||||
pub gateway: GatewayV1_1_28,
|
||||
|
||||
pub storage_paths: GatewayPathsV1_1_28,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV1_1_28,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_28,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_28 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_28> for ConfigV1_1_29 {
|
||||
fn from(value: ConfigV1_1_28) -> Self {
|
||||
ConfigV1_1_29 {
|
||||
// \/ ADDED
|
||||
save_path: None,
|
||||
// /\ ADDED
|
||||
gateway: GatewayV1_1_29 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
storage_paths: GatewayPathsV1_1_29 {
|
||||
keys: KeysPathsV1_1_29 {
|
||||
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
|
||||
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
|
||||
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.storage_paths.clients_storage,
|
||||
|
||||
// \/ ADDED
|
||||
network_requester_config: None,
|
||||
// /\ ADDED
|
||||
},
|
||||
|
||||
// \/ ADDED
|
||||
network_requester: Default::default(),
|
||||
// /\ ADDED
|
||||
logging: LoggingSettingsV1_1_29 {},
|
||||
debug: DebugV1_1_29 {
|
||||
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.debug.presence_sending_delay,
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_28 {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
#[serde(default)]
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// Whether gateway collects and sends anonymized statistics
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
/// Domain address of the statistics service
|
||||
pub statistics_service_url: Url,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_28 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub presence_sending_delay: Duration,
|
||||
|
||||
/// Length of filenames for new client messages.
|
||||
pub stored_messages_filename_length: u16,
|
||||
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_28 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_28 {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Host;
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use super::old_config_v1_1_31::{
|
||||
ConfigV1_1_31, DebugV1_1_31, GatewayPathsV1_1_31, GatewayV1_1_31, KeysPathsV1_1_31,
|
||||
LoggingSettingsV1_1_31, NetworkRequesterV1_1_31,
|
||||
};
|
||||
|
||||
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_GATEWAYS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to gateways's config file.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KeysPathsV1_1_29 {
|
||||
pub private_identity_key_file: PathBuf,
|
||||
pub public_identity_key_file: PathBuf,
|
||||
pub private_sphinx_key_file: PathBuf,
|
||||
pub public_sphinx_key_file: PathBuf,
|
||||
}
|
||||
|
||||
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let path = PathBuf::deserialize(deserializer)?;
|
||||
if path.as_os_str().is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(path))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPathsV1_1_29 {
|
||||
pub keys: KeysPathsV1_1_29,
|
||||
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
#[serde(deserialize_with = "de_maybe_path")]
|
||||
pub network_requester_config: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettingsV1_1_29 {}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_29 {
|
||||
#[serde(skip)]
|
||||
pub save_path: Option<PathBuf>,
|
||||
|
||||
pub gateway: GatewayV1_1_29,
|
||||
|
||||
pub storage_paths: GatewayPathsV1_1_29,
|
||||
|
||||
pub network_requester: NetworkRequesterV1_1_29,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV1_1_29,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_29,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_29 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_29> for ConfigV1_1_31 {
|
||||
fn from(value: ConfigV1_1_29) -> Self {
|
||||
ConfigV1_1_31 {
|
||||
save_path: value.save_path,
|
||||
|
||||
// \/ ADDED
|
||||
host: Host {
|
||||
// this is a very bad default!
|
||||
public_ips: vec![value.gateway.listening_address],
|
||||
hostname: None,
|
||||
},
|
||||
// /\ ADDED
|
||||
|
||||
// \/ ADDED
|
||||
http: Default::default(),
|
||||
// /\ ADDED
|
||||
gateway: GatewayV1_1_31 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
|
||||
// \/ ADDED
|
||||
clients_wss_port: None,
|
||||
// /\ ADDED
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
// \/ ADDED
|
||||
wireguard: Default::default(),
|
||||
// /\ ADDED
|
||||
storage_paths: GatewayPathsV1_1_31 {
|
||||
keys: KeysPathsV1_1_31 {
|
||||
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
|
||||
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
|
||||
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.storage_paths.clients_storage,
|
||||
network_requester_config: value.storage_paths.network_requester_config,
|
||||
},
|
||||
network_requester: NetworkRequesterV1_1_31 {
|
||||
enabled: value.network_requester.enabled,
|
||||
},
|
||||
logging: LoggingSettingsV1_1_31 {},
|
||||
debug: DebugV1_1_31 {
|
||||
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.debug.presence_sending_delay,
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_29 {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
#[serde(default)]
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// Whether gateway collects and sends anonymized statistics
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
/// Domain address of the statistics service
|
||||
pub statistics_service_url: Url,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkRequesterV1_1_29 {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for NetworkRequesterV1_1_29 {
|
||||
fn default() -> Self {
|
||||
NetworkRequesterV1_1_29 { enabled: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_29 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub presence_sending_delay: Duration,
|
||||
|
||||
/// Length of filenames for new client messages.
|
||||
pub stored_messages_filename_length: u16,
|
||||
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_29 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_29 {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use nym_network_defaults::WG_PORT;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::io;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use super::old_config_v1_1_36::{
|
||||
ConfigV1_1_36, DebugV1_1_36, GatewayPathsV1_1_36, GatewayV1_1_36, KeysPathsV1_1_36,
|
||||
LoggingSettingsV1_1_36, NetworkRequesterV1_1_36, WireguardPathsV1_1_36, WireguardV1_1_36,
|
||||
};
|
||||
use super::{Host, Http};
|
||||
|
||||
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
fn de_maybe_port<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let port = u16::deserialize(deserializer)?;
|
||||
if port == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(port))
|
||||
}
|
||||
}
|
||||
|
||||
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let path = PathBuf::deserialize(deserializer)?;
|
||||
if path.as_os_str().is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_GATEWAYS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to gateways's config file.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_31 {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
pub host: Host,
|
||||
|
||||
#[serde(default)]
|
||||
pub http: Http,
|
||||
|
||||
pub gateway: GatewayV1_1_31,
|
||||
|
||||
#[serde(default)]
|
||||
// currently not really used for anything useful
|
||||
pub wireguard: WireguardV1_1_31,
|
||||
|
||||
pub storage_paths: GatewayPathsV1_1_31,
|
||||
|
||||
pub network_requester: NetworkRequesterV1_1_31,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV1_1_31,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_31,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_31 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_31> for ConfigV1_1_36 {
|
||||
fn from(value: ConfigV1_1_31) -> Self {
|
||||
Self {
|
||||
save_path: value.save_path,
|
||||
host: value.host,
|
||||
http: value.http,
|
||||
gateway: GatewayV1_1_36 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
clients_wss_port: value.gateway.clients_wss_port,
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
wireguard: WireguardV1_1_36 {
|
||||
enabled: value.wireguard.enabled,
|
||||
bind_address: value.wireguard.bind_address,
|
||||
announced_port: value.wireguard.announced_port,
|
||||
private_network_prefix: Default::default(),
|
||||
storage_paths: WireguardPathsV1_1_36 {
|
||||
// no fields (yet)
|
||||
},
|
||||
},
|
||||
storage_paths: GatewayPathsV1_1_36 {
|
||||
keys: KeysPathsV1_1_36 {
|
||||
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
|
||||
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
|
||||
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.storage_paths.clients_storage,
|
||||
network_requester_config: value.storage_paths.network_requester_config,
|
||||
// \/ ADDED
|
||||
ip_packet_router_config: Default::default(),
|
||||
// /\ ADDED
|
||||
},
|
||||
network_requester: NetworkRequesterV1_1_36 {
|
||||
enabled: value.network_requester.enabled,
|
||||
},
|
||||
// \/ ADDED
|
||||
ip_packet_router: Default::default(),
|
||||
// /\ ADDED
|
||||
logging: LoggingSettingsV1_1_36 {
|
||||
// no fields (yet)
|
||||
},
|
||||
debug: DebugV1_1_36 {
|
||||
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.debug.presence_sending_delay,
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_31 {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
#[serde(default)]
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// If applicable, announced port for listening for secure websocket client traffic.
|
||||
/// (default: None)
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub clients_wss_port: Option<u16>,
|
||||
|
||||
/// Whether gateway collects and sends anonymized statistics
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
/// Domain address of the statistics service
|
||||
pub statistics_service_url: Url,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardV1_1_31 {
|
||||
/// Specifies whether the wireguard service is enabled on this node.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Socket address this node will use for binding its wireguard interface.
|
||||
/// default: `0.0.0.0:51820`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
pub announced_port: u16,
|
||||
|
||||
/// Paths for wireguard keys, client registries, etc.
|
||||
pub storage_paths: WireguardPathsV1_1_31,
|
||||
}
|
||||
|
||||
impl Default for WireguardV1_1_31 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
|
||||
announced_port: WG_PORT,
|
||||
storage_paths: WireguardPathsV1_1_31 {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardPathsV1_1_31 {
|
||||
// pub keys:
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPathsV1_1_31 {
|
||||
pub keys: KeysPathsV1_1_31,
|
||||
|
||||
/// Path to sqlite database containing all persistent data: messages for offline clients,
|
||||
/// derived shared keys and available client bandwidths.
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
/// Path to the configuration of the embedded network requester.
|
||||
#[serde(deserialize_with = "de_maybe_path")]
|
||||
pub network_requester_config: Option<PathBuf>,
|
||||
// pub node_description: PathBuf,
|
||||
|
||||
// pub cosmos_bip39_mnemonic: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct KeysPathsV1_1_31 {
|
||||
/// Path to file containing private identity key.
|
||||
pub private_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public identity key.
|
||||
pub public_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing private sphinx key.
|
||||
pub private_sphinx_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public sphinx key.
|
||||
pub public_sphinx_key_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkRequesterV1_1_31 {
|
||||
/// Specifies whether network requester service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for NetworkRequesterV1_1_31 {
|
||||
fn default() -> Self {
|
||||
Self { enabled: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettingsV1_1_31 {
|
||||
// well, we need to implement something here at some point...
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_31 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub presence_sending_delay: Duration,
|
||||
|
||||
/// Length of filenames for new client messages.
|
||||
pub stored_messages_filename_length: u16,
|
||||
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_31 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user