Compare commits

..

8 Commits

Author SHA1 Message Date
benedettadavico b7aa84cd5a updating mainnet mixnet conract address 2022-11-15 19:54:28 +01:00
Fouad bfbd509e4b Update/last minute release updates (#1753)
* fix vesting update bond settings

* style and text updates

* show tx fee when updating node settings

* allow cost param update with vesting tokens
2022-11-10 16:22:09 +00:00
Jon Häggblad 09b9601c7e Update client-core to 1.1.0 2022-11-10 09:38:38 +01:00
Mark Sinclair 2a1dd138e0 GH Actions: Install same dependencies as build..yml 2022-11-09 18:21:28 +00:00
Mark Sinclair 9874daa061 Release v1.1.0: bump versions and update CHANGELOGs (#1746)
* Bump version of nym-cli to 1.1.0 and move CHANGELOG to standalone file

* Bump version of nym-connect to v1.1.0 and update CHANGELOG

* Bump version of nym-wallet to v1.1.0 and update CHANGELOG

* Bump version of explorer-api to v1.1.0

* Bump versions of binaries (native-client, socks5-client, mixnode, gateway, network-requester) to v1.1.0

* Bump version of validator-api to v1.1.0

* Bump version of mixnet contract to v1.1.0 (vesting contract already v1.1.0 from #1472)

* Bump Nym Platform version to v1.1.0 and update CHANGELOG

* Update CHANGELOG.md

* Update CHANGELOG.md

* Updated changelog with v2-related changes

* Update CHANGELOG.md

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Fouad <fmtabbara@hotmail.co.uk>
Co-authored-by: Pierre Dommerc <dommerc.pierre@gmail.com>
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: Jess <31625607+jessgess@users.noreply.github.com>
2022-11-09 15:04:41 +00:00
Fouad baa61c07d5 Bug fix/ne snag list (#1741)
* use uncapped saturation

* display uncapped saturation

update profit margin tooltip

update operating cost

update rewards tooltip

update stake saturation tooltip

update reward tooltips

update profit margin tooltip

* allow full gateway field to be clickable

use uncapped saturation on mixnode details page
2022-11-09 12:39:14 +00:00
Jon Häggblad 62e9c8236a Remove log from vesting-contract (#1745)
* Remove log from vesting-contract

* rustfmt
2022-11-09 11:04:32 +01:00
durch cf268ffcd5 Actually save updated pledge cap 2022-11-09 10:19:21 +01:00
131 changed files with 3121 additions and 3066 deletions
Vendored
BIN
View File
Binary file not shown.
+14 -27
View File
@@ -1,49 +1,36 @@
name: Daily security audit
on:
schedule:
- cron: '5 9 * * *'
on: workflow_dispatch
jobs:
cargo-deny:
security_audit:
runs-on: ubuntu-latest
steps:
- name: Checkout repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
with:
toolchain: stable
- name: Install cargo deny
run: cargo install --locked cargo-deny
- name: Run cargo deny
run: cargo deny check advisories --hide-inclusion-graph &> .github/workflows/support-files/notifications/deny.message
- uses: actions/upload-artifact@v3
with:
name: report
path: .github/workflows/support-files/notifications/deny.message
token: ${{ secrets.GITHUB_TOKEN }}
notification:
needs: cargo-deny
if: ${{ failure() }}
needs: security_audit
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v2
- name: Download report from previous job
uses: actions/download-artifact@v3
with:
name: report
path: .github/workflows/support-files/notifications
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: security
NYM_PROJECT_NAME: "Daily security report"
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym daily audit"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "security"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "test"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -25,6 +25,9 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check the release tag starts with `nym-binaries-`
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot');
let context = {
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security'],
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect'],
};
/**
@@ -1,24 +0,0 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
const { Octokit, App } = require('octokit');
async function addToContextAndValidate(context) {
return
}
async function getMessageBody(context) {
try {
const source = fs
.readFileSync("deny.message").toString();
return source;
} catch (error) {
console.error(error);
}
}
module.exports = {
addToContextAndValidate,
getMessageBody,
};
-32
View File
@@ -1,32 +0,0 @@
name: Tests for validator API
on:
push:
paths:
- "validator-api/tests/**"
defaults:
run:
working-directory: validator-api/tests
jobs:
test:
name: validator api tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Node v18
uses: actions/setup-node@v3
with:
node-version: 18.1.0
- name: Install yarn
run: yarn install
- name: Run yarn
run: yarn
- name: Launch tests
run: yarn test
working-directory: validator-api/tests
+22 -22
View File
@@ -2,39 +2,40 @@
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
### Added
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
- common/ledger: new library for communicating with a Ledger device ([#1640])
- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to cmpute reward estimation endpoint
- native-client/socks5-client/network-requester: improve handling error cases ([#1713])
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 100_000 NYM
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
- common/ledger: new library for communicating with a Ledger device ([#1640])
- native-client/socks5-client/wasm-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
- native-client/socks5-client/wasm-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
- native-client/socks5-client/wasm-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to compute reward estimation endpoint
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
### Fixed
- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
- validator-api should now correctly update historical uptimes for all mixnodes and gateways every 24h ([#1721])
- socks5-client: fix bug where in some cases packet reordering could trigger a connection being closed too early ([#1702],[#1724])
- validator-api: mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
- validator-api: should now correctly update historical uptimes for all mixnodes and gateways every 24h ([#1721])
### Changed
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
- moved `Percent` struct to to `contracts-common`, change affects explorer-api
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703],[#1725])
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
- moved `Percent` struct to `contracts-common`, change affects explorer-api
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
- validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472])
- validator-api: changes to internal data structures due to the mixnet contract revamp ([#1472])
- validator-api: split epoch-operations into multiple separate transactions ([#1472])
[#1472]: https://github.com/nymtech/nym/pull/1472
[#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558
[#1577]: https://github.com/nymtech/nym/pull/1577
@@ -42,16 +43,15 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645
[#1611]: https://github.com/nymtech/nym/pull/1611
[#1664]: https://github.com/nymtech/nym/pull/1664
[#1666]: https://github.com/nymtech/nym/pull/1645
[#1669]: https://github.com/nymtech/nym/pull/1669
[#1671]: https://github.com/nymtech/nym/pull/1671
[#1673]: https://github.com/nymtech/nym/pull/1673
[#1681]: https://github.com/nymtech/nym/pull/1681
[#1687]: https://github.com/nymtech/nym/pull/1687
[#1702]: https://github.com/nymtech/nym/pull/1702
[#1703]: https://github.com/nymtech/nym/pull/1703
[#1713]: https://github.com/nymtech/nym/pull/1713
[#1721]: https://github.com/nymtech/nym/pull/1721
[#1724]: https://github.com/nymtech/nym/pull/1724
[#1725]: https://github.com/nymtech/nym/pull/1725
Generated
+9 -12
View File
@@ -578,7 +578,7 @@ dependencies = [
[[package]]
name = "client-core"
version = "1.0.1"
version = "1.1.0"
dependencies = [
"config",
"crypto",
@@ -1582,7 +1582,7 @@ dependencies = [
[[package]]
name = "explorer-api"
version = "1.0.1"
version = "1.1.0"
dependencies = [
"chrono",
"clap 3.2.8",
@@ -3070,7 +3070,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"base64",
@@ -3122,7 +3122,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"clap 3.2.8",
"client-core",
@@ -3147,7 +3147,6 @@ dependencies = [
"serde_json",
"sled",
"task",
"thiserror",
"tokio",
"tokio-tungstenite 0.14.0",
"topology",
@@ -3160,7 +3159,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"anyhow",
"async-trait",
@@ -3207,7 +3206,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"anyhow",
"bs58",
@@ -3249,7 +3248,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"async-trait",
"clap 3.2.8",
@@ -3296,7 +3295,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"clap 3.2.8",
"client-core",
@@ -3324,7 +3323,6 @@ dependencies = [
"snafu 0.6.10",
"socks5-requests",
"task",
"thiserror",
"tokio",
"topology",
"url",
@@ -3361,7 +3359,7 @@ dependencies = [
[[package]]
name = "nym-validator-api"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"anyhow",
"async-trait",
@@ -6516,7 +6514,6 @@ version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"log",
"mixnet-contract-common",
"schemars",
"serde",
-3
View File
@@ -122,6 +122,3 @@ mixnet-opt: wasm
generate-typescript:
cd tools/ts-rs-cli && cargo run && cd ../..
yarn types:lint:fix
run-validator-tests:
cd validator-api/tests/functional_test && yarn test
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.0.1"
version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
@@ -296,7 +296,7 @@ where
async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message");
let (next_message, fragment_id) = match next_message {
let next_message = match next_message {
StreamMessage::Cover => {
// TODO for way down the line: in very rare cases (during topology update) we might have
// to wait a really tiny bit before actually obtaining the permit hence messing with our
@@ -315,24 +315,20 @@ where
}
let topology_ref = topology_ref_option.unwrap();
(
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
self.config.cover_packet_size,
)
.expect(
"Somehow failed to generate a loop cover message with a valid topology",
),
None,
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
self.config.cover_packet_size,
)
.expect("Somehow failed to generate a loop cover message with a valid topology")
}
StreamMessage::Real(real_message) => {
(real_message.mix_packet, Some(real_message.fragment_id))
self.sent_notify(real_message.fragment_id);
real_message.mix_packet
}
};
@@ -340,12 +336,6 @@ where
log::error!("Failed to send - channel closed: {}", err);
}
// notify ack controller about sending our message only after we actually managed to push it
// through the channel
if let Some(fragment_id) = fragment_id {
self.sent_notify(fragment_id);
}
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
@@ -57,15 +57,24 @@ impl<'a> TopologyReadPermit<'a> {
) -> Option<&'a NymTopology> {
// Note: implicit deref with Deref for TopologyReadPermit is happening here
let topology_ref_option = self.permit.as_ref();
topology_ref_option.as_ref().filter(|topology_ref| {
!(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
match topology_ref_option {
None => None,
Some(topology_ref) => {
// see if it's possible to route the packet to both gateways
if !topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
} else {
false
}
{
None
} else {
false
})
})
Some(topology_ref)
}
}
}
}
}
-2
View File
@@ -24,6 +24,4 @@ pub enum ClientCoreError {
ListOfValidatorApisIsEmpty,
#[error("Could not load existing gateway configuration: {0}")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("The current network topology seem to be insufficient to route any packets through")]
InsufficientNetworkTopology,
}
+1 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.0.2"
version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -27,7 +27,6 @@ pretty_env_logger = "0.4" # for formatting log messages
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
tokio-tungstenite = "0.14" # websocket
+8 -13
View File
@@ -18,7 +18,6 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
@@ -34,7 +33,6 @@ use nymsphinx::receiver::ReconstructedMessage;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use crate::websocket;
pub(crate) mod config;
@@ -234,7 +232,7 @@ impl NymClient {
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), ClientError> {
) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -249,16 +247,14 @@ impl NymClient {
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -332,8 +328,8 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) -> Result<(), ClientError> {
let shutdown = self.start().await?;
pub async fn run_forever(&mut self) {
let shutdown = self.start().await;
wait_for_signal().await;
println!(
@@ -350,10 +346,9 @@ impl NymClient {
//shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-client");
Ok(())
}
pub async fn start(&mut self) -> Result<ShutdownNotifier, ClientError> {
pub async fn start(&mut self) -> ShutdownNotifier {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -384,7 +379,7 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?;
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
@@ -448,6 +443,6 @@ impl NymClient {
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
Ok(shutdown)
shutdown
}
}
+2 -4
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
@@ -84,17 +83,16 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool,
}
pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> {
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-native-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await?,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
Ok(())
}
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
+4 -5
View File
@@ -4,7 +4,6 @@
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
error::ClientError,
};
use clap::Args;
@@ -74,14 +73,14 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
pub(crate) async fn execute(args: &Run) {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return Err(ClientError::FailedToLoadConfig(id.to_string()));
return;
}
};
@@ -90,8 +89,8 @@ pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
if !version_check(&config) {
error!("failed the local version check");
return Err(ClientError::FailedLocalVersionCheck);
return;
}
NymClient::new(config).run_forever().await
NymClient::new(config).run_forever().await;
}
-23
View File
@@ -1,23 +0,0 @@
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
}
-1
View File
@@ -2,5 +2,4 @@
// SPDX-License-Identifier: Apache-2.0
pub mod client;
pub mod error;
pub mod websocket;
+2 -4
View File
@@ -2,23 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use error::ClientError;
use logging::setup_logging;
use network_defaults::setup_env;
pub mod client;
pub mod commands;
pub mod error;
pub mod websocket;
#[tokio::main]
async fn main() -> Result<(), ClientError> {
async fn main() {
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await
commands::execute(&args).await;
}
fn banner() -> String {
+1 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.0.2"
version = "1.1.0"
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"
@@ -20,7 +20,6 @@ pretty_env_logger = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
snafu = "0.6"
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
url = "2.2"
+10 -19
View File
@@ -4,7 +4,6 @@
use std::sync::atomic::Ordering;
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
@@ -24,7 +23,6 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
@@ -234,7 +232,7 @@ impl NymClient {
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), Socks5ClientError> {
) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -249,16 +247,14 @@ impl NymClient {
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -297,8 +293,8 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
pub async fn run_forever(&mut self) {
let mut shutdown = self.start().await;
wait_for_signal().await;
log::info!("Sending shutdown");
@@ -309,15 +305,11 @@ impl NymClient {
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
Ok(())
}
// Variant of `run_forever` that listends for remote control messages
pub async fn run_and_listen(
&mut self,
mut receiver: Socks5ControlMessageReceiver,
) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
let mut shutdown = self.start().await;
tokio::select! {
message = receiver.next() => {
log::debug!("Received message: {:?}", message);
@@ -343,10 +335,9 @@ impl NymClient {
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
Ok(())
}
pub async fn start(&mut self) -> Result<ShutdownNotifier, Socks5ClientError> {
pub async fn start(&mut self) -> ShutdownNotifier {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -377,7 +368,7 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?;
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
@@ -426,6 +417,6 @@ impl NymClient {
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
Ok(shutdown)
shutdown
}
}
+2 -4
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
@@ -84,17 +83,16 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool,
}
pub(crate) async fn execute(args: &Cli) -> Result<(), Socks5ClientError> {
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-socks5-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await?,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
Ok(())
}
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
+4 -5
View File
@@ -4,7 +4,6 @@
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
error::Socks5ClientError,
};
use clap::Args;
@@ -81,14 +80,14 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) -> Result<(), Socks5ClientError> {
pub(crate) async fn execute(args: &Run) {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
return;
}
};
@@ -97,8 +96,8 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Socks5ClientError> {
if !version_check(&config) {
error!("failed the local version check");
return Err(Socks5ClientError::FailedLocalVersionCheck);
return;
}
NymClient::new(config).run_forever().await
NymClient::new(config).run_forever().await;
}
-23
View File
@@ -1,23 +0,0 @@
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
}
-1
View File
@@ -2,5 +2,4 @@
// SPDX-License-Identifier: Apache-2.0
pub mod client;
pub mod error;
pub mod socks;
+2 -4
View File
@@ -2,23 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use error::Socks5ClientError;
use logging::setup_logging;
use network_defaults::setup_env;
pub mod client;
mod commands;
pub mod error;
pub mod socks;
#[tokio::main]
async fn main() -> Result<(), Socks5ClientError> {
async fn main() {
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await
commands::execute(&args).await;
}
fn banner() -> String {
+3 -5
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.0.1"
version = "1.0.0"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -55,8 +55,6 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release]
wasm-opt = true
wasm-opt = false
[profile.release]
lto = true
opt-level = 'z'
@@ -2,11 +2,6 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
entry: {
bootstrap: './bootstrap.js',
worker: './worker.js',
@@ -27,7 +22,6 @@ module.exports = {
},
],
}),
],
experiments: { syncWebAssembly: true },
};
-1
View File
@@ -73,7 +73,6 @@ async function main() {
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
// only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null)
File diff suppressed because it is too large Load Diff
-2
View File
@@ -1,2 +0,0 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
@@ -11,8 +11,6 @@ use cosmwasm_std::Event;
/// * `event`: event to search through.
/// * `key`: key associated with the particular attribute
pub fn must_find_attribute(event: &Event, key: &str) -> String {
// due to how the function is supposed to work, the unwrap is fine in this instance
#[allow(clippy::unwrap_used)]
may_find_attribute(event, key).unwrap()
}
@@ -1,9 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
pub mod events;
pub mod types;
@@ -72,7 +72,7 @@ impl Percent {
impl Display for Percent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let adjusted = Decimal::from_ratio(100u32, 1u32) * self.0;
let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0;
write!(f, "{}%", adjusted)
}
}
@@ -1,9 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Decimal, Uint128};
pub const TOKEN_SUPPLY: Uint128 = Uint128::new(1_000_000_000_000_000);
use cosmwasm_std::Decimal;
// I'm still not 100% sure how to feel about existence of this file
// This is equivalent of representing our display coin with 6 decimal places.
@@ -4,10 +4,8 @@
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::constants::TOKEN_SUPPLY;
use crate::helpers::IntoBaseDecimal;
use crate::{Addr, MixId};
use cosmwasm_std::{Coin, Decimal, StdResult};
use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -62,11 +60,6 @@ impl Delegation {
height: u64,
proxy: Option<Addr>,
) -> Self {
assert!(
amount.amount <= TOKEN_SUPPLY,
"delegation cannot be larger than the token supply"
);
Delegation {
owner,
mix_id,
@@ -94,8 +87,10 @@ impl Delegation {
(mix_id, owner_proxy_subkey)
}
pub fn dec_amount(&self) -> StdResult<Decimal> {
self.amount.amount.into_base_decimal()
pub fn dec_amount(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.amount.amount, 0).unwrap()
}
pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Decimal, StdError, StdResult, Uint128};
use cosmwasm_std::Decimal;
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128));
@@ -11,23 +11,3 @@ pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
assert!(b - a < epsilon, "{} != {}", a, b)
}
}
pub fn into_base_decimal(val: impl Into<Uint128>) -> StdResult<Decimal> {
val.into_base_decimal()
}
pub trait IntoBaseDecimal {
fn into_base_decimal(self) -> StdResult<Decimal>;
}
impl<T> IntoBaseDecimal for T
where
T: Into<Uint128>,
{
fn into_base_decimal(self) -> StdResult<Decimal> {
let atomics = self.into();
Decimal::from_atomics(atomics, 0).map_err(|_| StdError::GenericErr {
msg: format!("Decimal range exceeded for {atomics} with 0 decimal places."),
})
}
}
@@ -145,17 +145,14 @@ impl JsonSchema for Interval {
impl Interval {
/// Initialize epoch in the contract with default values.
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self {
// if this fails it means the value provided from the chain itself (via cosmwasm) is invalid,
// so we really have to panic here as anything beyond that point would be invalid anyway
#[allow(clippy::expect_used)]
let current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
.expect("The timestamp provided via env.block.time is invalid");
Interval {
id: 0,
epochs_in_interval,
current_epoch_start,
// I really don't see a way for this to fail, unless the blockchain is lying to us
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64
)
.expect("Invalid timestamp from env.block.time"),
current_epoch_id: 0,
epoch_length,
total_elapsed_epochs: 0,
@@ -1,9 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
mod constants;
pub mod delegation;
pub mod error;
@@ -4,14 +4,13 @@
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
use crate::constants::UNIT_DELEGATION_BASE;
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@@ -65,7 +64,7 @@ impl MixNodeDetails {
self.rewarding_details.pending_operator_reward(pledge)
}
pub fn pending_detailed_operator_reward(&self) -> StdResult<Decimal> {
pub fn pending_detailed_operator_reward(&self) -> Decimal {
let pledge = self.original_pledge();
self.rewarding_details
.pending_detailed_operator_reward(pledge)
@@ -108,21 +107,16 @@ impl MixNodeRewarding {
cost_params: MixNodeCostParams,
initial_pledge: &Coin,
current_epoch: EpochId,
) -> Result<Self, MixnetContractError> {
assert!(
initial_pledge.amount <= TOKEN_SUPPLY,
"pledge cannot be larger than the token supply"
);
Ok(MixNodeRewarding {
) -> Self {
MixNodeRewarding {
cost_params,
operator: initial_pledge.amount.into_base_decimal()?,
operator: Decimal::from_atomics(initial_pledge.amount, 0).unwrap(),
delegates: Decimal::zero(),
total_unit_reward: Decimal::zero(),
unit_delegation: UNIT_DELEGATION_BASE,
last_rewarded_epoch: current_epoch,
unique_delegations: 0,
})
}
}
/// Determines whether this node is still bonded. This is performed via a simple check,
@@ -141,30 +135,27 @@ impl MixNodeRewarding {
}
}
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> StdResult<Decimal> {
let initial_dec = original_pledge.amount.into_base_decimal()?;
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> Decimal {
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
if initial_dec > self.operator {
panic!(
"seems slashing has occurred while it has not been implemented nor accounted for!"
)
}
Ok(self.operator - initial_dec)
self.operator - initial_dec
}
pub fn operator_pledge_with_reward(&self, denom: impl Into<String>) -> Coin {
truncate_reward(self.operator, denom)
}
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
let delegator_reward = self.determine_delegation_reward(delegation)?;
Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> Coin {
let delegator_reward = self.determine_delegation_reward(delegation);
truncate_reward(delegator_reward, &delegation.amount.denom)
}
pub fn withdraw_operator_reward(
&mut self,
original_pledge: &Coin,
) -> Result<Coin, MixnetContractError> {
let initial_dec = original_pledge.amount.into_base_decimal()?;
pub fn withdraw_operator_reward(&mut self, original_pledge: &Coin) -> Coin {
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
if initial_dec > self.operator {
panic!(
"seems slashing has occurred while it has not been implemented nor accounted for!"
@@ -173,14 +164,14 @@ impl MixNodeRewarding {
let diff = self.operator - initial_dec;
self.operator = initial_dec;
Ok(truncate_reward(diff, &original_pledge.denom))
truncate_reward(diff, &original_pledge.denom)
}
pub fn withdraw_delegator_reward(
&mut self,
delegation: &mut Delegation,
) -> Result<Coin, MixnetContractError> {
let reward = self.determine_delegation_reward(delegation)?;
let reward = self.determine_delegation_reward(delegation);
self.decrease_delegates_decimal(reward)?;
delegation.cumulative_reward_ratio = self.full_reward_ratio();
@@ -310,27 +301,23 @@ impl MixNodeRewarding {
self.distribute_rewards(reward_distribution, absolute_epoch_id)
}
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> StdResult<Decimal> {
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
let starting_ratio = delegation.cumulative_reward_ratio;
let ending_ratio = self.full_reward_ratio();
let adjust = starting_ratio + self.unit_delegation;
Ok((ending_ratio - starting_ratio) * delegation.dec_amount()? / adjust)
(ending_ratio - starting_ratio) * delegation.dec_amount() / adjust
}
// this updates `unique_delegations` field
pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> {
self.increase_delegates_uint128(amount)?;
pub fn add_base_delegation(&mut self, amount: Uint128) {
self.increase_delegates_uint128(amount);
self.unique_delegations += 1;
Ok(())
}
pub fn increase_delegates_uint128(
&mut self,
amount: Uint128,
) -> Result<(), MixnetContractError> {
self.delegates += amount.into_base_decimal()?;
Ok(())
pub fn increase_delegates_uint128(&mut self, amount: Uint128) {
// the unwrap here is fine as the value is guaranteed to fit under provided constraints
self.delegates += Decimal::from_atomics(amount, 0).unwrap()
}
// this updates `unique_delegations` field
@@ -348,7 +335,7 @@ impl MixNodeRewarding {
&mut self,
amount: Uint128,
) -> Result<(), MixnetContractError> {
let amount_dec = amount.into_base_decimal()?;
let amount_dec = Decimal::from_atomics(amount, 0).unwrap();
self.decrease_delegates_decimal(amount_dec)
}
@@ -381,8 +368,8 @@ impl MixNodeRewarding {
}
pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> {
let reward = self.determine_delegation_reward(delegation)?;
let full_amount = reward + delegation.dec_amount()?;
let reward = self.determine_delegation_reward(delegation);
let full_amount = reward + delegation.dec_amount();
self.remove_delegation_decimal(full_amount)?;
Ok(truncate_reward(full_amount, &delegation.amount.denom))
}
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::delegation::OwnerProxySubKey;
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
@@ -43,17 +41,14 @@ pub struct InitialRewardingParams {
}
impl InitialRewardingParams {
pub fn into_rewarding_params(
self,
epochs_in_interval: u32,
) -> Result<RewardingParams, MixnetContractError> {
pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams {
let epoch_reward_budget = self.initial_reward_pool
/ epochs_in_interval.into_base_decimal()?
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
* self.interval_pool_emission;
let stake_saturation_point =
self.initial_staking_supply / self.rewarded_set_size.into_base_decimal()?;
self.initial_staking_supply / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
Ok(RewardingParams {
RewardingParams {
interval: IntervalRewardParams {
reward_pool: self.initial_reward_pool,
staking_supply: self.initial_staking_supply,
@@ -66,7 +61,7 @@ impl InitialRewardingParams {
},
rewarded_set_size: self.rewarded_set_size,
active_set_size: self.active_set_size,
})
}
}
}
@@ -1,7 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::IntoBaseDecimal;
use crate::{error::MixnetContractError, Percent};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
@@ -111,33 +110,24 @@ impl RewardingParams {
pub fn dec_rewarded_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
#[allow(clippy::unwrap_used)]
self.rewarded_set_size.into_base_decimal().unwrap()
Decimal::from_atomics(self.rewarded_set_size, 0).unwrap()
}
pub fn dec_active_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
#[allow(clippy::unwrap_used)]
self.active_set_size.into_base_decimal().unwrap()
Decimal::from_atomics(self.active_set_size, 0).unwrap()
}
fn dec_standby_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
#[allow(clippy::unwrap_used)]
(self.rewarded_set_size - self.active_set_size)
.into_base_decimal()
.unwrap()
Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap()
}
pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
#[allow(clippy::unwrap_used)]
let new_epochs_in_interval = new_epochs_in_interval.into_base_decimal().unwrap();
self.interval.epoch_reward_budget = self.interval.reward_pool / new_epochs_in_interval
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(new_epochs_in_interval, 0).unwrap()
* self.interval.interval_pool_emission;
}
@@ -210,13 +200,13 @@ impl RewardingParams {
if recompute_epoch_budget {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ epochs_in_interval.into_base_decimal()?
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
* self.interval.interval_pool_emission;
}
if recompute_saturation_point {
self.interval.stake_saturation_point =
self.interval.staking_supply / self.rewarded_set_size.into_base_decimal()?
self.interval.stake_saturation_point = self.interval.staking_supply
/ Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
}
Ok(())
@@ -29,7 +29,7 @@ pub struct RewardEstimate {
pub operating_cost: Decimal,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct RewardDistribution {
pub operator: Decimal,
pub delegates: Decimal,
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::reward_params::NodeRewardParams;
use crate::rewarding::simulator::simulated_node::SimulatedNode;
use crate::rewarding::RewardDistribution;
@@ -34,7 +33,7 @@ impl Simulator {
}
}
fn advance_epoch(&mut self) -> Result<(), MixnetContractError> {
fn advance_epoch(&mut self) {
let updated = self.interval.advance_epoch();
// we rolled over an interval
@@ -48,13 +47,10 @@ impl Simulator {
.staking_supply_scale_factor
* self.pending_reward_pool_emission;
let epoch_reward_budget = reward_pool
/ self.interval.epochs_in_interval().into_base_decimal()?
/ Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap()
* old.interval_pool_emission.value();
let stake_saturation_point = staking_supply
/ self
.system_rewarding_params
.rewarded_set_size
.into_base_decimal()?;
/ Decimal::from_atomics(self.system_rewarding_params.rewarded_set_size, 0).unwrap();
let updated_params = RewardingParams {
interval: IntervalRewardParams {
@@ -75,15 +71,9 @@ impl Simulator {
self.pending_reward_pool_emission = Decimal::zero();
}
self.interval = updated;
Ok(())
}
pub fn bond(
&mut self,
pledge: Coin,
cost_params: MixNodeCostParams,
) -> Result<MixId, MixnetContractError> {
pub fn bond(&mut self, pledge: Coin, cost_params: MixNodeCostParams) -> MixId {
let mix_id = self.next_mix_id;
self.nodes.insert(
@@ -93,24 +83,16 @@ impl Simulator {
cost_params,
&pledge,
self.interval.current_epoch_absolute_id(),
)?,
),
);
self.next_mix_id += 1;
Ok(mix_id)
mix_id
}
pub fn delegate<S: Into<String>>(
&mut self,
delegator: S,
delegation: Coin,
mix_id: MixId,
) -> Result<(), MixnetContractError> {
let node = self
.nodes
.get_mut(&mix_id)
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin, mix_id: MixId) {
let node = self.nodes.get_mut(&mix_id).expect("node doesn't exist");
node.delegate(delegator, delegation)
}
@@ -121,35 +103,23 @@ impl Simulator {
delegator: S,
mix_id: MixId,
) -> Result<(Coin, Coin), MixnetContractError> {
let node = self
.nodes
.get_mut(&mix_id)
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
let node = self.nodes.get_mut(&mix_id).expect("node not found");
node.undelegate(delegator)
}
pub fn simulate_epoch_single_node(
&mut self,
params: NodeRewardParams,
) -> Result<RewardDistribution, MixnetContractError> {
pub fn simulate_epoch_single_node(&mut self, params: NodeRewardParams) -> RewardDistribution {
assert_eq!(self.nodes.len(), 1);
if let Some(&id) = self.nodes.keys().next() {
let mut params_map = BTreeMap::new();
params_map.insert(id, params);
Ok(self
.simulate_epoch(&params_map)?
.remove(&id)
.unwrap_or_default())
} else {
Ok(RewardDistribution::default())
}
let id = *self.nodes.keys().next().unwrap();
let mut params_map = BTreeMap::new();
params_map.insert(id, params);
self.simulate_epoch(&params_map).remove(&id).unwrap()
}
pub fn simulate_epoch(
&mut self,
node_params: &BTreeMap<MixId, NodeRewardParams>,
) -> Result<BTreeMap<MixId, RewardDistribution>, MixnetContractError> {
) -> BTreeMap<MixId, RewardDistribution> {
let mut params_keys = node_params.keys().copied().collect::<Vec<_>>();
params_keys.sort_unstable();
let mut node_keys = self.nodes.keys().copied().collect::<Vec<_>>();
@@ -177,41 +147,34 @@ impl Simulator {
dist.insert(*mix_id, reward_distribution);
}
self.advance_epoch()?;
Ok(dist)
self.advance_epoch();
dist
}
pub fn determine_delegation_reward(
&self,
delegation: &Delegation,
) -> Result<Decimal, MixnetContractError> {
Ok(self.nodes[&delegation.mix_id]
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
self.nodes[&delegation.mix_id]
.rewarding_details
.determine_delegation_reward(delegation)?)
.determine_delegation_reward(delegation)
}
pub fn determine_total_delegation_reward(&self) -> Result<Decimal, MixnetContractError> {
pub fn determine_total_delegation_reward(&self) -> Decimal {
let mut total = Decimal::zero();
for node in self.nodes.values() {
for delegation in node.delegations.values() {
total += node
.rewarding_details
.determine_delegation_reward(delegation)?
.determine_delegation_reward(delegation)
}
}
Ok(total)
total
}
// assume node state doesn't change in the interval (kinda unrealistic)
pub fn simulate_full_interval(
&mut self,
node_params: &BTreeMap<MixId, NodeRewardParams>,
) -> Result<(), MixnetContractError> {
pub fn simulate_full_interval(&mut self, node_params: &BTreeMap<MixId, NodeRewardParams>) {
for _ in 0..self.interval.epochs_in_interval() {
self.simulate_epoch(node_params)?;
self.simulate_epoch(node_params);
}
Ok(())
}
}
@@ -234,10 +197,6 @@ mod tests {
use cosmwasm_std::testing::mock_env;
use std::time::Duration;
// explicitly marking this as part of #[allow(clippy::unwrap_used)] until
// https://github.com/rust-lang/rust-clippy/pull/9686
// is merged into a release
#[allow(clippy::unwrap_used)]
fn base_simulator(initial_pledge: u128) -> Simulator {
let profit_margin = Percent::from_percentage_value(10).unwrap();
let interval_operating_cost = Coin::new(40_000_000, "unym");
@@ -279,32 +238,20 @@ mod tests {
profit_margin_percent: profit_margin,
interval_operating_cost,
};
simulator.bond(initial_pledge, cost_params).unwrap();
simulator.bond(initial_pledge, cost_params);
simulator
}
// essentially our delegations + estimated rewards HAVE TO equal to what we actually determined
//
// explicitly marking this as part of #[allow(clippy::unwrap_used)] until
// https://github.com/rust-lang/rust-clippy/pull/9686
// is merged into a release
#[allow(clippy::unwrap_used)]
fn check_rewarding_invariant(simulator: &Simulator) {
for node in simulator.nodes.values() {
let delegation_sum: Decimal = node
.delegations
.values()
.map(|d| d.dec_amount().unwrap())
.sum();
let delegation_sum: Decimal =
node.delegations.values().map(|d| d.dec_amount()).sum();
let reward_sum: Decimal = node
.delegations
.values()
.map(|d| {
node.rewarding_details
.determine_delegation_reward(d)
.unwrap()
})
.map(|d| node.rewarding_details.determine_delegation_reward(d))
.sum();
// let reward_sum = simulator.determine_total_delegation_reward();
@@ -322,7 +269,7 @@ mod tests {
let epoch_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards = simulator.simulate_epoch_single_node(epoch_params).unwrap();
let rewards = simulator.simulate_epoch_single_node(epoch_params);
assert_eq!(rewards.delegates, Decimal::zero());
compare_decimals(
@@ -335,13 +282,11 @@ mod tests {
#[test]
fn single_delegation_at_genesis() {
let mut simulator = base_simulator(10000_000000);
simulator
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards = simulator.simulate_epoch_single_node(node_params).unwrap();
let rewards = simulator.simulate_epoch_single_node(node_params);
compare_decimals(
rewards.delegates,
@@ -352,7 +297,7 @@ mod tests {
compare_decimals(
rewards.delegates,
simulator.determine_total_delegation_reward().unwrap(),
simulator.determine_total_delegation_reward(),
None,
);
let node = &simulator.nodes[&0];
@@ -372,22 +317,20 @@ mod tests {
let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
let rewards1 = simulator.simulate_epoch_single_node(node_params);
let expected_operator1 = "1128452.5416104363".parse().unwrap();
assert_eq!(rewards1.delegates, Decimal::zero());
compare_decimals(rewards1.operator, expected_operator1, None);
simulator
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
let rewards2 = simulator.simulate_epoch_single_node(node_params);
let expected_operator2 = "1363843.413584609".parse().unwrap();
let expected_delegator_reward1 = "1795952.25874404".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward1, None);
compare_decimals(rewards2.operator, expected_operator2, None);
let rewards3 = simulator.simulate_epoch_single_node(node_params).unwrap();
let rewards3 = simulator.simulate_epoch_single_node(node_params);
let expected_operator3 = "1364017.7824440491".parse().unwrap();
let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap();
compare_decimals(rewards3.delegates, expected_delegator_reward2, None);
@@ -421,15 +364,11 @@ mod tests {
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
simulator
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
.unwrap();
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
// "normal", sanity check rewarding
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
let rewards1 = simulator.simulate_epoch_single_node(node_params);
let expected_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
@@ -439,15 +378,14 @@ mod tests {
let node = simulator.nodes.get_mut(&0).unwrap();
let reward = node
.rewarding_details
.withdraw_operator_reward(&original_pledge)
.unwrap();
.withdraw_operator_reward(&original_pledge);
assert_eq!(reward.amount, truncate_reward_amount(expected_operator1));
assert_eq!(
node.rewarding_details.operator,
Decimal::from_atomics(original_pledge.amount, 0).unwrap()
);
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
let rewards2 = simulator.simulate_epoch_single_node(node_params);
let expected_operator2 = "1411113.0004067947".parse().unwrap();
let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
@@ -464,15 +402,11 @@ mod tests {
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
simulator
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
.unwrap();
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
// "normal", sanity check rewarding
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
let rewards1 = simulator.simulate_epoch_single_node(node_params);
let expected_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
@@ -490,7 +424,7 @@ mod tests {
assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward));
// new reward after withdrawal
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
let rewards2 = simulator.simulate_epoch_single_node(node_params);
let expected_operator2 = "1411250.1907492676".parse().unwrap();
let expected_delegator_reward2 = "2200004.051009689".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
@@ -533,30 +467,22 @@ mod tests {
let mut performance = Percent::from_percentage_value(100).unwrap();
for epoch in 0..720 {
if epoch == 0 {
simulator
.delegate("a", Coin::new(18000_000000, "unym"), 0)
.unwrap()
simulator.delegate("a", Coin::new(18000_000000, "unym"), 0)
}
if epoch == 42 {
simulator
.delegate("b", Coin::new(2000_000000, "unym"), 0)
.unwrap()
simulator.delegate("b", Coin::new(2000_000000, "unym"), 0)
}
if epoch == 89 {
is_active = false;
}
if epoch == 123 {
simulator
.delegate("c", Coin::new(6666_000000, "unym"), 0)
.unwrap()
simulator.delegate("c", Coin::new(6666_000000, "unym"), 0)
}
if epoch == 167 {
performance = Percent::from_percentage_value(90).unwrap();
}
if epoch == 245 {
simulator
.delegate("d", Coin::new(2050_000000, "unym"), 0)
.unwrap()
simulator.delegate("d", Coin::new(2050_000000, "unym"), 0)
}
if epoch == 264 {
let (delegation, _reward) = simulator.undelegate("b", 0).unwrap();
@@ -577,15 +503,13 @@ mod tests {
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
}
if epoch == 545 {
simulator
.delegate("e", Coin::new(5000_000000, "unym"), 0)
.unwrap()
simulator.delegate("e", Coin::new(5000_000000, "unym"), 0)
}
// this has to always hold
check_rewarding_invariant(&simulator);
let node_params = NodeRewardParams::new(performance, is_active);
simulator.simulate_epoch_single_node(node_params).unwrap();
simulator.simulate_epoch_single_node(node_params);
}
// after everyone undelegates, there should be nothing left in the delegates pool
@@ -639,135 +563,95 @@ mod tests {
let mut simulator = Simulator::new(rewarding_params, interval);
let n0 = simulator
.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0)
.unwrap();
let n0 = simulator.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0);
let n1 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1)
.unwrap();
let n1 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1);
let n2 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2)
.unwrap();
let n2 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2);
let n3 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3)
.unwrap();
let n3 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3);
let n4 = simulator
.bond(
Coin::new(1000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4)
.unwrap();
let n4 = simulator.bond(
Coin::new(1000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4);
let n5 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5)
.unwrap();
let n5 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5);
let n6 = simulator
.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6)
.unwrap();
let n6 = simulator.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6);
let n7 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7)
.unwrap();
let n7 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7);
let n8 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8)
.unwrap();
let n8 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8);
let n9 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9)
.unwrap();
let n9 = simulator.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
);
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9);
let uptime_1 = Percent::from_percentage_value(100).unwrap();
let uptime_09 = Percent::from_percentage_value(90).unwrap();
@@ -789,7 +673,7 @@ mod tests {
.collect::<BTreeMap<_, _>>();
for _ in 0..23 {
simulator.simulate_full_interval(&node_params).unwrap();
simulator.simulate_full_interval(&node_params);
}
// we allow the delta to be within 0.1unym,
@@ -20,25 +20,21 @@ impl SimulatedNode {
cost_params: MixNodeCostParams,
initial_pledge: &Coin,
current_epoch: EpochId,
) -> Result<Self, MixnetContractError> {
Ok(SimulatedNode {
) -> Self {
SimulatedNode {
mix_id,
rewarding_details: MixNodeRewarding::initialise_new(
cost_params,
initial_pledge,
current_epoch,
)?,
),
delegations: HashMap::new(),
})
}
}
pub fn delegate<S: Into<String>>(
&mut self,
delegator: S,
delegation: Coin,
) -> Result<(), MixnetContractError> {
pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin) {
self.rewarding_details
.add_base_delegation(delegation.amount)?;
.add_base_delegation(delegation.amount);
let delegator = delegator.into();
let delegation = Delegation::new(
@@ -51,7 +47,6 @@ impl SimulatedNode {
);
self.delegations.insert(delegator, delegation);
Ok(())
}
pub fn undelegate<S: Into<String>>(
@@ -59,19 +54,16 @@ impl SimulatedNode {
delegator: S,
) -> Result<(Coin, Coin), MixnetContractError> {
let delegator = delegator.into();
let delegation = self.delegations.remove(&delegator).ok_or(
MixnetContractError::NoMixnodeDelegationFound {
mix_id: MixId::MAX,
address: delegator,
proxy: None,
},
)?;
let delegation = self
.delegations
.remove(&delegator)
.expect("delegation not found");
let reward = self
.rewarding_details
.determine_delegation_reward(&delegation)?;
.determine_delegation_reward(&delegation);
self.rewarding_details
.remove_delegation_decimal(delegation.dec_amount()? + reward)?;
.remove_delegation_decimal(delegation.dec_amount() + reward)?;
let reward_denom = &delegation.amount.denom;
let truncated_reward = truncate_reward(reward, reward_denom);
@@ -35,10 +35,6 @@ impl LayerDistribution {
(Layer::Two, self.layer2),
(Layer::Three, self.layer3),
];
// we explicitly put 3 elements into the iterator, so the iterator is DEFINITELY
// not empty and thus the unwrap cannot fail
#[allow(clippy::unwrap_used)]
layers.iter().min_by_key(|x| x.1).unwrap().0
}
@@ -9,7 +9,6 @@ mixnet-contract-common = { path = "../mixnet-contract" }
contracts-common = { path = "../contracts-common" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
log = "0.4"
ts-rs = {version = "6.1.2", optional = true}
[features]
@@ -1,16 +1,12 @@
use std::str::FromStr;
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use contracts_common::Percent;
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
use log::warn;
use mixnet_contract_common::MixId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
@@ -62,23 +58,11 @@ impl FromStr for PledgeCap {
fn from_str(cap: &str) -> Result<Self, Self::Err> {
let cap = cap.replace('_', "").replace(',', ".");
match Percent::from_str(&cap) {
Ok(p) => {
if p.is_zero() {
warn!("Pledge cap set to 0%, are you sure this is right?")
}
Ok(PledgeCap::Percent(p))
}
Err(_) => {
match cap.parse::<u128>() {
Ok(i) => {
if i < 100_000_000_000 {
warn!("PledgeCap set to less then 100_000 NYM, are you sure this is right?");
}
Ok(PledgeCap::Absolute(Uint128::from(i)))
}
Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
}
}
Ok(p) => Ok(PledgeCap::Percent(p)),
Err(_) => match cap.parse::<u128>() {
Ok(i) => Ok(PledgeCap::Absolute(Uint128::from(i))),
Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
},
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
+3 -3
View File
@@ -85,7 +85,7 @@ impl SecretKey {
// x || ys.len() || ys
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) * 32);
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
@@ -162,7 +162,7 @@ impl TryFrom<&[u8]> for VerificationKey {
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = start + 48;
let end = (start + 48) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
@@ -178,7 +178,7 @@ impl TryFrom<&[u8]> for VerificationKey {
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = start + 96;
let end = (start + 96) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
+2 -2
View File
@@ -94,10 +94,10 @@ fn prepare_unlinked_fragmented_set(
for i in 1..(pre_casted_frags + 1) {
// we can't use u8 directly here as upper (NON-INCLUSIVE, so it would always fit) bound could be u8::max_value() + 1
let lb = (i - 1) * unlinked_fragment_payload_max_len(max_plaintext_size);
let lb = (i as usize - 1) * unlinked_fragment_payload_max_len(max_plaintext_size);
let ub = usize::min(
message.len(),
i * unlinked_fragment_payload_max_len(max_plaintext_size),
i as usize * unlinked_fragment_payload_max_len(max_plaintext_size),
);
fragments.push(
Fragment::try_new(
+28 -5
View File
@@ -1,8 +1,34 @@
## Unreleased
## [nym-contracts-v1.1.0](https://github.com/nymtech/nym/tree/nym-contracts-v1.1.0) (2022-11-09)
### Changed
- mixnet-contract: rework of rewarding ([#1472]), which includes, but is not limited to:
- internal reward accounting was modified to be similar to the ideas presented in Cosmos' F1 paper, which results in throughput gains and no storage or gas cost bloat over time,
- introduced internal queues for pending epoch and interval events that only get resolved once relevant epoch/interval rolls over
- the contract no longer stores any historical information regarding past epochs/parameters/stake state for the purposes of rewarding
- a lot of queries got renamed to keep naming more consistent,
- introduced new utility-based queries such as a query for reward estimation for the current epoch,
- mixnodes are now identified by a monotonously increasing `mix_id`
- bonding now results in getting fresh `mix_id` and thus if given node decides to unbond and rebond, it will lose all its delegations,
- mixnode operators are now allowed to set their operating costs as opposed to having fixed value of 40nym/interval
- rewarding parameters are now correctly updated at an **interval** end
- rewarding parameters now include a staking supply scale factor attribute (beta in the tokenomics paper)
- node performance can now be more granular with internal `Decimal` representation as opposed to an `u8`
- node profit margin can now be more granular with internal `Decimal` representation as opposed to an `u8`
- mixnode operators are now allowed to change their configuration options, such as port information, without having to unbond
- mixnode unbonding is no longer instantaneous, instead it happens once an epoch rolls over
- it is now possible to query for operator and node history to see how often (and with what parameters) they rebonded
- other minor bugfixes and changes
- ...
- new exciting bugs to find and squash
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 10%
- vesting-contract: updated internal delegation storage due to mixnet contract revamp ([#1472])
### Added
- vesting-contract: added query for obtaining contract build information ([#1726])
[#1472]: https://github.com/nymtech/nym/pull/1472
[#1687]: https://github.com/nymtech/nym/pull/1687
[#1726]: https://github.com/nymtech/nym/pull/1726
@@ -11,12 +37,10 @@
### Added
- vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569])
- all binaries: added shell completion and [Fig](fig.io) spec generation ([#1638])
### Changed
- mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571])
- network-requester: updated CLI to use `clap` macros ([#1638])
### Fixed
@@ -26,9 +50,8 @@
[#1544]: https://github.com/nymtech/nym/pull/1544
[#1569]: https://github.com/nymtech/nym/pull/1569
[#1569]: https://github.com/nymtech/nym/pull/1571
[#1571]: https://github.com/nymtech/nym/pull/1571
[#1613]: https://github.com/nymtech/nym/pull/1613
[#1638]: https://github.com/nymtech/nym/pull/1638
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
+1 -2
View File
@@ -930,7 +930,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mixnet-contract"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"bs58",
"cosmwasm-schema",
@@ -1625,7 +1625,6 @@ version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"log",
"mixnet-contract-common",
"schemars",
"serde",
-2
View File
@@ -1,2 +0,0 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mixnet-contract"
version = "1.0.2"
version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
+1 -1
View File
@@ -63,7 +63,7 @@ pub fn instantiate(
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
let reward_params = msg
.initial_rewarding_params
.into_rewarding_params(msg.epochs_in_interval)?;
.into_rewarding_params(msg.epochs_in_interval);
interval_storage::initialise_storage(deps.storage, starting_interval)?;
mixnet_params_storage::initialise_storage(deps.storage, state)?;
@@ -107,7 +107,7 @@ pub(crate) fn delegate(
};
// add the amount we're intending to delegate (whether it's fresh or we're adding to the existing one)
mix_rewarding.add_base_delegation(stored_delegation_amount.amount)?;
mix_rewarding.add_base_delegation(stored_delegation_amount.amount);
let cosmos_event = new_delegation_event(
created_at,
-3
View File
@@ -1,9 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
mod constants;
pub mod contract;
mod delegations;
+1 -1
View File
@@ -78,7 +78,7 @@ pub(crate) fn save_new_mixnode(
let mix_id = next_mixnode_id_counter(storage)?;
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_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch);
let mixnode_bond = MixNodeBond::new(
mix_id,
owner,
+12 -18
View File
@@ -4,9 +4,8 @@
use super::storage;
use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_storage;
use cosmwasm_std::{Coin, Storage};
use cosmwasm_std::{Coin, Decimal, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::helpers::IntoBaseDecimal;
use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding};
use mixnet_contract_common::Delegation;
@@ -23,10 +22,11 @@ pub(crate) fn apply_reward_pool_changes(
+ pending_pool_change.added;
let staking_supply = rewarding_params.interval.staking_supply
+ rewarding_params.interval.staking_supply_scale_factor * pending_pool_change.removed;
let epoch_reward_budget = reward_pool / interval.epochs_in_interval().into_base_decimal()?
let epoch_reward_budget = reward_pool
/ Decimal::from_atomics(interval.epochs_in_interval(), 0).unwrap()
* rewarding_params.interval.interval_pool_emission;
let stake_saturation_point =
staking_supply / rewarding_params.rewarded_set_size.into_base_decimal()?;
staking_supply / Decimal::from_atomics(rewarding_params.rewarded_set_size, 0).unwrap();
rewarding_params.interval.reward_pool = reward_pool;
rewarding_params.interval.staking_supply = staking_supply;
@@ -46,7 +46,7 @@ pub(crate) fn withdraw_operator_reward(
let mix_id = mix_details.mix_id();
let mut mix_rewarding = mix_details.rewarding_details;
let original_pledge = mix_details.bond_information.original_pledge;
let reward = mix_rewarding.withdraw_operator_reward(&original_pledge)?;
let reward = mix_rewarding.withdraw_operator_reward(&original_pledge);
// save updated rewarding info
storage::MIXNODE_REWARDING.save(store, mix_id, &mix_rewarding)?;
@@ -87,7 +87,7 @@ mod tests {
let mut test = TestSetup::new();
let epochs_in_interval = test.current_interval().epochs_in_interval();
let epochs_in_interval_dec = epochs_in_interval.into_base_decimal().unwrap();
let epochs_in_interval_dec = Decimal::from_atomics(epochs_in_interval, 0).unwrap();
let start_rewarding_params = test.rewarding_params();
// nothing changes if pending changes are empty
@@ -95,7 +95,7 @@ mod tests {
assert_eq!(start_rewarding_params, test.rewarding_params());
// normal case of having distributed some rewards
let distributed_rewards = 100_000_000u32.into_base_decimal().unwrap();
let distributed_rewards = Decimal::from_atomics(100_000_000u32, 0).unwrap();
storage::PENDING_REWARD_POOL_CHANGE
.save(
test.deps_mut().storage,
@@ -132,10 +132,7 @@ mod tests {
assert_eq!(
updated_rewarding_params.interval.stake_saturation_point,
updated_rewarding_params.interval.staking_supply
/ updated_rewarding_params
.rewarded_set_size
.into_base_decimal()
.unwrap()
/ Decimal::from_atomics(updated_rewarding_params.rewarded_set_size, 0).unwrap()
);
// resets changes back to 0
@@ -147,7 +144,7 @@ mod tests {
);
// future case of having to also increase the reward pool
let added_credentials = 50_000_000u32.into_base_decimal().unwrap();
let added_credentials = Decimal::from_atomics(50_000_000u32, 0).unwrap();
storage::PENDING_REWARD_POOL_CHANGE
.save(
test.deps_mut().storage,
@@ -184,10 +181,7 @@ mod tests {
assert_eq!(
updated_rewarding_params2.interval.stake_saturation_point,
updated_rewarding_params2.interval.staking_supply
/ updated_rewarding_params2
.rewarded_set_size
.into_base_decimal()
.unwrap()
/ Decimal::from_atomics(updated_rewarding_params2.rewarded_set_size, 0).unwrap()
);
// resets changes back to 0
@@ -204,7 +198,7 @@ mod tests {
let mut test = TestSetup::new();
let pledge = Uint128::new(250_000_000);
let pledge_dec = 250_000_000u32.into_base_decimal().unwrap();
let pledge_dec = Decimal::from_atomics(250_000_000u32, 0).unwrap();
let mix_id = test.add_dummy_mixnode("mix-owner", Some(pledge));
// no rewards
@@ -241,7 +235,7 @@ mod tests {
let mut test = TestSetup::new();
let delegation_amount = Uint128::new(2500_000_000);
let delegation_dec = 2500_000_000u32.into_base_decimal().unwrap();
let delegation_dec = Decimal::from_atomics(2500_000_000u32, 0).unwrap();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegator = "delegator";
test.add_immediate_delegation(delegator, delegation_amount, mix_id);
+11 -17
View File
@@ -7,7 +7,6 @@ use crate::interval::storage as interval_storage;
use crate::mixnodes;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{coin, Coin, Decimal, Deps, StdResult};
use mixnet_contract_common::helpers::into_base_decimal;
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams};
use mixnet_contract_common::rewarding::helpers::truncate_reward;
@@ -20,18 +19,16 @@ pub(crate) fn query_rewarding_params(deps: Deps<'_>) -> StdResult<RewardingParam
storage::REWARDING_PARAMS.load(deps.storage)
}
fn pending_operator_reward(
mix_details: Option<MixNodeDetails>,
) -> StdResult<PendingRewardResponse> {
Ok(match mix_details {
fn pending_operator_reward(mix_details: Option<MixNodeDetails>) -> PendingRewardResponse {
match mix_details {
Some(mix_details) => PendingRewardResponse {
amount_staked: Some(mix_details.original_pledge().clone()),
amount_earned: Some(mix_details.pending_operator_reward()),
amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()?),
amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()),
mixnode_still_fully_bonded: !mix_details.is_unbonding(),
},
None => PendingRewardResponse::default(),
})
}
}
pub fn query_pending_operator_reward(
@@ -42,7 +39,7 @@ pub fn query_pending_operator_reward(
// in order to determine operator's reward we need to know its original pledge and thus
// we have to load the entire thing
let mix_details = mixnodes::helpers::get_mixnode_details_by_owner(deps.storage, owner_address)?;
pending_operator_reward(mix_details)
Ok(pending_operator_reward(mix_details))
}
pub fn query_pending_mixnode_operator_reward(
@@ -52,7 +49,7 @@ pub fn query_pending_mixnode_operator_reward(
// in order to determine operator's reward we need to know its original pledge and thus
// we have to load the entire thing
let mix_details = mixnodes::helpers::get_mixnode_details_by_id(deps.storage, mix_id)?;
pending_operator_reward(mix_details)
Ok(pending_operator_reward(mix_details))
}
pub fn query_pending_delegator_reward(
@@ -77,8 +74,8 @@ pub fn query_pending_delegator_reward(
None => return Ok(PendingRewardResponse::default()),
};
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation)?;
let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation)?;
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation);
let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation);
// check if the mixnode isnt in the process of unbonding (or has already unbonded)
let is_bonded = matches!(mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)?, Some(mix_bond) if !mix_bond.is_unbonding);
@@ -180,8 +177,8 @@ pub(crate) fn query_estimated_current_epoch_delegator_reward(
None => return Ok(EstimatedCurrentEpochRewardResponse::empty_response()),
};
let staked_dec = into_base_decimal(delegation.amount.amount)?;
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation)?;
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
let amount_staked = delegation.amount;
// check if the mixnode isnt in the process of unbonding (or has already unbonded)
@@ -795,10 +792,7 @@ mod tests {
let delegation = test.delegation(mix_id, owner, &None);
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
let current_value = staked_dec
+ mix_rewarding
.determine_delegation_reward(&delegation)
.unwrap();
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
let amount_staked = delegation.amount;
EstimatedCurrentEpochRewardResponse {
+22 -30
View File
@@ -744,7 +744,7 @@ pub mod tests {
performance,
in_active_set: true,
};
let sim_res = sim.simulate_epoch_single_node(node_params).unwrap();
let sim_res = sim.simulate_epoch_single_node(node_params);
assert_eq!(sim_res, dist);
}
test.skip_to_next_epoch_end();
@@ -768,7 +768,7 @@ pub mod tests {
performance,
in_active_set: true,
};
let sim_res = sim.simulate_epoch_single_node(node_params).unwrap();
let sim_res = sim.simulate_epoch_single_node(node_params);
assert_eq!(sim_res, dist);
}
test.skip_to_next_epoch_end();
@@ -809,8 +809,8 @@ pub mod tests {
in_active_set: true,
};
let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap();
let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap();
let dist1 = sim1.simulate_epoch_single_node(node_params);
let dist2 = sim2.simulate_epoch_single_node(node_params);
let env = test.env();
@@ -858,17 +858,15 @@ pub mod tests {
let unit_delegation_base = actual_prior1.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del11 = del11.dec_amount().unwrap()
+ (prior_unit_reward - del11.cumulative_reward_ratio)
* del11.dec_amount().unwrap()
let pre_rewarding_del11 = del11.dec_amount()
+ (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
/ (del11.cumulative_reward_ratio + unit_delegation_base);
let computed_del11_reward =
pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del12 = del12.dec_amount().unwrap()
+ (prior_unit_reward - del12.cumulative_reward_ratio)
* del12.dec_amount().unwrap()
let pre_rewarding_del12 = del12.dec_amount()
+ (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
/ (del12.cumulative_reward_ratio + unit_delegation_base);
let computed_del12_reward =
@@ -922,9 +920,8 @@ pub mod tests {
let unit_delegation_base = actual_prior2.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del21 = del21.dec_amount().unwrap()
+ (prior_unit_reward - del21.cumulative_reward_ratio)
* del21.dec_amount().unwrap()
let pre_rewarding_del21 = del21.dec_amount()
+ (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
/ (del21.cumulative_reward_ratio + unit_delegation_base);
let computed_del21_reward =
@@ -952,8 +949,8 @@ pub mod tests {
in_active_set: true,
};
let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap();
let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap();
let dist1 = sim1.simulate_epoch_single_node(node_params);
let dist2 = sim2.simulate_epoch_single_node(node_params);
let env = test.env();
@@ -1001,25 +998,22 @@ pub mod tests {
let unit_delegation_base = actual_prior1.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del11 = del11.dec_amount().unwrap()
+ (prior_unit_reward - del11.cumulative_reward_ratio)
* del11.dec_amount().unwrap()
let pre_rewarding_del11 = del11.dec_amount()
+ (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
/ (del11.cumulative_reward_ratio + unit_delegation_base);
let computed_del11_reward =
pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del12 = del12.dec_amount().unwrap()
+ (prior_unit_reward - del12.cumulative_reward_ratio)
* del12.dec_amount().unwrap()
let pre_rewarding_del12 = del12.dec_amount()
+ (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
/ (del12.cumulative_reward_ratio + unit_delegation_base);
let computed_del12_reward =
pre_rewarding_del12 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del13 = del13.dec_amount().unwrap()
+ (prior_unit_reward - del13.cumulative_reward_ratio)
* del13.dec_amount().unwrap()
let pre_rewarding_del13 = del13.dec_amount()
+ (prior_unit_reward - del13.cumulative_reward_ratio) * del13.dec_amount()
/ (del13.cumulative_reward_ratio + unit_delegation_base);
let computed_del13_reward =
@@ -1073,17 +1067,15 @@ pub mod tests {
let unit_delegation_base = actual_prior2.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del21 = del21.dec_amount().unwrap()
+ (prior_unit_reward - del21.cumulative_reward_ratio)
* del21.dec_amount().unwrap()
let pre_rewarding_del21 = del21.dec_amount()
+ (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
/ (del21.cumulative_reward_ratio + unit_delegation_base);
let computed_del21_reward =
pre_rewarding_del21 / prior_delegates2 * delegates_reward2;
let pre_rewarding_del23 = del23.dec_amount().unwrap()
+ (prior_unit_reward - del23.cumulative_reward_ratio)
* del23.dec_amount().unwrap()
let pre_rewarding_del23 = del23.dec_amount()
+ (prior_unit_reward - del23.cumulative_reward_ratio) * del23.dec_amount()
/ (del23.cumulative_reward_ratio + unit_delegation_base);
let computed_del23_reward =
-8
View File
@@ -69,10 +69,6 @@ pub(crate) fn validate_pledge(
});
}
// throughout this function we've been using the value at `pledge[0]` without problems
// (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine,
// since it cannot possibly fail without UB
#[allow(clippy::unwrap_used)]
Ok(pledge.pop().unwrap())
}
@@ -110,10 +106,6 @@ pub(crate) fn validate_delegation_stake(
return Err(MixnetContractError::EmptyDelegation);
}
// throughout this function we've been using the value at `delegation[0]` without problems
// (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine,
// since it cannot possibly fail without UB
#[allow(clippy::unwrap_used)]
Ok(delegation.pop().unwrap())
}
+5 -5
View File
@@ -1,8 +1,8 @@
use crate::errors::ContractError;
use crate::queued_migrations::migrate_to_v2_mixnet_contract;
use crate::storage::{
account_from_address, BlockTimestampSecs, ADMIN, DELEGATIONS, MIXNET_CONTRACT_ADDRESS,
MIX_DENOM,
account_from_address, save_account, BlockTimestampSecs, ADMIN, DELEGATIONS,
MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
};
use crate::traits::{
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
@@ -158,7 +158,7 @@ pub fn try_update_locked_pledge_cap(
let mut account = account_from_address(&address, deps.storage, deps.api)?;
account.pledge_cap = Some(cap);
// update_locked_pledge_cap(amount, deps.storage)?;
save_account(&account, deps.storage)?;
Ok(Response::default())
}
@@ -589,7 +589,7 @@ pub fn try_get_current_vesting_period(
env: Env,
) -> Result<Period, ContractError> {
let account = account_from_address(address, deps.storage, deps.api)?;
account.get_current_vesting_period(env.block.time)
Ok(account.get_current_vesting_period(env.block.time))
}
/// Loads mixnode bond from vesting contract storage.
@@ -691,7 +691,7 @@ pub fn try_get_original_vesting(
deps: Deps<'_>,
) -> Result<OriginalVestingResponse, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_original_vesting()
Ok(account.get_original_vesting())
}
/// See [crate::traits::VestingAccount::get_delegated_free]
-2
View File
@@ -50,6 +50,4 @@ pub enum ContractError {
MinVestingFunds { sent: u128, need: u128 },
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
UnpopulatedVestingPeriods { owner: Addr },
}
-3
View File
@@ -1,9 +1,6 @@
#![allow(rustdoc::private_intra_doc_links)]
//! Nym vesting contract, providing vesting accounts with ability to stake unvested tokens
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
pub mod contract;
mod errors;
mod queued_migrations;
@@ -55,7 +55,7 @@ pub trait VestingAccount {
/// Returns amount of coins set at account creation
/// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl
fn get_original_vesting(&self) -> Result<OriginalVestingResponse, ContractError>;
fn get_original_vesting(&self) -> OriginalVestingResponse;
/// See [/vesting-contract/struct.Account.html/method.get_delegated_free] for impl
fn get_delegated_free(
+9 -31
View File
@@ -69,7 +69,7 @@ impl Account {
pub fn absolute_pledge_cap(&self) -> Result<Uint128, ContractError> {
match self.pledge_cap() {
PledgeCap::Absolute(cap) => Ok(cap),
PledgeCap::Percent(p) => Ok(p * self.get_original_vesting()?.amount.amount),
PledgeCap::Percent(p) => Ok(p * self.get_original_vesting().amount.amount),
}
}
@@ -81,13 +81,8 @@ impl Account {
self.periods.len()
}
pub fn period_duration(&self) -> Result<u64, ContractError> {
self.periods
.get(0)
.ok_or(ContractError::UnpopulatedVestingPeriods {
owner: self.owner_address.clone(),
})
.map(|p| p.period_seconds)
pub fn period_duration(&self) -> u64 {
self.periods.get(0).unwrap().period_seconds
}
pub fn storage_key(&self) -> u32 {
@@ -124,28 +119,11 @@ impl Account {
/// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet.
/// In case vesting is over it will always return NUM_VESTING_PERIODS.
pub fn get_current_vesting_period(
&self,
block_time: Timestamp,
) -> Result<Period, ContractError> {
let first_period =
self.periods
.first()
.ok_or(ContractError::UnpopulatedVestingPeriods {
owner: self.owner_address.clone(),
})?;
let last_period = self
.periods
.last()
.ok_or(ContractError::UnpopulatedVestingPeriods {
owner: self.owner_address.clone(),
})?;
if block_time.seconds() < first_period.start_time {
Ok(Period::Before)
} else if last_period.end_time() < block_time {
Ok(Period::After)
pub fn get_current_vesting_period(&self, block_time: Timestamp) -> Period {
if block_time.seconds() < self.periods.first().unwrap().start_time {
Period::Before
} else if self.periods.last().unwrap().end_time() < block_time {
Period::After
} else {
let mut index = 0;
for period in &self.periods {
@@ -154,7 +132,7 @@ impl Account {
}
index += 1;
}
Ok(Period::In(index))
Period::In(index)
}
}
@@ -67,7 +67,7 @@ impl VestingAccount for Account {
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time);
let period = self.get_current_vesting_period(block_time)?;
let period = self.get_current_vesting_period(block_time);
let denom = MIX_DENOM.load(storage)?;
let amount = match period {
@@ -94,7 +94,7 @@ impl VestingAccount for Account {
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
Ok(Coin {
amount: self.get_original_vesting()?.amount().amount
amount: self.get_original_vesting().amount().amount
- self.get_vested_coins(block_time, env, storage)?.amount,
denom: MIX_DENOM.load(storage)?,
})
@@ -108,12 +108,12 @@ impl VestingAccount for Account {
self.periods[(self.num_vesting_periods() - 1)].end_time()
}
fn get_original_vesting(&self) -> Result<OriginalVestingResponse, ContractError> {
Ok(OriginalVestingResponse::new(
fn get_original_vesting(&self) -> OriginalVestingResponse {
OriginalVestingResponse::new(
self.coin.clone(),
self.num_vesting_periods(),
self.period_duration()?,
))
self.period_duration(),
)
}
fn get_delegated_free(
@@ -123,12 +123,13 @@ impl VestingAccount for Account {
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time);
let period = self.get_current_vesting_period(block_time)?;
let withdrawn = self.load_withdrawn(storage)?;
let max_available = self
.get_vested_coins(Some(block_time), env, storage)?
.amount
.saturating_sub(withdrawn);
let period = self.get_current_vesting_period(block_time);
let start_time = match period {
Period::Before => 0,
Period::After => u64::MAX,
@@ -173,7 +174,7 @@ impl VestingAccount for Account {
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time);
let period = self.get_current_vesting_period(block_time)?;
let period = self.get_current_vesting_period(block_time);
let max_vested = self.get_vested_coins(Some(block_time), env, storage)?;
let start_time = match period {
Period::Before => 0,
+11 -51
View File
@@ -183,14 +183,12 @@ mod tests {
assert_eq!(account.periods().len(), num_vesting_periods as usize);
let current_period = account
.get_current_vesting_period(Timestamp::from_seconds(0))
.unwrap();
let current_period = account.get_current_vesting_period(Timestamp::from_seconds(0));
assert_eq!(Period::Before, current_period);
let block_time =
Timestamp::from_seconds(account.start_time().seconds() + vesting_period + 1);
let current_period = account.get_current_vesting_period(block_time).unwrap();
let current_period = account.get_current_vesting_period(block_time);
assert_eq!(current_period, Period::In(1));
let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -201,37 +199,21 @@ mod tests {
assert_eq!(
vested_coins.amount,
Uint128::new(
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128
account.get_original_vesting().amount().amount.u128() / num_vesting_periods as u128
)
);
assert_eq!(
vesting_coins.amount,
Uint128::new(
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
- account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
account.get_original_vesting().amount().amount.u128()
- account.get_original_vesting().amount().amount.u128()
/ num_vesting_periods as u128
)
);
let block_time =
Timestamp::from_seconds(account.start_time().seconds() + 5 * vesting_period + 1);
let current_period = account.get_current_vesting_period(block_time).unwrap();
let current_period = account.get_current_vesting_period(block_time);
assert_eq!(current_period, Period::In(5));
let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -242,30 +224,15 @@ mod tests {
assert_eq!(
vested_coins.amount,
Uint128::new(
5 * account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
5 * account.get_original_vesting().amount().amount.u128()
/ num_vesting_periods as u128
)
);
assert_eq!(
vesting_coins.amount,
Uint128::new(
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
- 5 * account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
account.get_original_vesting().amount().amount.u128()
- 5 * account.get_original_vesting().amount().amount.u128()
/ num_vesting_periods as u128
)
);
@@ -273,7 +240,7 @@ mod tests {
let block_time = Timestamp::from_seconds(
account.start_time().seconds() + vesting_over_period * vesting_period + 1,
);
let current_period = account.get_current_vesting_period(block_time).unwrap();
let current_period = account.get_current_vesting_period(block_time);
assert_eq!(current_period, Period::After);
let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -283,14 +250,7 @@ mod tests {
.unwrap();
assert_eq!(
vested_coins.amount,
Uint128::new(
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
)
Uint128::new(account.get_original_vesting().amount().amount.u128())
);
assert_eq!(vesting_coins.amount, Uint128::zero());
}
-269
View File
@@ -1,269 +0,0 @@
# This template contains all of the possible sections and their default values
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced, though in some cases a note
# will be
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# Root options
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
# dependency, such as, for example, the `nix` crate only being used via the
# `target_family = "unix"` configuration, that only having windows targets in
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db"
# The url(s) of the advisory databases to use
db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "allow"
# The lint level for crates that have been yanked from their source registry
yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
"RUSTSEC-2020-0159",
"RUSTSEC-2020-0071",
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
# will still output a note when they are encountered.
# * None - CVSS Score 0.0
# * Low - CVSS Score 0.1 - 3.9
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# The lint level for crates which do not have a detectable license
unlicensed = "deny"
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
#"MIT",
#"Apache-2.0",
]
# List of explicitly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
deny = [
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "warn"
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
# * both - The license will be approved if it is both OSI-approved *AND* FSF
# * either - The license will be approved if it is either OSI-approved *OR* FSF
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
# * neither - This predicate is ignored and the default lint level is used
allow-osi-fsf-free = "neither"
# Lint level used when no other predicates are matched
# 1. License isn't in the allow or deny lists
# 2. License isn't copyleft
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
default = "deny"
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
#[[licenses.clarify]]
# The name of the crate the clarification applies to
#name = "ring"
# The optional version constraint for the crate
#version = "*"
# The SPDX expression for the license requirements of the crate
#expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
# To see how to mark a crate as unpublished (to the official registry),
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overriden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overriden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# List of crates to deny
deny = [
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#name = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
skip-tree = [
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []
[sources.allow-org]
# 1 or more github.com organizations to allow git sources for
github = [""]
# 1 or more gitlab.com organizations to allow git sources for
gitlab = [""]
# 1 or more bitbucket.org organizations to allow git sources for
bitbucket = [""]
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.0.1"
version = "1.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+2
View File
@@ -41,6 +41,8 @@ pub(crate) async fn retrieve_mixnode_econ_stats(
Some(EconomicDynamicsStats {
stake_saturation: best_effort_small_dec_to_f64(stake_saturation.saturation) as f32,
uncapped_saturation: best_effort_small_dec_to_f64(stake_saturation.uncapped_saturation)
as f32,
active_set_inclusion_probability: inclusion_probability.in_active,
reserve_set_inclusion_probability: inclusion_probability.in_reserve,
// drop precision for compatibility sake
+2
View File
@@ -34,6 +34,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
pub layer: Layer,
pub mix_node: MixNode,
pub stake_saturation: f32,
pub uncapped_saturation: f32,
pub avg_uptime: u8,
pub estimated_operator_apy: f64,
pub estimated_delegators_apy: f64,
@@ -153,6 +154,7 @@ pub(crate) struct NodeStats {
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
pub(crate) struct EconomicDynamicsStats {
pub(crate) stake_saturation: f32,
pub(crate) uncapped_saturation: f32,
pub(crate) active_set_inclusion_probability: SelectionChance,
pub(crate) reserve_set_inclusion_probability: SelectionChance,
+2
View File
@@ -151,6 +151,8 @@ impl ThreadsafeMixNodesCache {
mix_node: node.mixnode_details.bond_information.mix_node.clone(),
avg_uptime: node.performance.round_to_integer(),
stake_saturation: best_effort_small_dec_to_f64(node.stake_saturation) as f32,
uncapped_saturation: best_effort_small_dec_to_f64(node.uncapped_stake_saturation)
as f32,
estimated_operator_apy: best_effort_small_dec_to_f64(node.estimated_operator_apy),
estimated_delegators_apy: best_effort_small_dec_to_f64(node.estimated_delegators_apy),
operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(),
@@ -6,14 +6,16 @@ export const EconomicsInfoColumns: ColumnsType[] = [
title: 'Estimated Total Reward',
flex: 1,
headerAlign: 'left',
tooltipInfo: 'Estimated reward per epoch for this profit margin if your node is selected in the active set.',
tooltipInfo:
'Estimated node reward (total for the operator and delegators) in the current epoch. There are roughly 24 epochs in a day.',
},
{
field: 'estimatedOperatorReward',
title: 'Estimated Operator Reward',
flex: 1,
headerAlign: 'left',
tooltipInfo: 'Estimated reward per epoch for this profit margin if your node is selected in the active set.',
tooltipInfo:
"Estimated operator's reward (including PM and Operating Cost) in the current epoch. There are roughly 24 epochs in a day.",
},
{
field: 'selectionChance',
@@ -29,7 +31,7 @@ export const EconomicsInfoColumns: ColumnsType[] = [
flex: 1,
headerAlign: 'left',
tooltipInfo:
'Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 1 million NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set.',
'Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 750k NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set.',
},
{
field: 'profitMargin',
@@ -37,7 +39,7 @@ export const EconomicsInfoColumns: ColumnsType[] = [
flex: 1,
headerAlign: 'left',
tooltipInfo:
'Percentage of the delegates rewards that the operator takes as fee before rewards are distributed to the delegates.',
'Percentage of the delegators rewards that the operator takes as fee before rewards are distributed to the delegators.',
},
{
field: 'operatingCost',
@@ -4,23 +4,8 @@ import { ApiState, MixNodeEconomicDynamicsStatsResponse } from '../../../typeDef
import { EconomicsInfoRowWithIndex } from './types';
import { toPercentIntegerString } from '../../../utils';
const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) => {
const inclusionProbability = economicDynamicsStats?.data?.active_set_inclusion_probability;
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
switch (inclusionProbability) {
case 'VeryLow':
return 'Very Low';
case 'VeryHigh':
return 'Very High';
case 'High':
case 'Good':
case 'Low':
case 'Moderate':
return inclusionProbability;
default:
return '-';
}
};
const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) =>
economicDynamicsStats?.data?.active_set_inclusion_probability || '-';
export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
const { economicDynamicsStats, mixNode } = useMixnodeContext();
@@ -29,7 +14,7 @@ export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
currencyToString((economicDynamicsStats?.data?.estimated_total_node_reward || '').toString()) || '-';
const estimatedOperatorRewards =
currencyToString((economicDynamicsStats?.data?.estimated_operator_reward || '').toString()) || '-';
const stakeSaturation = economicDynamicsStats?.data?.stake_saturation || '-';
const stakeSaturation = economicDynamicsStats?.data?.uncapped_saturation || '-';
const profitMargin = mixNode?.data?.profit_margin_percent
? toPercentIntegerString(mixNode?.data?.profit_margin_percent)
: '-';
+3 -3
View File
@@ -31,7 +31,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
const totalBond = pledge + delegations;
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
const profitPercentage = toPercentIntegerString(item.profit_margin_percent) || 0;
const stakeSaturation = typeof item.stake_saturation === 'number' ? item.stake_saturation * 100 : 0;
const uncappedSaturation = typeof item.uncapped_saturation === 'number' ? item.uncapped_saturation * 100 : 0;
return {
mix_id: item.mix_id,
@@ -47,7 +47,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
layer: item?.layer || '',
profit_percentage: `${profitPercentage}%`,
avg_uptime: `${item.avg_uptime}%` || '-',
stake_saturation: stakeSaturation,
operating_cost: `${unymToNym(item.operating_cost.amount, 6)} NYM`,
stake_saturation: uncappedSaturation,
operating_cost: `${unymToNym(item.operating_cost?.amount, 6)} NYM`,
};
}
+25 -8
View File
@@ -1,6 +1,6 @@
import * as React from 'react';
import { Link as RRDLink } from 'react-router-dom';
import { Box, Button, Card, Grid, Typography, Link as MuiLink } from '@mui/material';
import { Box, Button, Card, Grid, Link as MuiLink } from '@mui/material';
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { SelectChangeEvent } from '@mui/material/Select';
import { useMainContext } from '../../context/main';
@@ -12,6 +12,8 @@ import { Title } from '../../components/Title';
import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid';
import { currencyToString } from '../../utils/currency';
import { Tooltip } from '../../components/Tooltip';
import { BIG_DIPPER } from '../../api/constants';
import { splice } from '../../utils';
export const PageGateways: React.FC = () => {
const { gateways } = useMainContext();
@@ -69,9 +71,14 @@ export const PageGateways: React.FC = () => {
headerClassName: 'MuiDataGrid-header-override',
headerAlign: 'left',
renderCell: (params: GridRenderCellParams) => (
<Typography sx={cellStyles} data-testid="pledge-amount">
<MuiLink
sx={{ ...cellStyles }}
component={RRDLink}
to={`/network-components/gateway/${params.row.identityKey}`}
data-testid="pledge-amount"
>
{currencyToString(params.value)}
</Typography>
</MuiLink>
),
},
{
@@ -81,9 +88,14 @@ export const PageGateways: React.FC = () => {
headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => (
<Typography sx={cellStyles} data-testid="host">
<MuiLink
sx={{ ...cellStyles }}
component={RRDLink}
to={`/network-components/gateway/${params.row.identityKey}`}
data-testid="host"
>
{params.value}
</Typography>
</MuiLink>
),
},
{
@@ -120,9 +132,14 @@ export const PageGateways: React.FC = () => {
headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => (
<Typography sx={cellStyles} data-testid="owner">
{params.value}
</Typography>
<MuiLink
sx={{ ...cellStyles }}
href={`${BIG_DIPPER}/account/${params.value}`}
target="_blank"
data-testid="owner"
>
{splice(7, 29, params.value)}
</MuiLink>
),
},
];
+1 -1
View File
@@ -35,7 +35,7 @@ const columns: ColumnsType[] = [
},
{
field: 'self_percentage',
title: 'Self %',
title: 'Bond %',
headerAlign: 'left',
width: 99,
},
+4 -4
View File
@@ -149,7 +149,7 @@ export const PageMixnodes: React.FC = () => {
renderHeader: () => (
<CustomColumnHeading
headingTitle="Stake Saturation"
tooltipInfo="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 1 million NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set."
tooltipInfo="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 750k NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set."
/>
),
headerClassName: 'MuiDataGrid-header-override',
@@ -192,7 +192,7 @@ export const PageMixnodes: React.FC = () => {
renderHeader: () => (
<CustomColumnHeading
headingTitle="Profit Margin"
tooltipInfo="Percentage of the delegates rewards that the operator takes as fee before rewards are distributed to the delegates."
tooltipInfo="Percentage of the delegators rewards that the operator takes as fee before rewards are distributed to the delegators."
/>
),
headerClassName: 'MuiDataGrid-header-override',
@@ -210,10 +210,10 @@ export const PageMixnodes: React.FC = () => {
},
{
field: 'operating_cost',
headerName: 'Operating cost',
headerName: 'Operating Cost',
renderHeader: () => (
<CustomColumnHeading
headingTitle="Operating cost"
headingTitle="Operating Cost"
tooltipInfo="Monthly operational cost of running this node. This cost is set by the operator and it influences how the rewards are split between the operator and delegators."
/>
),
+3 -1
View File
@@ -85,6 +85,7 @@ export interface MixNodeResponseItem {
mix_node: MixNode;
avg_uptime: number;
stake_saturation: number;
uncapped_saturation: number;
operating_cost: Amount;
profit_margin_percent: string;
}
@@ -214,8 +215,9 @@ export type UptimeStoryResponse = {
export type MixNodeEconomicDynamicsStatsResponse = {
stake_saturation: number;
uncapped_saturation: number;
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
active_set_inclusion_probability: 'High' | 'Good' | 'Low' | 'VeryLow' | 'Moderate' | 'VeryHigh';
active_set_inclusion_probability: 'High' | 'Good' | 'Low';
reserve_set_inclusion_probability: 'High' | 'Good' | 'Low';
estimated_total_node_reward: number;
estimated_operator_reward: number;
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.0.2"
version = "1.1.0"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.0.2"
version = "1.1.0"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+17
View File
@@ -1,3 +1,20 @@
## [nym-connect-v1.1.0](https://github.com/nymtech/nym/tree/nym-connect-v1.1.0) (2022-11-09)
- nym-connect: rework of rewarding changes the directory data structures that describe the mixnet topology ([#1472])
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
[#1472]: https://github.com/nymtech/nym/pull/1472
[#1558]: https://github.com/nymtech/nym/pull/1558
[#1611]: https://github.com/nymtech/nym/pull/1611
[#1664]: https://github.com/nymtech/nym/pull/1664
[#1666]: https://github.com/nymtech/nym/pull/1666
[#1671]: https://github.com/nymtech/nym/pull/1671
## [nym-connect-v1.0.2](https://github.com/nymtech/nym/tree/nym-connect-v1.0.2) (2022-08-18)
### Changed
+2 -4
View File
@@ -3214,7 +3214,7 @@ dependencies = [
[[package]]
name = "nym-connect"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"bip39",
"client-core",
@@ -3247,7 +3247,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"clap",
"client-core",
@@ -3273,7 +3273,6 @@ dependencies = [
"snafu",
"socks5-requests",
"task",
"thiserror",
"tokio",
"topology",
"url",
@@ -6189,7 +6188,6 @@ version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"log",
"mixnet-contract-common",
"schemars",
"serde",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-connect"
version = "1.0.2"
version = "1.1.0"
description = "nym-connect"
authors = ["Nym Technologies SA"]
license = ""
+1 -14
View File
@@ -18,8 +18,6 @@ pub type StatusReceiver = futures::channel::oneshot::Receiver<Socks5StatusMessag
pub enum Socks5StatusMessage {
/// The SOCKS5 task successfully stopped
Stopped,
/// The SOCKS5 task failed to start
FailedToStart,
}
/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`.
@@ -45,18 +43,10 @@ pub fn start_nym_socks5_client(
// The status channel is used to both get the state of the task, and if it's closed, to check
// for panic.
std::thread::spawn(|| {
let result = tokio::runtime::Runtime::new()
tokio::runtime::Runtime::new()
.expect("Failed to create runtime for SOCKS5 client")
.block_on(async move { socks5_client.run_and_listen(socks5_ctrl_rx).await });
if let Err(err) = result {
log::error!("SOCKS5 proxy failed to start: {err}");
socks5_status_tx
.send(Socks5StatusMessage::FailedToStart)
.expect("Failed to send status message back to main task");
return;
}
log::info!("SOCKS5 task finished");
socks5_status_tx
.send(Socks5StatusMessage::Stopped)
@@ -79,9 +69,6 @@ pub fn start_disconnect_listener(
Ok(Socks5StatusMessage::Stopped) => {
log::info!("SOCKS5 task reported it has finished");
}
Ok(Socks5StatusMessage::FailedToStart) => {
log::info!("SOCKS5 task reported it failed to start");
}
Err(_) => {
log::info!("SOCKS5 task appears to have stopped abruptly");
// TODO: we should probably generate some events here, or otherwise signal to the
+1 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.0.2"
"version": "1.1.0"
},
"build": {
"distDir": "../dist",
BIN
View File
Binary file not shown.
+15 -3
View File
@@ -1,8 +1,21 @@
# Changelog
## Unreleased
## [nym-wallet-v1.1.0](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.0) (2022-11-09)
- Add window for showing logs for when the terminal is not available.
- wallet: Add window for showing logs for when the terminal is not available.
- wallet: Rework of rewarding ([#1472])
- wallet: Highlight delegations on an unbonded node to notify the delegator to undelegate stake
- wallet: Group delegations on unbonded nodes at the top of the list
- wallet: Simplified delegation screen when user has no delegations
- wallet: Update "create password" howto guide
- wallet: Allow setting of operatoring cost when node is bonded
- wallet: Allow update of operatoring cost on bonded node
- wallet: New bond settings area
- wallet: Display pending unbonds
- wallet: Display routing score and average score for gateways
- wallet: Improve dialogs for account creation
[#1472]: https://github.com/nymtech/nym/pull/1472
## [nym-wallet-v1.0.9](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.0.8) (2022-09-08)
@@ -14,7 +27,6 @@
- wallet: compound and redeem functionalities for operator rewards
- wallet: a few minor touch ups and bug fixes
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
- wallet: dark mode
+1 -2
View File
@@ -2877,7 +2877,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.0.9"
version = "1.1.0"
dependencies = [
"aes-gcm",
"argon2 0.3.4",
@@ -5423,7 +5423,6 @@ version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"log",
"mixnet-contract-common",
"schemars",
"serde",
+4 -4
View File
@@ -160,9 +160,9 @@ mod qa {
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n1qa4hswlcjmttulj0q9qa46jf64f93pecl6tydcsjldfe0hy5ju0sdmwzya";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
@@ -177,8 +177,8 @@ mod qa {
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://v2-env-validator.qa.nymte.ch/",
Some("https://v2-env-val-api.qa.nymte.ch/api/"),
"https://qwerty-validator.qa.nymte.ch/",
Some("https://qwerty-validator-api.qa.nymte.ch/api"),
)]
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym_wallet"
version = "1.0.9"
version = "1.1.0"
description = "Nym Native Wallet"
authors = ["Nym Technologies SA"]
license = ""
+1 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-wallet",
"version": "1.0.9"
"version": "1.1.0"
},
"build": {
"distDir": "../dist",
@@ -42,7 +42,7 @@ const headers: Header[] = [
header: 'Operator rewards',
id: 'operator-rewards',
tooltipText:
'This is your (operator) new rewards including the PM and cost. You can compound your rewards manually every epoch or unbond your node to redeem them.',
'This is your (operator) rewards including the PM and cost. Rewards are automatically compounded every epoch. You can redeem your rewards at any time.',
},
{
header: 'No. delegators',
@@ -179,7 +179,7 @@ const AmountFormData = ({
<CurrencyFormField
required
fullWidth
label="Operator cost"
label="Operator Cost"
autoFocus
onChanged={(newValue) => setValue('operatorCost', newValue, { shouldValidate: true })}
validationError={errors.operatorCost?.amount?.message}
+2 -9
View File
@@ -130,16 +130,10 @@ const TokenTransfer = () => {
</Grid>
<Grid item>
<Typography variant="subtitle2" sx={{ color: (t) => t.palette.nym.text.muted, mt: 2 }}>
Transferable tokens
Unlocked transferable tokens
</Typography>
<Typography
data-testid="refresh-success"
sx={{ color: 'text.primary' }}
variant="h5"
fontWeight="700"
textTransform="uppercase"
>
<Typography data-testid="refresh-success" sx={{ color: 'text.primary' }} variant="h5" textTransform="uppercase">
{userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()}
</Typography>
</Grid>
@@ -167,7 +161,6 @@ export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise<void> })
<NymCard
title="Vesting Schedule"
data-testid="check-unvested-tokens"
Icon={<InfoOutlined />}
Action={
<IconButton
onClick={async () => {
@@ -33,10 +33,17 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => {
</Grid>
<Grid item container direction={'column'} spacing={2} width={0.5} padding={3}>
<Grid item>
<Box sx={{ mb: 1 }}>
<Error
message={`Remember you should only unbond if you want to remove your ${
isMixnode(bondedNode) ? 'node' : 'gateway'
} from the network for good.`}
/>
</Box>
<Error
message={`Unbonding is irreversible and it wont be possible to restore the current state of your ${
isMixnode(bondedNode) ? 'node' : 'gateway'
} again`}
} again.`}
/>
</Grid>
<Grid item>

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