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 name: Daily security audit
on: on: workflow_dispatch
schedule:
- cron: '5 9 * * *'
jobs: jobs:
cargo-deny: security_audit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository code - uses: actions/checkout@v1
uses: actions/checkout@v2 - uses: actions-rs/audit-check@v1
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with: with:
toolchain: stable token: ${{ secrets.GITHUB_TOKEN }}
- 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
notification: notification:
needs: cargo-deny if: ${{ failure() }}
needs: security_audit
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v2 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 - name: Keybase - Node Install
run: npm install run: npm install
working-directory: .github/workflows/support-files working-directory: .github/workflows/support-files
- name: Keybase - Send Notification - name: Keybase - Send Notification
env: env:
NYM_NOTIFICATION_KIND: security NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Daily security report" NYM_PROJECT_NAME: "Nym daily audit"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 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_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "security" KEYBASE_NYM_CHANNEL: "test"
uses: docker://keybaseio/client:stable-node uses: docker://keybaseio/client:stable-node
with: with:
args: .github/workflows/support-files/notifications/entry_point.sh args: .github/workflows/support-files/notifications/entry_point.sh
@@ -25,6 +25,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - 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-` - name: Check the release tag starts with `nym-binaries-`
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch' if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3 uses: actions/github-script@v3
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot'); const Bot = require('keybase-bot');
let context = { 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). 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 ### 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). - 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 ### 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]) - 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 ### 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]) - 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 [#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558 [#1558]: https://github.com/nymtech/nym/pull/1558
[#1577]: https://github.com/nymtech/nym/pull/1577 [#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 [#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640 [#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645 [#1645]: https://github.com/nymtech/nym/pull/1645
[#1611]: https://github.com/nymtech/nym/pull/1611
[#1664]: https://github.com/nymtech/nym/pull/1664 [#1664]: https://github.com/nymtech/nym/pull/1664
[#1666]: https://github.com/nymtech/nym/pull/1645 [#1666]: https://github.com/nymtech/nym/pull/1645
[#1669]: https://github.com/nymtech/nym/pull/1669 [#1669]: https://github.com/nymtech/nym/pull/1669
[#1671]: https://github.com/nymtech/nym/pull/1671 [#1671]: https://github.com/nymtech/nym/pull/1671
[#1673]: https://github.com/nymtech/nym/pull/1673 [#1673]: https://github.com/nymtech/nym/pull/1673
[#1681]: https://github.com/nymtech/nym/pull/1681 [#1681]: https://github.com/nymtech/nym/pull/1681
[#1687]: https://github.com/nymtech/nym/pull/1687
[#1702]: https://github.com/nymtech/nym/pull/1702 [#1702]: https://github.com/nymtech/nym/pull/1702
[#1703]: https://github.com/nymtech/nym/pull/1703 [#1703]: https://github.com/nymtech/nym/pull/1703
[#1713]: https://github.com/nymtech/nym/pull/1713
[#1721]: https://github.com/nymtech/nym/pull/1721 [#1721]: https://github.com/nymtech/nym/pull/1721
[#1724]: https://github.com/nymtech/nym/pull/1724 [#1724]: https://github.com/nymtech/nym/pull/1724
[#1725]: https://github.com/nymtech/nym/pull/1725 [#1725]: https://github.com/nymtech/nym/pull/1725
Generated
+9 -12
View File
@@ -578,7 +578,7 @@ dependencies = [
[[package]] [[package]]
name = "client-core" name = "client-core"
version = "1.0.1" version = "1.1.0"
dependencies = [ dependencies = [
"config", "config",
"crypto", "crypto",
@@ -1582,7 +1582,7 @@ dependencies = [
[[package]] [[package]]
name = "explorer-api" name = "explorer-api"
version = "1.0.1" version = "1.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap 3.2.8", "clap 3.2.8",
@@ -3070,7 +3070,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.0.0" version = "1.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -3122,7 +3122,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"client-core", "client-core",
@@ -3147,7 +3147,6 @@ dependencies = [
"serde_json", "serde_json",
"sled", "sled",
"task", "task",
"thiserror",
"tokio", "tokio",
"tokio-tungstenite 0.14.0", "tokio-tungstenite 0.14.0",
"topology", "topology",
@@ -3160,7 +3159,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-gateway" name = "nym-gateway"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3207,7 +3206,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bs58", "bs58",
@@ -3249,7 +3248,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"clap 3.2.8", "clap 3.2.8",
@@ -3296,7 +3295,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"client-core", "client-core",
@@ -3324,7 +3323,6 @@ dependencies = [
"snafu 0.6.10", "snafu 0.6.10",
"socks5-requests", "socks5-requests",
"task", "task",
"thiserror",
"tokio", "tokio",
"topology", "topology",
"url", "url",
@@ -3361,7 +3359,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-validator-api" name = "nym-validator-api"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -6516,7 +6514,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"contracts-common", "contracts-common",
"cosmwasm-std", "cosmwasm-std",
"log",
"mixnet-contract-common", "mixnet-contract-common",
"schemars", "schemars",
"serde", "serde",
-3
View File
@@ -122,6 +122,3 @@ mixnet-opt: wasm
generate-typescript: generate-typescript:
cd tools/ts-rs-cli && cargo run && cd ../.. cd tools/ts-rs-cli && cargo run && cd ../..
yarn types:lint:fix yarn types:lint:fix
run-validator-tests:
cd validator-api/tests/functional_test && yarn test
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "client-core" name = "client-core"
version = "1.0.1" version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021" edition = "2021"
@@ -296,7 +296,7 @@ where
async fn on_message(&mut self, next_message: StreamMessage) { async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message"); trace!("created new message");
let (next_message, fragment_id) = match next_message { let next_message = match next_message {
StreamMessage::Cover => { StreamMessage::Cover => {
// TODO for way down the line: in very rare cases (during topology update) we might have // 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 // 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(); let topology_ref = topology_ref_option.unwrap();
( generate_loop_cover_packet(
generate_loop_cover_packet( &mut self.rng,
&mut self.rng, topology_ref,
topology_ref, &self.ack_key,
&self.ack_key, &self.our_full_destination,
&self.our_full_destination, self.config.average_ack_delay,
self.config.average_ack_delay, self.config.average_packet_delay,
self.config.average_packet_delay, self.config.cover_packet_size,
self.config.cover_packet_size,
)
.expect(
"Somehow failed to generate a loop cover message with a valid topology",
),
None,
) )
.expect("Somehow failed to generate a loop cover message with a valid topology")
} }
StreamMessage::Real(real_message) => { 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); 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, // 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 // 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 // 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> { ) -> Option<&'a NymTopology> {
// Note: implicit deref with Deref for TopologyReadPermit is happening here // Note: implicit deref with Deref for TopologyReadPermit is happening here
let topology_ref_option = self.permit.as_ref(); let topology_ref_option = self.permit.as_ref();
topology_ref_option.as_ref().filter(|topology_ref| { match topology_ref_option {
!(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS) None => None,
|| !topology_ref.gateway_exists(ack_recipient.gateway()) Some(topology_ref) => {
|| if let Some(packet_recipient) = packet_recipient { // see if it's possible to route the packet to both gateways
!topology_ref.gateway_exists(packet_recipient.gateway()) 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 { } else {
false Some(topology_ref)
}) }
}) }
}
} }
} }
-2
View File
@@ -24,6 +24,4 @@ pub enum ClientCoreError {
ListOfValidatorApisIsEmpty, ListOfValidatorApisIsEmpty,
#[error("Could not load existing gateway configuration: {0}")] #[error("Could not load existing gateway configuration: {0}")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error), 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] [package]
name = "nym-client" 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>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client" description = "Implementation of the Nym Client"
edition = "2021" 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 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 serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys 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 = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
tokio-tungstenite = "0.14" # websocket tokio-tungstenite = "0.14" # websocket
+8 -13
View File
@@ -18,7 +18,6 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
}; };
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity; use crypto::asymmetric::identity;
use futures::channel::mpsc; use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController; use gateway_client::bandwidth::BandwidthController;
@@ -34,7 +33,6 @@ use nymsphinx::receiver::ReconstructedMessage;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier}; use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use crate::client::config::{Config, SocketType}; use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use crate::websocket; use crate::websocket;
pub(crate) mod config; pub(crate) mod config;
@@ -234,7 +232,7 @@ impl NymClient {
&mut self, &mut self,
topology_accessor: TopologyAccessor, topology_accessor: TopologyAccessor,
shutdown: ShutdownListener, shutdown: ShutdownListener,
) -> Result<(), ClientError> { ) {
let topology_refresher_config = TopologyRefresherConfig::new( let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(), self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(), self.config.get_base().get_topology_refresh_rate(),
@@ -249,16 +247,14 @@ impl NymClient {
// TODO: a slightly more graceful termination here // TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await { if !topology_refresher.is_topology_routable().await {
log::error!( panic!(
"The current network topology seem to be insufficient to route any packets through \ "The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online" - check if enough nodes and a gateway are online"
); );
return Err(ClientCoreError::InsufficientNetworkTopology.into());
} }
info!("Starting topology refresher..."); info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown); topology_refresher.start_with_shutdown(shutdown);
Ok(())
} }
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic) // 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) /// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) -> Result<(), ClientError> { pub async fn run_forever(&mut self) {
let shutdown = self.start().await?; let shutdown = self.start().await;
wait_for_signal().await; wait_for_signal().await;
println!( println!(
@@ -350,10 +346,9 @@ impl NymClient {
//shutdown.wait_for_shutdown().await; //shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-client"); 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"); info!("Starting nym client");
// channels for inter-component communication // channels for inter-component communication
// TODO: make the channels be internally created by the relevant components // 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, // the components are started in very specific order. Unless you know what you are doing,
// do not change that. // do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe()) self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?; .await;
self.start_received_messages_buffer_controller( self.start_received_messages_buffer_controller(
received_buffer_request_receiver, received_buffer_request_receiver,
mixnet_messages_receiver, mixnet_messages_receiver,
@@ -448,6 +443,6 @@ impl NymClient {
info!("Client startup finished!"); info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient()); 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 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType}; use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use clap::CommandFactory; use clap::CommandFactory;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell}; use completions::{fig_generate, ArgShell};
@@ -84,17 +83,16 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool, 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"; let bin_name = "nym-native-client";
match &args.command { match &args.command {
Commands::Init(m) => init::execute(m).await, 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::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name), Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_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 { pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
+4 -5
View File
@@ -4,7 +4,6 @@
use crate::{ use crate::{
client::{config::Config, NymClient}, client::{config::Config, NymClient},
commands::{override_config, OverrideConfig}, commands::{override_config, OverrideConfig},
error::ClientError,
}; };
use clap::Args; 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 id = &args.id;
let mut config = match Config::load_from_file(Some(id)) { let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, 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) { if !version_check(&config) {
error!("failed the local version check"); 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 // SPDX-License-Identifier: Apache-2.0
pub mod client; pub mod client;
pub mod error;
pub mod websocket; pub mod websocket;
+2 -4
View File
@@ -2,23 +2,21 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser}; use clap::{crate_version, Parser};
use error::ClientError;
use logging::setup_logging; use logging::setup_logging;
use network_defaults::setup_env; use network_defaults::setup_env;
pub mod client; pub mod client;
pub mod commands; pub mod commands;
pub mod error;
pub mod websocket; pub mod websocket;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), ClientError> { async fn main() {
setup_logging(); setup_logging();
println!("{}", banner()); println!("{}", banner());
let args = commands::Cli::parse(); let args = commands::Cli::parse();
setup_env(args.config_env_file.clone()); setup_env(args.config_env_file.clone());
commands::execute(&args).await commands::execute(&args).await;
} }
fn banner() -> String { fn banner() -> String {
+1 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.0.2" version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] 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" description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021" edition = "2021"
@@ -20,7 +20,6 @@ pretty_env_logger = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] } rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
snafu = "0.6" snafu = "0.6"
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
url = "2.2" url = "2.2"
+10 -19
View File
@@ -4,7 +4,6 @@
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::client::config::Config; use crate::client::config::Config;
use crate::error::Socks5ClientError;
use crate::socks::{ use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User}, authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer, server::SphinxSocksServer,
@@ -24,7 +23,6 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
}; };
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity; use crypto::asymmetric::identity;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
@@ -234,7 +232,7 @@ impl NymClient {
&mut self, &mut self,
topology_accessor: TopologyAccessor, topology_accessor: TopologyAccessor,
shutdown: ShutdownListener, shutdown: ShutdownListener,
) -> Result<(), Socks5ClientError> { ) {
let topology_refresher_config = TopologyRefresherConfig::new( let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(), self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(), self.config.get_base().get_topology_refresh_rate(),
@@ -249,16 +247,14 @@ impl NymClient {
// TODO: a slightly more graceful termination here // TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await { if !topology_refresher.is_topology_routable().await {
log::error!( panic!(
"The current network topology seem to be insufficient to route any packets through \ "The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online" - check if enough nodes and a gateway are online"
); );
return Err(ClientCoreError::InsufficientNetworkTopology.into());
} }
info!("Starting topology refresher..."); info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown); topology_refresher.start_with_shutdown(shutdown);
Ok(())
} }
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic) // 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) /// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) -> Result<(), Socks5ClientError> { pub async fn run_forever(&mut self) {
let mut shutdown = self.start().await?; let mut shutdown = self.start().await;
wait_for_signal().await; wait_for_signal().await;
log::info!("Sending shutdown"); log::info!("Sending shutdown");
@@ -309,15 +305,11 @@ impl NymClient {
shutdown.wait_for_shutdown().await; shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client"); log::info!("Stopping nym-socks5-client");
Ok(())
} }
// Variant of `run_forever` that listends for remote control messages // Variant of `run_forever` that listends for remote control messages
pub async fn run_and_listen( pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
&mut self, let mut shutdown = self.start().await;
mut receiver: Socks5ControlMessageReceiver,
) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
tokio::select! { tokio::select! {
message = receiver.next() => { message = receiver.next() => {
log::debug!("Received message: {:?}", message); log::debug!("Received message: {:?}", message);
@@ -343,10 +335,9 @@ impl NymClient {
shutdown.wait_for_shutdown().await; shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client"); 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"); info!("Starting nym client");
// channels for inter-component communication // channels for inter-component communication
// TODO: make the channels be internally created by the relevant components // 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, // the components are started in very specific order. Unless you know what you are doing,
// do not change that. // do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe()) self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?; .await;
self.start_received_messages_buffer_controller( self.start_received_messages_buffer_controller(
received_buffer_request_receiver, received_buffer_request_receiver,
mixnet_messages_receiver, mixnet_messages_receiver,
@@ -426,6 +417,6 @@ impl NymClient {
info!("Client startup finished!"); info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient()); 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 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config; use crate::client::config::Config;
use crate::error::Socks5ClientError;
use clap::CommandFactory; use clap::CommandFactory;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell}; use completions::{fig_generate, ArgShell};
@@ -84,17 +83,16 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool, 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"; let bin_name = "nym-socks5-client";
match &args.command { match &args.command {
Commands::Init(m) => init::execute(m).await, 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::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name), Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_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 { pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
+4 -5
View File
@@ -4,7 +4,6 @@
use crate::{ use crate::{
client::{config::Config, NymClient}, client::{config::Config, NymClient},
commands::{override_config, OverrideConfig}, commands::{override_config, OverrideConfig},
error::Socks5ClientError,
}; };
use clap::Args; 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 id = &args.id;
let mut config = match Config::load_from_file(Some(id)) { let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, 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) { if !version_check(&config) {
error!("failed the local version check"); 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 // SPDX-License-Identifier: Apache-2.0
pub mod client; pub mod client;
pub mod error;
pub mod socks; pub mod socks;
+2 -4
View File
@@ -2,23 +2,21 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser}; use clap::{crate_version, Parser};
use error::Socks5ClientError;
use logging::setup_logging; use logging::setup_logging;
use network_defaults::setup_env; use network_defaults::setup_env;
pub mod client; pub mod client;
mod commands; mod commands;
pub mod error;
pub mod socks; pub mod socks;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Socks5ClientError> { async fn main() {
setup_logging(); setup_logging();
println!("{}", banner()); println!("{}", banner());
let args = commands::Cli::parse(); let args = commands::Cli::parse();
setup_env(args.config_env_file.clone()); setup_env(args.config_env_file.clone());
commands::execute(&args).await commands::execute(&args).await;
} }
fn banner() -> String { fn banner() -> String {
+3 -5
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "nym-client-wasm" name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.0.1" version = "1.0.0"
edition = "2021" edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"] keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0" license = "Apache-2.0"
@@ -55,8 +55,6 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3" wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release] [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'); const path = require('path');
module.exports = { module.exports = {
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
entry: { entry: {
bootstrap: './bootstrap.js', bootstrap: './bootstrap.js',
worker: './worker.js', worker: './worker.js',
@@ -27,7 +22,6 @@ module.exports = {
}, },
], ],
}), }),
], ],
experiments: { syncWebAssembly: true }, experiments: { syncWebAssembly: true },
}; };
-1
View File
@@ -73,7 +73,6 @@ async function main() {
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM'; // const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
const gatewayEndpoint = await get_gateway(validator, preferredGateway); 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 // only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null) // (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. /// * `event`: event to search through.
/// * `key`: key associated with the particular attribute /// * `key`: key associated with the particular attribute
pub fn must_find_attribute(event: &Event, key: &str) -> String { 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() may_find_attribute(event, key).unwrap()
} }
@@ -1,9 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
pub mod events; pub mod events;
pub mod types; pub mod types;
@@ -72,7 +72,7 @@ impl Percent {
impl Display for Percent { impl Display for Percent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 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) write!(f, "{}%", adjusted)
} }
} }
@@ -1,9 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Decimal, Uint128}; use cosmwasm_std::Decimal;
pub const TOKEN_SUPPLY: Uint128 = Uint128::new(1_000_000_000_000_000);
// I'm still not 100% sure how to feel about existence of this file // 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. // This is equivalent of representing our display coin with 6 decimal places.
@@ -4,10 +4,8 @@
// due to code generated by JsonSchema // due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)] #![allow(clippy::field_reassign_with_default)]
use crate::constants::TOKEN_SUPPLY;
use crate::helpers::IntoBaseDecimal;
use crate::{Addr, MixId}; use crate::{Addr, MixId};
use cosmwasm_std::{Coin, Decimal, StdResult}; use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -62,11 +60,6 @@ impl Delegation {
height: u64, height: u64,
proxy: Option<Addr>, proxy: Option<Addr>,
) -> Self { ) -> Self {
assert!(
amount.amount <= TOKEN_SUPPLY,
"delegation cannot be larger than the token supply"
);
Delegation { Delegation {
owner, owner,
mix_id, mix_id,
@@ -94,8 +87,10 @@ impl Delegation {
(mix_id, owner_proxy_subkey) (mix_id, owner_proxy_subkey)
} }
pub fn dec_amount(&self) -> StdResult<Decimal> { pub fn dec_amount(&self) -> Decimal {
self.amount.amount.into_base_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 { pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Decimal, StdError, StdResult, Uint128}; use cosmwasm_std::Decimal;
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<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)); 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) 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 { impl Interval {
/// Initialize epoch in the contract with default values. /// Initialize epoch in the contract with default values.
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self { 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 { Interval {
id: 0, id: 0,
epochs_in_interval, 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, current_epoch_id: 0,
epoch_length, epoch_length,
total_elapsed_epochs: 0, total_elapsed_epochs: 0,
@@ -1,9 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
mod constants; mod constants;
pub mod delegation; pub mod delegation;
pub mod error; pub mod error;
@@ -4,14 +4,13 @@
// due to code generated by JsonSchema // due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)] #![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::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams}; use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward; use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution; use crate::rewarding::RewardDistribution;
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey}; use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
@@ -65,7 +64,7 @@ impl MixNodeDetails {
self.rewarding_details.pending_operator_reward(pledge) 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(); let pledge = self.original_pledge();
self.rewarding_details self.rewarding_details
.pending_detailed_operator_reward(pledge) .pending_detailed_operator_reward(pledge)
@@ -108,21 +107,16 @@ impl MixNodeRewarding {
cost_params: MixNodeCostParams, cost_params: MixNodeCostParams,
initial_pledge: &Coin, initial_pledge: &Coin,
current_epoch: EpochId, current_epoch: EpochId,
) -> Result<Self, MixnetContractError> { ) -> Self {
assert!( MixNodeRewarding {
initial_pledge.amount <= TOKEN_SUPPLY,
"pledge cannot be larger than the token supply"
);
Ok(MixNodeRewarding {
cost_params, cost_params,
operator: initial_pledge.amount.into_base_decimal()?, operator: Decimal::from_atomics(initial_pledge.amount, 0).unwrap(),
delegates: Decimal::zero(), delegates: Decimal::zero(),
total_unit_reward: Decimal::zero(), total_unit_reward: Decimal::zero(),
unit_delegation: UNIT_DELEGATION_BASE, unit_delegation: UNIT_DELEGATION_BASE,
last_rewarded_epoch: current_epoch, last_rewarded_epoch: current_epoch,
unique_delegations: 0, unique_delegations: 0,
}) }
} }
/// Determines whether this node is still bonded. This is performed via a simple check, /// 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> { pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> Decimal {
let initial_dec = original_pledge.amount.into_base_decimal()?; let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
if initial_dec > self.operator { if initial_dec > self.operator {
panic!( panic!(
"seems slashing has occurred while it has not been implemented nor accounted for!" "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 { pub fn operator_pledge_with_reward(&self, denom: impl Into<String>) -> Coin {
truncate_reward(self.operator, denom) truncate_reward(self.operator, denom)
} }
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> { pub fn pending_delegator_reward(&self, delegation: &Delegation) -> Coin {
let delegator_reward = self.determine_delegation_reward(delegation)?; let delegator_reward = self.determine_delegation_reward(delegation);
Ok(truncate_reward(delegator_reward, &delegation.amount.denom)) truncate_reward(delegator_reward, &delegation.amount.denom)
} }
pub fn withdraw_operator_reward( pub fn withdraw_operator_reward(&mut self, original_pledge: &Coin) -> Coin {
&mut self, let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
original_pledge: &Coin,
) -> Result<Coin, MixnetContractError> {
let initial_dec = original_pledge.amount.into_base_decimal()?;
if initial_dec > self.operator { if initial_dec > self.operator {
panic!( panic!(
"seems slashing has occurred while it has not been implemented nor accounted for!" "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; let diff = self.operator - initial_dec;
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( pub fn withdraw_delegator_reward(
&mut self, &mut self,
delegation: &mut Delegation, delegation: &mut Delegation,
) -> Result<Coin, MixnetContractError> { ) -> Result<Coin, MixnetContractError> {
let reward = self.determine_delegation_reward(delegation)?; let reward = self.determine_delegation_reward(delegation);
self.decrease_delegates_decimal(reward)?; self.decrease_delegates_decimal(reward)?;
delegation.cumulative_reward_ratio = self.full_reward_ratio(); delegation.cumulative_reward_ratio = self.full_reward_ratio();
@@ -310,27 +301,23 @@ impl MixNodeRewarding {
self.distribute_rewards(reward_distribution, absolute_epoch_id) 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 starting_ratio = delegation.cumulative_reward_ratio;
let ending_ratio = self.full_reward_ratio(); let ending_ratio = self.full_reward_ratio();
let adjust = starting_ratio + self.unit_delegation; 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 // this updates `unique_delegations` field
pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> { pub fn add_base_delegation(&mut self, amount: Uint128) {
self.increase_delegates_uint128(amount)?; self.increase_delegates_uint128(amount);
self.unique_delegations += 1; self.unique_delegations += 1;
Ok(())
} }
pub fn increase_delegates_uint128( pub fn increase_delegates_uint128(&mut self, amount: Uint128) {
&mut self, // the unwrap here is fine as the value is guaranteed to fit under provided constraints
amount: Uint128, self.delegates += Decimal::from_atomics(amount, 0).unwrap()
) -> Result<(), MixnetContractError> {
self.delegates += amount.into_base_decimal()?;
Ok(())
} }
// this updates `unique_delegations` field // this updates `unique_delegations` field
@@ -348,7 +335,7 @@ impl MixNodeRewarding {
&mut self, &mut self,
amount: Uint128, amount: Uint128,
) -> Result<(), MixnetContractError> { ) -> Result<(), MixnetContractError> {
let amount_dec = amount.into_base_decimal()?; let amount_dec = Decimal::from_atomics(amount, 0).unwrap();
self.decrease_delegates_decimal(amount_dec) self.decrease_delegates_decimal(amount_dec)
} }
@@ -381,8 +368,8 @@ impl MixNodeRewarding {
} }
pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> { pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> {
let reward = self.determine_delegation_reward(delegation)?; let reward = self.determine_delegation_reward(delegation);
let full_amount = reward + delegation.dec_amount()?; let full_amount = reward + delegation.dec_amount();
self.remove_delegation_decimal(full_amount)?; self.remove_delegation_decimal(full_amount)?;
Ok(truncate_reward(full_amount, &delegation.amount.denom)) Ok(truncate_reward(full_amount, &delegation.amount.denom))
} }
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::delegation::OwnerProxySubKey; use crate::delegation::OwnerProxySubKey;
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{ use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams, IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
@@ -43,17 +41,14 @@ pub struct InitialRewardingParams {
} }
impl InitialRewardingParams { impl InitialRewardingParams {
pub fn into_rewarding_params( pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams {
self,
epochs_in_interval: u32,
) -> Result<RewardingParams, MixnetContractError> {
let epoch_reward_budget = self.initial_reward_pool 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; * self.interval_pool_emission;
let stake_saturation_point = 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 { interval: IntervalRewardParams {
reward_pool: self.initial_reward_pool, reward_pool: self.initial_reward_pool,
staking_supply: self.initial_staking_supply, staking_supply: self.initial_staking_supply,
@@ -66,7 +61,7 @@ impl InitialRewardingParams {
}, },
rewarded_set_size: self.rewarded_set_size, rewarded_set_size: self.rewarded_set_size,
active_set_size: self.active_set_size, active_set_size: self.active_set_size,
}) }
} }
} }
@@ -1,7 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::helpers::IntoBaseDecimal;
use crate::{error::MixnetContractError, Percent}; use crate::{error::MixnetContractError, Percent};
use cosmwasm_std::Decimal; use cosmwasm_std::Decimal;
use schemars::JsonSchema; use schemars::JsonSchema;
@@ -111,33 +110,24 @@ impl RewardingParams {
pub fn dec_rewarded_set_size(&self) -> Decimal { 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 // the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places // with 0 decimal places
#[allow(clippy::unwrap_used)] Decimal::from_atomics(self.rewarded_set_size, 0).unwrap()
self.rewarded_set_size.into_base_decimal().unwrap()
} }
pub fn dec_active_set_size(&self) -> Decimal { 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 // the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places // with 0 decimal places
#[allow(clippy::unwrap_used)] Decimal::from_atomics(self.active_set_size, 0).unwrap()
self.active_set_size.into_base_decimal().unwrap()
} }
fn dec_standby_set_size(&self) -> Decimal { 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 // the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places // with 0 decimal places
#[allow(clippy::unwrap_used)] Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap()
(self.rewarded_set_size - self.active_set_size)
.into_base_decimal()
.unwrap()
} }
pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) { 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 self.interval.epoch_reward_budget = self.interval.reward_pool
// with 0 decimal places / Decimal::from_atomics(new_epochs_in_interval, 0).unwrap()
#[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.interval_pool_emission; * self.interval.interval_pool_emission;
} }
@@ -210,13 +200,13 @@ impl RewardingParams {
if recompute_epoch_budget { if recompute_epoch_budget {
self.interval.epoch_reward_budget = self.interval.reward_pool 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; * self.interval.interval_pool_emission;
} }
if recompute_saturation_point { if recompute_saturation_point {
self.interval.stake_saturation_point = self.interval.stake_saturation_point = self.interval.staking_supply
self.interval.staking_supply / self.rewarded_set_size.into_base_decimal()? / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
} }
Ok(()) Ok(())
@@ -29,7 +29,7 @@ pub struct RewardEstimate {
pub operating_cost: Decimal, 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 struct RewardDistribution {
pub operator: Decimal, pub operator: Decimal,
pub delegates: Decimal, pub delegates: Decimal,
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError; use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::reward_params::NodeRewardParams; use crate::reward_params::NodeRewardParams;
use crate::rewarding::simulator::simulated_node::SimulatedNode; use crate::rewarding::simulator::simulated_node::SimulatedNode;
use crate::rewarding::RewardDistribution; 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(); let updated = self.interval.advance_epoch();
// we rolled over an interval // we rolled over an interval
@@ -48,13 +47,10 @@ impl Simulator {
.staking_supply_scale_factor .staking_supply_scale_factor
* self.pending_reward_pool_emission; * self.pending_reward_pool_emission;
let epoch_reward_budget = reward_pool 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(); * old.interval_pool_emission.value();
let stake_saturation_point = staking_supply let stake_saturation_point = staking_supply
/ self / Decimal::from_atomics(self.system_rewarding_params.rewarded_set_size, 0).unwrap();
.system_rewarding_params
.rewarded_set_size
.into_base_decimal()?;
let updated_params = RewardingParams { let updated_params = RewardingParams {
interval: IntervalRewardParams { interval: IntervalRewardParams {
@@ -75,15 +71,9 @@ impl Simulator {
self.pending_reward_pool_emission = Decimal::zero(); self.pending_reward_pool_emission = Decimal::zero();
} }
self.interval = updated; self.interval = updated;
Ok(())
} }
pub fn bond( pub fn bond(&mut self, pledge: Coin, cost_params: MixNodeCostParams) -> MixId {
&mut self,
pledge: Coin,
cost_params: MixNodeCostParams,
) -> Result<MixId, MixnetContractError> {
let mix_id = self.next_mix_id; let mix_id = self.next_mix_id;
self.nodes.insert( self.nodes.insert(
@@ -93,24 +83,16 @@ impl Simulator {
cost_params, cost_params,
&pledge, &pledge,
self.interval.current_epoch_absolute_id(), self.interval.current_epoch_absolute_id(),
)?, ),
); );
self.next_mix_id += 1; self.next_mix_id += 1;
Ok(mix_id) mix_id
} }
pub fn delegate<S: Into<String>>( pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin, mix_id: MixId) {
&mut self, let node = self.nodes.get_mut(&mix_id).expect("node doesn't exist");
delegator: S,
delegation: Coin,
mix_id: MixId,
) -> Result<(), MixnetContractError> {
let node = self
.nodes
.get_mut(&mix_id)
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
node.delegate(delegator, delegation) node.delegate(delegator, delegation)
} }
@@ -121,35 +103,23 @@ impl Simulator {
delegator: S, delegator: S,
mix_id: MixId, mix_id: MixId,
) -> Result<(Coin, Coin), MixnetContractError> { ) -> Result<(Coin, Coin), MixnetContractError> {
let node = self let node = self.nodes.get_mut(&mix_id).expect("node not found");
.nodes
.get_mut(&mix_id)
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
node.undelegate(delegator) node.undelegate(delegator)
} }
pub fn simulate_epoch_single_node( pub fn simulate_epoch_single_node(&mut self, params: NodeRewardParams) -> RewardDistribution {
&mut self,
params: NodeRewardParams,
) -> Result<RewardDistribution, MixnetContractError> {
assert_eq!(self.nodes.len(), 1); assert_eq!(self.nodes.len(), 1);
if let Some(&id) = self.nodes.keys().next() { let id = *self.nodes.keys().next().unwrap();
let mut params_map = BTreeMap::new(); let mut params_map = BTreeMap::new();
params_map.insert(id, params); params_map.insert(id, params);
Ok(self self.simulate_epoch(&params_map).remove(&id).unwrap()
.simulate_epoch(&params_map)?
.remove(&id)
.unwrap_or_default())
} else {
Ok(RewardDistribution::default())
}
} }
pub fn simulate_epoch( pub fn simulate_epoch(
&mut self, &mut self,
node_params: &BTreeMap<MixId, NodeRewardParams>, node_params: &BTreeMap<MixId, NodeRewardParams>,
) -> Result<BTreeMap<MixId, RewardDistribution>, MixnetContractError> { ) -> BTreeMap<MixId, RewardDistribution> {
let mut params_keys = node_params.keys().copied().collect::<Vec<_>>(); let mut params_keys = node_params.keys().copied().collect::<Vec<_>>();
params_keys.sort_unstable(); params_keys.sort_unstable();
let mut node_keys = self.nodes.keys().copied().collect::<Vec<_>>(); let mut node_keys = self.nodes.keys().copied().collect::<Vec<_>>();
@@ -177,41 +147,34 @@ impl Simulator {
dist.insert(*mix_id, reward_distribution); dist.insert(*mix_id, reward_distribution);
} }
self.advance_epoch()?; self.advance_epoch();
Ok(dist) dist
} }
pub fn determine_delegation_reward( pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
&self, self.nodes[&delegation.mix_id]
delegation: &Delegation,
) -> Result<Decimal, MixnetContractError> {
Ok(self.nodes[&delegation.mix_id]
.rewarding_details .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(); let mut total = Decimal::zero();
for node in self.nodes.values() { for node in self.nodes.values() {
for delegation in node.delegations.values() { for delegation in node.delegations.values() {
total += node total += node
.rewarding_details .rewarding_details
.determine_delegation_reward(delegation)? .determine_delegation_reward(delegation)
} }
} }
Ok(total) total
} }
// assume node state doesn't change in the interval (kinda unrealistic) // assume node state doesn't change in the interval (kinda unrealistic)
pub fn simulate_full_interval( pub fn simulate_full_interval(&mut self, node_params: &BTreeMap<MixId, NodeRewardParams>) {
&mut self,
node_params: &BTreeMap<MixId, NodeRewardParams>,
) -> Result<(), MixnetContractError> {
for _ in 0..self.interval.epochs_in_interval() { 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 cosmwasm_std::testing::mock_env;
use std::time::Duration; 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 { fn base_simulator(initial_pledge: u128) -> Simulator {
let profit_margin = Percent::from_percentage_value(10).unwrap(); let profit_margin = Percent::from_percentage_value(10).unwrap();
let interval_operating_cost = Coin::new(40_000_000, "unym"); let interval_operating_cost = Coin::new(40_000_000, "unym");
@@ -279,32 +238,20 @@ mod tests {
profit_margin_percent: profit_margin, profit_margin_percent: profit_margin,
interval_operating_cost, interval_operating_cost,
}; };
simulator.bond(initial_pledge, cost_params).unwrap(); simulator.bond(initial_pledge, cost_params);
simulator simulator
} }
// essentially our delegations + estimated rewards HAVE TO equal to what we actually determined // 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) { fn check_rewarding_invariant(simulator: &Simulator) {
for node in simulator.nodes.values() { for node in simulator.nodes.values() {
let delegation_sum: Decimal = node let delegation_sum: Decimal =
.delegations node.delegations.values().map(|d| d.dec_amount()).sum();
.values()
.map(|d| d.dec_amount().unwrap())
.sum();
let reward_sum: Decimal = node let reward_sum: Decimal = node
.delegations .delegations
.values() .values()
.map(|d| { .map(|d| node.rewarding_details.determine_delegation_reward(d))
node.rewarding_details
.determine_delegation_reward(d)
.unwrap()
})
.sum(); .sum();
// let reward_sum = simulator.determine_total_delegation_reward(); // let reward_sum = simulator.determine_total_delegation_reward();
@@ -322,7 +269,7 @@ mod tests {
let epoch_params = let epoch_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true); 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()); assert_eq!(rewards.delegates, Decimal::zero());
compare_decimals( compare_decimals(
@@ -335,13 +282,11 @@ mod tests {
#[test] #[test]
fn single_delegation_at_genesis() { fn single_delegation_at_genesis() {
let mut simulator = base_simulator(10000_000000); let mut simulator = base_simulator(10000_000000);
simulator simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
let node_params = let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true); 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( compare_decimals(
rewards.delegates, rewards.delegates,
@@ -352,7 +297,7 @@ mod tests {
compare_decimals( compare_decimals(
rewards.delegates, rewards.delegates,
simulator.determine_total_delegation_reward().unwrap(), simulator.determine_total_delegation_reward(),
None, None,
); );
let node = &simulator.nodes[&0]; let node = &simulator.nodes[&0];
@@ -372,22 +317,20 @@ mod tests {
let node_params = let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true); 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(); let expected_operator1 = "1128452.5416104363".parse().unwrap();
assert_eq!(rewards1.delegates, Decimal::zero()); assert_eq!(rewards1.delegates, Decimal::zero());
compare_decimals(rewards1.operator, expected_operator1, None); compare_decimals(rewards1.operator, expected_operator1, None);
simulator simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
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_operator2 = "1363843.413584609".parse().unwrap();
let expected_delegator_reward1 = "1795952.25874404".parse().unwrap(); let expected_delegator_reward1 = "1795952.25874404".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward1, None); compare_decimals(rewards2.delegates, expected_delegator_reward1, None);
compare_decimals(rewards2.operator, expected_operator2, 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_operator3 = "1364017.7824440491".parse().unwrap();
let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap(); let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap();
compare_decimals(rewards3.delegates, expected_delegator_reward2, None); 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 // add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work) // delegating at different times still work)
simulator simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
.delegate("alice", Coin::new(18000_000000, "unym"), 0) simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
.unwrap();
simulator
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
.unwrap();
// "normal", sanity check rewarding // "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_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap(); let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1, None); compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
@@ -439,15 +378,14 @@ mod tests {
let node = simulator.nodes.get_mut(&0).unwrap(); let node = simulator.nodes.get_mut(&0).unwrap();
let reward = node let reward = node
.rewarding_details .rewarding_details
.withdraw_operator_reward(&original_pledge) .withdraw_operator_reward(&original_pledge);
.unwrap();
assert_eq!(reward.amount, truncate_reward_amount(expected_operator1)); assert_eq!(reward.amount, truncate_reward_amount(expected_operator1));
assert_eq!( assert_eq!(
node.rewarding_details.operator, node.rewarding_details.operator,
Decimal::from_atomics(original_pledge.amount, 0).unwrap() 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_operator2 = "1411113.0004067947".parse().unwrap();
let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap(); let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2, None); 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 // add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work) // delegating at different times still work)
simulator simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
.delegate("alice", Coin::new(18000_000000, "unym"), 0) simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
.unwrap();
simulator
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
.unwrap();
// "normal", sanity check rewarding // "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_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap(); let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1, None); compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
@@ -490,7 +424,7 @@ mod tests {
assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward)); assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward));
// new reward after withdrawal // 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_operator2 = "1411250.1907492676".parse().unwrap();
let expected_delegator_reward2 = "2200004.051009689".parse().unwrap(); let expected_delegator_reward2 = "2200004.051009689".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2, None); compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
@@ -533,30 +467,22 @@ mod tests {
let mut performance = Percent::from_percentage_value(100).unwrap(); let mut performance = Percent::from_percentage_value(100).unwrap();
for epoch in 0..720 { for epoch in 0..720 {
if epoch == 0 { if epoch == 0 {
simulator simulator.delegate("a", Coin::new(18000_000000, "unym"), 0)
.delegate("a", Coin::new(18000_000000, "unym"), 0)
.unwrap()
} }
if epoch == 42 { if epoch == 42 {
simulator simulator.delegate("b", Coin::new(2000_000000, "unym"), 0)
.delegate("b", Coin::new(2000_000000, "unym"), 0)
.unwrap()
} }
if epoch == 89 { if epoch == 89 {
is_active = false; is_active = false;
} }
if epoch == 123 { if epoch == 123 {
simulator simulator.delegate("c", Coin::new(6666_000000, "unym"), 0)
.delegate("c", Coin::new(6666_000000, "unym"), 0)
.unwrap()
} }
if epoch == 167 { if epoch == 167 {
performance = Percent::from_percentage_value(90).unwrap(); performance = Percent::from_percentage_value(90).unwrap();
} }
if epoch == 245 { if epoch == 245 {
simulator simulator.delegate("d", Coin::new(2050_000000, "unym"), 0)
.delegate("d", Coin::new(2050_000000, "unym"), 0)
.unwrap()
} }
if epoch == 264 { if epoch == 264 {
let (delegation, _reward) = simulator.undelegate("b", 0).unwrap(); 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 // TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
} }
if epoch == 545 { if epoch == 545 {
simulator simulator.delegate("e", Coin::new(5000_000000, "unym"), 0)
.delegate("e", Coin::new(5000_000000, "unym"), 0)
.unwrap()
} }
// this has to always hold // this has to always hold
check_rewarding_invariant(&simulator); check_rewarding_invariant(&simulator);
let node_params = NodeRewardParams::new(performance, is_active); 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 // 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 mut simulator = Simulator::new(rewarding_params, interval);
let n0 = simulator let n0 = simulator.bond(
.bond( Coin::new(11_000_000_000000, "unym"),
Coin::new(11_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
profit_margin_percent: Percent::from_percentage_value(10).unwrap(), interval_operating_cost: Coin::new(40_000_000, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0);
.unwrap();
simulator
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0)
.unwrap();
let n1 = simulator let n1 = simulator.bond(
.bond( Coin::new(1_000_000_000000, "unym"),
Coin::new(1_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
profit_margin_percent: Percent::from_percentage_value(10).unwrap(), interval_operating_cost: Coin::new(40_000_000, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1);
.unwrap();
simulator
.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1)
.unwrap();
let n2 = simulator let n2 = simulator.bond(
.bond( Coin::new(1_000_000_000000, "unym"),
Coin::new(1_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
profit_margin_percent: Percent::from_percentage_value(10).unwrap(), interval_operating_cost: Coin::new(40_000_000, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2);
.unwrap();
simulator
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2)
.unwrap();
let n3 = simulator let n3 = simulator.bond(
.bond( Coin::new(1_000_000_000000, "unym"),
Coin::new(1_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
profit_margin_percent: Percent::from_percentage_value(0).unwrap(), interval_operating_cost: Coin::new(500_000_000, "unym"),
interval_operating_cost: Coin::new(500_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3);
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3)
.unwrap();
let n4 = simulator let n4 = simulator.bond(
.bond( Coin::new(1000_000000, "unym"),
Coin::new(1000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
profit_margin_percent: Percent::from_percentage_value(10).unwrap(), interval_operating_cost: Coin::new(40_000_000, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4);
.unwrap();
simulator
.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4)
.unwrap();
let n5 = simulator let n5 = simulator.bond(
.bond( Coin::new(1_000_000_000000, "unym"),
Coin::new(1_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
profit_margin_percent: Percent::from_percentage_value(10).unwrap(), interval_operating_cost: Coin::new(40_000_000, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5);
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5)
.unwrap();
let n6 = simulator let n6 = simulator.bond(
.bond( Coin::new(11_000_000_000000, "unym"),
Coin::new(11_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
profit_margin_percent: Percent::from_percentage_value(10).unwrap(), interval_operating_cost: Coin::new(40_000_000, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6);
.unwrap();
simulator
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6)
.unwrap();
let n7 = simulator let n7 = simulator.bond(
.bond( Coin::new(1_000_000_000000, "unym"),
Coin::new(1_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
profit_margin_percent: Percent::from_percentage_value(10).unwrap(), interval_operating_cost: Coin::new(40_000_000, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7);
.unwrap();
simulator
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7)
.unwrap();
let n8 = simulator let n8 = simulator.bond(
.bond( Coin::new(1_000_000_000000, "unym"),
Coin::new(1_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
profit_margin_percent: Percent::from_percentage_value(0).unwrap(), interval_operating_cost: Coin::new(500_000_000, "unym"),
interval_operating_cost: Coin::new(500_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8);
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8)
.unwrap();
let n9 = simulator let n9 = simulator.bond(
.bond( Coin::new(1_000_000_000000, "unym"),
Coin::new(1_000_000_000000, "unym"), MixNodeCostParams {
MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
profit_margin_percent: Percent::from_percentage_value(10).unwrap(), interval_operating_cost: Coin::new(40_000_000, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"), },
}, );
) simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9);
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9)
.unwrap();
let uptime_1 = Percent::from_percentage_value(100).unwrap(); let uptime_1 = Percent::from_percentage_value(100).unwrap();
let uptime_09 = Percent::from_percentage_value(90).unwrap(); let uptime_09 = Percent::from_percentage_value(90).unwrap();
@@ -789,7 +673,7 @@ mod tests {
.collect::<BTreeMap<_, _>>(); .collect::<BTreeMap<_, _>>();
for _ in 0..23 { 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, // we allow the delta to be within 0.1unym,
@@ -20,25 +20,21 @@ impl SimulatedNode {
cost_params: MixNodeCostParams, cost_params: MixNodeCostParams,
initial_pledge: &Coin, initial_pledge: &Coin,
current_epoch: EpochId, current_epoch: EpochId,
) -> Result<Self, MixnetContractError> { ) -> Self {
Ok(SimulatedNode { SimulatedNode {
mix_id, mix_id,
rewarding_details: MixNodeRewarding::initialise_new( rewarding_details: MixNodeRewarding::initialise_new(
cost_params, cost_params,
initial_pledge, initial_pledge,
current_epoch, current_epoch,
)?, ),
delegations: HashMap::new(), delegations: HashMap::new(),
}) }
} }
pub fn delegate<S: Into<String>>( pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin) {
&mut self,
delegator: S,
delegation: Coin,
) -> Result<(), MixnetContractError> {
self.rewarding_details self.rewarding_details
.add_base_delegation(delegation.amount)?; .add_base_delegation(delegation.amount);
let delegator = delegator.into(); let delegator = delegator.into();
let delegation = Delegation::new( let delegation = Delegation::new(
@@ -51,7 +47,6 @@ impl SimulatedNode {
); );
self.delegations.insert(delegator, delegation); self.delegations.insert(delegator, delegation);
Ok(())
} }
pub fn undelegate<S: Into<String>>( pub fn undelegate<S: Into<String>>(
@@ -59,19 +54,16 @@ impl SimulatedNode {
delegator: S, delegator: S,
) -> Result<(Coin, Coin), MixnetContractError> { ) -> Result<(Coin, Coin), MixnetContractError> {
let delegator = delegator.into(); let delegator = delegator.into();
let delegation = self.delegations.remove(&delegator).ok_or( let delegation = self
MixnetContractError::NoMixnodeDelegationFound { .delegations
mix_id: MixId::MAX, .remove(&delegator)
address: delegator, .expect("delegation not found");
proxy: None,
},
)?;
let reward = self let reward = self
.rewarding_details .rewarding_details
.determine_delegation_reward(&delegation)?; .determine_delegation_reward(&delegation);
self.rewarding_details self.rewarding_details
.remove_delegation_decimal(delegation.dec_amount()? + reward)?; .remove_delegation_decimal(delegation.dec_amount() + reward)?;
let reward_denom = &delegation.amount.denom; let reward_denom = &delegation.amount.denom;
let truncated_reward = truncate_reward(reward, reward_denom); let truncated_reward = truncate_reward(reward, reward_denom);
@@ -35,10 +35,6 @@ impl LayerDistribution {
(Layer::Two, self.layer2), (Layer::Two, self.layer2),
(Layer::Three, self.layer3), (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 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" } contracts-common = { path = "../contracts-common" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
schemars = "0.8" schemars = "0.8"
log = "0.4"
ts-rs = {version = "6.1.2", optional = true} ts-rs = {version = "6.1.2", optional = true}
[features] [features]
@@ -1,16 +1,12 @@
use std::str::FromStr;
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use contracts_common::Percent; use contracts_common::Percent;
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128}; use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
use log::warn;
use mixnet_contract_common::MixId; use mixnet_contract_common::MixId;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg}; pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
@@ -62,23 +58,11 @@ impl FromStr for PledgeCap {
fn from_str(cap: &str) -> Result<Self, Self::Err> { fn from_str(cap: &str) -> Result<Self, Self::Err> {
let cap = cap.replace('_', "").replace(',', "."); let cap = cap.replace('_', "").replace(',', ".");
match Percent::from_str(&cap) { match Percent::from_str(&cap) {
Ok(p) => { Ok(p) => Ok(PledgeCap::Percent(p)),
if p.is_zero() { Err(_) => match cap.parse::<u128>() {
warn!("Pledge cap set to 0%, are you sure this is right?") Ok(i) => Ok(PledgeCap::Absolute(Uint128::from(i))),
} Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
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)),
}
}
} }
} }
} }
+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 const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g"; "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
+3 -3
View File
@@ -85,7 +85,7 @@ impl SecretKey {
// x || ys.len() || ys // x || ys.len() || ys
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len(); 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(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_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; let mut beta_g1_end: u64 = 0;
for i in 0..betas_len { for i in 0..betas_len {
let start = (104 + i * 48) as usize; 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_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective( let beta_i = try_deserialize_g1_projective(
&beta_i_bytes, &beta_i_bytes,
@@ -178,7 +178,7 @@ impl TryFrom<&[u8]> for VerificationKey {
let mut beta_g2 = Vec::with_capacity(betas_len as usize); let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len { for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize; 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_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective( let beta_i = try_deserialize_g2_projective(
&beta_i_bytes, &beta_i_bytes,
+2 -2
View File
@@ -94,10 +94,10 @@ fn prepare_unlinked_fragmented_set(
for i in 1..(pre_casted_frags + 1) { 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 // 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( let ub = usize::min(
message.len(), message.len(),
i * unlinked_fragment_payload_max_len(max_plaintext_size), i as usize * unlinked_fragment_payload_max_len(max_plaintext_size),
); );
fragments.push( fragments.push(
Fragment::try_new( 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 ### Added
- vesting-contract: added query for obtaining contract build information ([#1726]) - 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 [#1726]: https://github.com/nymtech/nym/pull/1726
@@ -11,12 +37,10 @@
### Added ### Added
- vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569]) - 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 ### Changed
- mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571]) - 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 ### Fixed
@@ -26,9 +50,8 @@
[#1544]: https://github.com/nymtech/nym/pull/1544 [#1544]: https://github.com/nymtech/nym/pull/1544
[#1569]: https://github.com/nymtech/nym/pull/1569 [#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 [#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) ## [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]] [[package]]
name = "mixnet-contract" name = "mixnet-contract"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"bs58", "bs58",
"cosmwasm-schema", "cosmwasm-schema",
@@ -1625,7 +1625,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"contracts-common", "contracts-common",
"cosmwasm-std", "cosmwasm-std",
"log",
"mixnet-contract-common", "mixnet-contract-common",
"schemars", "schemars",
"serde", "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] [package]
name = "mixnet-contract" name = "mixnet-contract"
version = "1.0.2" version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021" edition = "2021"
+1 -1
View File
@@ -63,7 +63,7 @@ pub fn instantiate(
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env); Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
let reward_params = msg let reward_params = msg
.initial_rewarding_params .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)?; interval_storage::initialise_storage(deps.storage, starting_interval)?;
mixnet_params_storage::initialise_storage(deps.storage, state)?; 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) // 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( let cosmos_event = new_delegation_event(
created_at, created_at,
-3
View File
@@ -1,9 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
mod constants; mod constants;
pub mod contract; pub mod contract;
mod delegations; 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 mix_id = next_mixnode_id_counter(storage)?;
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id(); 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( let mixnode_bond = MixNodeBond::new(
mix_id, mix_id,
owner, owner,
+12 -18
View File
@@ -4,9 +4,8 @@
use super::storage; use super::storage;
use crate::delegations::storage as delegations_storage; use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_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::error::MixnetContractError;
use mixnet_contract_common::helpers::IntoBaseDecimal;
use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding}; use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding};
use mixnet_contract_common::Delegation; use mixnet_contract_common::Delegation;
@@ -23,10 +22,11 @@ pub(crate) fn apply_reward_pool_changes(
+ pending_pool_change.added; + pending_pool_change.added;
let staking_supply = rewarding_params.interval.staking_supply let staking_supply = rewarding_params.interval.staking_supply
+ rewarding_params.interval.staking_supply_scale_factor * pending_pool_change.removed; + 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; * rewarding_params.interval.interval_pool_emission;
let stake_saturation_point = 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.reward_pool = reward_pool;
rewarding_params.interval.staking_supply = staking_supply; 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 mix_id = mix_details.mix_id();
let mut mix_rewarding = mix_details.rewarding_details; let mut mix_rewarding = mix_details.rewarding_details;
let original_pledge = mix_details.bond_information.original_pledge; 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 // save updated rewarding info
storage::MIXNODE_REWARDING.save(store, mix_id, &mix_rewarding)?; storage::MIXNODE_REWARDING.save(store, mix_id, &mix_rewarding)?;
@@ -87,7 +87,7 @@ mod tests {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
let epochs_in_interval = test.current_interval().epochs_in_interval(); 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(); let start_rewarding_params = test.rewarding_params();
// nothing changes if pending changes are empty // nothing changes if pending changes are empty
@@ -95,7 +95,7 @@ mod tests {
assert_eq!(start_rewarding_params, test.rewarding_params()); assert_eq!(start_rewarding_params, test.rewarding_params());
// normal case of having distributed some rewards // 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 storage::PENDING_REWARD_POOL_CHANGE
.save( .save(
test.deps_mut().storage, test.deps_mut().storage,
@@ -132,10 +132,7 @@ mod tests {
assert_eq!( assert_eq!(
updated_rewarding_params.interval.stake_saturation_point, updated_rewarding_params.interval.stake_saturation_point,
updated_rewarding_params.interval.staking_supply updated_rewarding_params.interval.staking_supply
/ updated_rewarding_params / Decimal::from_atomics(updated_rewarding_params.rewarded_set_size, 0).unwrap()
.rewarded_set_size
.into_base_decimal()
.unwrap()
); );
// resets changes back to 0 // resets changes back to 0
@@ -147,7 +144,7 @@ mod tests {
); );
// future case of having to also increase the reward pool // 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 storage::PENDING_REWARD_POOL_CHANGE
.save( .save(
test.deps_mut().storage, test.deps_mut().storage,
@@ -184,10 +181,7 @@ mod tests {
assert_eq!( assert_eq!(
updated_rewarding_params2.interval.stake_saturation_point, updated_rewarding_params2.interval.stake_saturation_point,
updated_rewarding_params2.interval.staking_supply updated_rewarding_params2.interval.staking_supply
/ updated_rewarding_params2 / Decimal::from_atomics(updated_rewarding_params2.rewarded_set_size, 0).unwrap()
.rewarded_set_size
.into_base_decimal()
.unwrap()
); );
// resets changes back to 0 // resets changes back to 0
@@ -204,7 +198,7 @@ mod tests {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
let pledge = Uint128::new(250_000_000); 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)); let mix_id = test.add_dummy_mixnode("mix-owner", Some(pledge));
// no rewards // no rewards
@@ -241,7 +235,7 @@ mod tests {
let mut test = TestSetup::new(); let mut test = TestSetup::new();
let delegation_amount = Uint128::new(2500_000_000); 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 mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegator = "delegator"; let delegator = "delegator";
test.add_immediate_delegation(delegator, delegation_amount, mix_id); test.add_immediate_delegation(delegator, delegation_amount, mix_id);
+11 -17
View File
@@ -7,7 +7,6 @@ use crate::interval::storage as interval_storage;
use crate::mixnodes; use crate::mixnodes;
use crate::mixnodes::storage as mixnodes_storage; use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{coin, Coin, Decimal, Deps, StdResult}; 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::mixnode::MixNodeDetails;
use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams}; use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams};
use mixnet_contract_common::rewarding::helpers::truncate_reward; 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) storage::REWARDING_PARAMS.load(deps.storage)
} }
fn pending_operator_reward( fn pending_operator_reward(mix_details: Option<MixNodeDetails>) -> PendingRewardResponse {
mix_details: Option<MixNodeDetails>, match mix_details {
) -> StdResult<PendingRewardResponse> {
Ok(match mix_details {
Some(mix_details) => PendingRewardResponse { Some(mix_details) => PendingRewardResponse {
amount_staked: Some(mix_details.original_pledge().clone()), amount_staked: Some(mix_details.original_pledge().clone()),
amount_earned: Some(mix_details.pending_operator_reward()), 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(), mixnode_still_fully_bonded: !mix_details.is_unbonding(),
}, },
None => PendingRewardResponse::default(), None => PendingRewardResponse::default(),
}) }
} }
pub fn query_pending_operator_reward( 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 // in order to determine operator's reward we need to know its original pledge and thus
// we have to load the entire thing // we have to load the entire thing
let mix_details = mixnodes::helpers::get_mixnode_details_by_owner(deps.storage, owner_address)?; 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( 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 // in order to determine operator's reward we need to know its original pledge and thus
// we have to load the entire thing // we have to load the entire thing
let mix_details = mixnodes::helpers::get_mixnode_details_by_id(deps.storage, mix_id)?; 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( pub fn query_pending_delegator_reward(
@@ -77,8 +74,8 @@ pub fn query_pending_delegator_reward(
None => return Ok(PendingRewardResponse::default()), None => return Ok(PendingRewardResponse::default()),
}; };
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation)?; let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation);
let delegator_reward = mix_rewarding.pending_delegator_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) // 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); 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()), None => return Ok(EstimatedCurrentEpochRewardResponse::empty_response()),
}; };
let staked_dec = into_base_decimal(delegation.amount.amount)?; let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation)?; let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
let amount_staked = delegation.amount; let amount_staked = delegation.amount;
// check if the mixnode isnt in the process of unbonding (or has already unbonded) // 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 delegation = test.delegation(mix_id, owner, &None);
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap(); let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
let current_value = staked_dec let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
+ mix_rewarding
.determine_delegation_reward(&delegation)
.unwrap();
let amount_staked = delegation.amount; let amount_staked = delegation.amount;
EstimatedCurrentEpochRewardResponse { EstimatedCurrentEpochRewardResponse {
+22 -30
View File
@@ -744,7 +744,7 @@ pub mod tests {
performance, performance,
in_active_set: true, 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); assert_eq!(sim_res, dist);
} }
test.skip_to_next_epoch_end(); test.skip_to_next_epoch_end();
@@ -768,7 +768,7 @@ pub mod tests {
performance, performance,
in_active_set: true, 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); assert_eq!(sim_res, dist);
} }
test.skip_to_next_epoch_end(); test.skip_to_next_epoch_end();
@@ -809,8 +809,8 @@ pub mod tests {
in_active_set: true, in_active_set: true,
}; };
let dist1 = sim1.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).unwrap(); let dist2 = sim2.simulate_epoch_single_node(node_params);
let env = test.env(); let env = test.env();
@@ -858,17 +858,15 @@ pub mod tests {
let unit_delegation_base = actual_prior1.unit_delegation; let unit_delegation_base = actual_prior1.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed // recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del11 = del11.dec_amount().unwrap() let pre_rewarding_del11 = del11.dec_amount()
+ (prior_unit_reward - del11.cumulative_reward_ratio) + (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
* del11.dec_amount().unwrap()
/ (del11.cumulative_reward_ratio + unit_delegation_base); / (del11.cumulative_reward_ratio + unit_delegation_base);
let computed_del11_reward = let computed_del11_reward =
pre_rewarding_del11 / prior_delegates1 * delegates_reward1; pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del12 = del12.dec_amount().unwrap() let pre_rewarding_del12 = del12.dec_amount()
+ (prior_unit_reward - del12.cumulative_reward_ratio) + (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
* del12.dec_amount().unwrap()
/ (del12.cumulative_reward_ratio + unit_delegation_base); / (del12.cumulative_reward_ratio + unit_delegation_base);
let computed_del12_reward = let computed_del12_reward =
@@ -922,9 +920,8 @@ pub mod tests {
let unit_delegation_base = actual_prior2.unit_delegation; let unit_delegation_base = actual_prior2.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed // recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del21 = del21.dec_amount().unwrap() let pre_rewarding_del21 = del21.dec_amount()
+ (prior_unit_reward - del21.cumulative_reward_ratio) + (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
* del21.dec_amount().unwrap()
/ (del21.cumulative_reward_ratio + unit_delegation_base); / (del21.cumulative_reward_ratio + unit_delegation_base);
let computed_del21_reward = let computed_del21_reward =
@@ -952,8 +949,8 @@ pub mod tests {
in_active_set: true, in_active_set: true,
}; };
let dist1 = sim1.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).unwrap(); let dist2 = sim2.simulate_epoch_single_node(node_params);
let env = test.env(); let env = test.env();
@@ -1001,25 +998,22 @@ pub mod tests {
let unit_delegation_base = actual_prior1.unit_delegation; let unit_delegation_base = actual_prior1.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed // recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del11 = del11.dec_amount().unwrap() let pre_rewarding_del11 = del11.dec_amount()
+ (prior_unit_reward - del11.cumulative_reward_ratio) + (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
* del11.dec_amount().unwrap()
/ (del11.cumulative_reward_ratio + unit_delegation_base); / (del11.cumulative_reward_ratio + unit_delegation_base);
let computed_del11_reward = let computed_del11_reward =
pre_rewarding_del11 / prior_delegates1 * delegates_reward1; pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del12 = del12.dec_amount().unwrap() let pre_rewarding_del12 = del12.dec_amount()
+ (prior_unit_reward - del12.cumulative_reward_ratio) + (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
* del12.dec_amount().unwrap()
/ (del12.cumulative_reward_ratio + unit_delegation_base); / (del12.cumulative_reward_ratio + unit_delegation_base);
let computed_del12_reward = let computed_del12_reward =
pre_rewarding_del12 / prior_delegates1 * delegates_reward1; pre_rewarding_del12 / prior_delegates1 * delegates_reward1;
let pre_rewarding_del13 = del13.dec_amount().unwrap() let pre_rewarding_del13 = del13.dec_amount()
+ (prior_unit_reward - del13.cumulative_reward_ratio) + (prior_unit_reward - del13.cumulative_reward_ratio) * del13.dec_amount()
* del13.dec_amount().unwrap()
/ (del13.cumulative_reward_ratio + unit_delegation_base); / (del13.cumulative_reward_ratio + unit_delegation_base);
let computed_del13_reward = let computed_del13_reward =
@@ -1073,17 +1067,15 @@ pub mod tests {
let unit_delegation_base = actual_prior2.unit_delegation; let unit_delegation_base = actual_prior2.unit_delegation;
// recompute the state of fully compounded delegation from before this rewarding was distributed // recompute the state of fully compounded delegation from before this rewarding was distributed
let pre_rewarding_del21 = del21.dec_amount().unwrap() let pre_rewarding_del21 = del21.dec_amount()
+ (prior_unit_reward - del21.cumulative_reward_ratio) + (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
* del21.dec_amount().unwrap()
/ (del21.cumulative_reward_ratio + unit_delegation_base); / (del21.cumulative_reward_ratio + unit_delegation_base);
let computed_del21_reward = let computed_del21_reward =
pre_rewarding_del21 / prior_delegates2 * delegates_reward2; pre_rewarding_del21 / prior_delegates2 * delegates_reward2;
let pre_rewarding_del23 = del23.dec_amount().unwrap() let pre_rewarding_del23 = del23.dec_amount()
+ (prior_unit_reward - del23.cumulative_reward_ratio) + (prior_unit_reward - del23.cumulative_reward_ratio) * del23.dec_amount()
* del23.dec_amount().unwrap()
/ (del23.cumulative_reward_ratio + unit_delegation_base); / (del23.cumulative_reward_ratio + unit_delegation_base);
let computed_del23_reward = 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()) Ok(pledge.pop().unwrap())
} }
@@ -110,10 +106,6 @@ pub(crate) fn validate_delegation_stake(
return Err(MixnetContractError::EmptyDelegation); 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()) Ok(delegation.pop().unwrap())
} }
+5 -5
View File
@@ -1,8 +1,8 @@
use crate::errors::ContractError; use crate::errors::ContractError;
use crate::queued_migrations::migrate_to_v2_mixnet_contract; use crate::queued_migrations::migrate_to_v2_mixnet_contract;
use crate::storage::{ use crate::storage::{
account_from_address, BlockTimestampSecs, ADMIN, DELEGATIONS, MIXNET_CONTRACT_ADDRESS, account_from_address, save_account, BlockTimestampSecs, ADMIN, DELEGATIONS,
MIX_DENOM, MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
}; };
use crate::traits::{ use crate::traits::{
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount, 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)?; let mut account = account_from_address(&address, deps.storage, deps.api)?;
account.pledge_cap = Some(cap); account.pledge_cap = Some(cap);
// update_locked_pledge_cap(amount, deps.storage)?; save_account(&account, deps.storage)?;
Ok(Response::default()) Ok(Response::default())
} }
@@ -589,7 +589,7 @@ pub fn try_get_current_vesting_period(
env: Env, env: Env,
) -> Result<Period, ContractError> { ) -> Result<Period, ContractError> {
let account = account_from_address(address, deps.storage, deps.api)?; 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. /// Loads mixnode bond from vesting contract storage.
@@ -691,7 +691,7 @@ pub fn try_get_original_vesting(
deps: Deps<'_>, deps: Deps<'_>,
) -> Result<OriginalVestingResponse, ContractError> { ) -> Result<OriginalVestingResponse, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; 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] /// See [crate::traits::VestingAccount::get_delegated_free]
-2
View File
@@ -50,6 +50,4 @@ pub enum ContractError {
MinVestingFunds { sent: u128, need: u128 }, MinVestingFunds { sent: u128, need: u128 },
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())] #[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
LockedPledgeCapReached { current: Uint128, cap: Uint128 }, 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)] #![allow(rustdoc::private_intra_doc_links)]
//! Nym vesting contract, providing vesting accounts with ability to stake unvested tokens //! Nym vesting contract, providing vesting accounts with ability to stake unvested tokens
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
pub mod contract; pub mod contract;
mod errors; mod errors;
mod queued_migrations; mod queued_migrations;
@@ -55,7 +55,7 @@ pub trait VestingAccount {
/// Returns amount of coins set at account creation /// Returns amount of coins set at account creation
/// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl /// 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 /// See [/vesting-contract/struct.Account.html/method.get_delegated_free] for impl
fn get_delegated_free( fn get_delegated_free(
+9 -31
View File
@@ -69,7 +69,7 @@ impl Account {
pub fn absolute_pledge_cap(&self) -> Result<Uint128, ContractError> { pub fn absolute_pledge_cap(&self) -> Result<Uint128, ContractError> {
match self.pledge_cap() { match self.pledge_cap() {
PledgeCap::Absolute(cap) => Ok(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() self.periods.len()
} }
pub fn period_duration(&self) -> Result<u64, ContractError> { pub fn period_duration(&self) -> u64 {
self.periods self.periods.get(0).unwrap().period_seconds
.get(0)
.ok_or(ContractError::UnpopulatedVestingPeriods {
owner: self.owner_address.clone(),
})
.map(|p| p.period_seconds)
} }
pub fn storage_key(&self) -> u32 { 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. /// 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. /// In case vesting is over it will always return NUM_VESTING_PERIODS.
pub fn get_current_vesting_period( pub fn get_current_vesting_period(&self, block_time: Timestamp) -> Period {
&self, if block_time.seconds() < self.periods.first().unwrap().start_time {
block_time: Timestamp, Period::Before
) -> Result<Period, ContractError> { } else if self.periods.last().unwrap().end_time() < block_time {
let first_period = Period::After
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)
} else { } else {
let mut index = 0; let mut index = 0;
for period in &self.periods { for period in &self.periods {
@@ -154,7 +132,7 @@ impl Account {
} }
index += 1; index += 1;
} }
Ok(Period::In(index)) Period::In(index)
} }
} }
@@ -67,7 +67,7 @@ impl VestingAccount for Account {
storage: &dyn Storage, storage: &dyn Storage,
) -> Result<Coin, ContractError> { ) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time); 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 denom = MIX_DENOM.load(storage)?;
let amount = match period { let amount = match period {
@@ -94,7 +94,7 @@ impl VestingAccount for Account {
storage: &dyn Storage, storage: &dyn Storage,
) -> Result<Coin, ContractError> { ) -> Result<Coin, ContractError> {
Ok(Coin { 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, - self.get_vested_coins(block_time, env, storage)?.amount,
denom: MIX_DENOM.load(storage)?, denom: MIX_DENOM.load(storage)?,
}) })
@@ -108,12 +108,12 @@ impl VestingAccount for Account {
self.periods[(self.num_vesting_periods() - 1)].end_time() self.periods[(self.num_vesting_periods() - 1)].end_time()
} }
fn get_original_vesting(&self) -> Result<OriginalVestingResponse, ContractError> { fn get_original_vesting(&self) -> OriginalVestingResponse {
Ok(OriginalVestingResponse::new( OriginalVestingResponse::new(
self.coin.clone(), self.coin.clone(),
self.num_vesting_periods(), self.num_vesting_periods(),
self.period_duration()?, self.period_duration(),
)) )
} }
fn get_delegated_free( fn get_delegated_free(
@@ -123,12 +123,13 @@ impl VestingAccount for Account {
storage: &dyn Storage, storage: &dyn Storage,
) -> Result<Coin, ContractError> { ) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time); 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 withdrawn = self.load_withdrawn(storage)?;
let max_available = self let max_available = self
.get_vested_coins(Some(block_time), env, storage)? .get_vested_coins(Some(block_time), env, storage)?
.amount .amount
.saturating_sub(withdrawn); .saturating_sub(withdrawn);
let period = self.get_current_vesting_period(block_time);
let start_time = match period { let start_time = match period {
Period::Before => 0, Period::Before => 0,
Period::After => u64::MAX, Period::After => u64::MAX,
@@ -173,7 +174,7 @@ impl VestingAccount for Account {
storage: &dyn Storage, storage: &dyn Storage,
) -> Result<Coin, ContractError> { ) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time); 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 max_vested = self.get_vested_coins(Some(block_time), env, storage)?;
let start_time = match period { let start_time = match period {
Period::Before => 0, Period::Before => 0,
+11 -51
View File
@@ -183,14 +183,12 @@ mod tests {
assert_eq!(account.periods().len(), num_vesting_periods as usize); assert_eq!(account.periods().len(), num_vesting_periods as usize);
let current_period = account let current_period = account.get_current_vesting_period(Timestamp::from_seconds(0));
.get_current_vesting_period(Timestamp::from_seconds(0))
.unwrap();
assert_eq!(Period::Before, current_period); assert_eq!(Period::Before, current_period);
let block_time = let block_time =
Timestamp::from_seconds(account.start_time().seconds() + vesting_period + 1); 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)); assert_eq!(current_period, Period::In(1));
let vested_coins = account let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage) .get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -201,37 +199,21 @@ mod tests {
assert_eq!( assert_eq!(
vested_coins.amount, vested_coins.amount,
Uint128::new( Uint128::new(
account account.get_original_vesting().amount().amount.u128() / num_vesting_periods as u128
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128
) )
); );
assert_eq!( assert_eq!(
vesting_coins.amount, vesting_coins.amount,
Uint128::new( Uint128::new(
account account.get_original_vesting().amount().amount.u128()
.get_original_vesting() - account.get_original_vesting().amount().amount.u128()
.unwrap()
.amount()
.amount
.u128()
- account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128 / num_vesting_periods as u128
) )
); );
let block_time = let block_time =
Timestamp::from_seconds(account.start_time().seconds() + 5 * vesting_period + 1); 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)); assert_eq!(current_period, Period::In(5));
let vested_coins = account let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage) .get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -242,30 +224,15 @@ mod tests {
assert_eq!( assert_eq!(
vested_coins.amount, vested_coins.amount,
Uint128::new( Uint128::new(
5 * account 5 * account.get_original_vesting().amount().amount.u128()
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128 / num_vesting_periods as u128
) )
); );
assert_eq!( assert_eq!(
vesting_coins.amount, vesting_coins.amount,
Uint128::new( Uint128::new(
account account.get_original_vesting().amount().amount.u128()
.get_original_vesting() - 5 * account.get_original_vesting().amount().amount.u128()
.unwrap()
.amount()
.amount
.u128()
- 5 * account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
/ num_vesting_periods as u128 / num_vesting_periods as u128
) )
); );
@@ -273,7 +240,7 @@ mod tests {
let block_time = Timestamp::from_seconds( let block_time = Timestamp::from_seconds(
account.start_time().seconds() + vesting_over_period * vesting_period + 1, 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); assert_eq!(current_period, Period::After);
let vested_coins = account let vested_coins = account
.get_vested_coins(Some(block_time), &env, &deps.storage) .get_vested_coins(Some(block_time), &env, &deps.storage)
@@ -283,14 +250,7 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
vested_coins.amount, vested_coins.amount,
Uint128::new( Uint128::new(account.get_original_vesting().amount().amount.u128())
account
.get_original_vesting()
.unwrap()
.amount()
.amount
.u128()
)
); );
assert_eq!(vesting_coins.amount, Uint128::zero()); 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] [package]
name = "explorer-api" name = "explorer-api"
version = "1.0.1" version = "1.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # 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 { Some(EconomicDynamicsStats {
stake_saturation: best_effort_small_dec_to_f64(stake_saturation.saturation) as f32, 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, active_set_inclusion_probability: inclusion_probability.in_active,
reserve_set_inclusion_probability: inclusion_probability.in_reserve, reserve_set_inclusion_probability: inclusion_probability.in_reserve,
// drop precision for compatibility sake // drop precision for compatibility sake
+2
View File
@@ -34,6 +34,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
pub layer: Layer, pub layer: Layer,
pub mix_node: MixNode, pub mix_node: MixNode,
pub stake_saturation: f32, pub stake_saturation: f32,
pub uncapped_saturation: f32,
pub avg_uptime: u8, pub avg_uptime: u8,
pub estimated_operator_apy: f64, pub estimated_operator_apy: f64,
pub estimated_delegators_apy: f64, pub estimated_delegators_apy: f64,
@@ -153,6 +154,7 @@ pub(crate) struct NodeStats {
#[derive(Clone, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Serialize, Deserialize, JsonSchema)]
pub(crate) struct EconomicDynamicsStats { pub(crate) struct EconomicDynamicsStats {
pub(crate) stake_saturation: f32, pub(crate) stake_saturation: f32,
pub(crate) uncapped_saturation: f32,
pub(crate) active_set_inclusion_probability: SelectionChance, pub(crate) active_set_inclusion_probability: SelectionChance,
pub(crate) reserve_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(), mix_node: node.mixnode_details.bond_information.mix_node.clone(),
avg_uptime: node.performance.round_to_integer(), avg_uptime: node.performance.round_to_integer(),
stake_saturation: best_effort_small_dec_to_f64(node.stake_saturation) as f32, 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_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), estimated_delegators_apy: best_effort_small_dec_to_f64(node.estimated_delegators_apy),
operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(), operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(),
@@ -6,14 +6,16 @@ export const EconomicsInfoColumns: ColumnsType[] = [
title: 'Estimated Total Reward', title: 'Estimated Total Reward',
flex: 1, flex: 1,
headerAlign: 'left', 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', field: 'estimatedOperatorReward',
title: 'Estimated Operator Reward', title: 'Estimated Operator Reward',
flex: 1, flex: 1,
headerAlign: 'left', 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', field: 'selectionChance',
@@ -29,7 +31,7 @@ export const EconomicsInfoColumns: ColumnsType[] = [
flex: 1, flex: 1,
headerAlign: 'left', headerAlign: 'left',
tooltipInfo: 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', field: 'profitMargin',
@@ -37,7 +39,7 @@ export const EconomicsInfoColumns: ColumnsType[] = [
flex: 1, flex: 1,
headerAlign: 'left', headerAlign: 'left',
tooltipInfo: 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', field: 'operatingCost',
@@ -4,23 +4,8 @@ import { ApiState, MixNodeEconomicDynamicsStatsResponse } from '../../../typeDef
import { EconomicsInfoRowWithIndex } from './types'; import { EconomicsInfoRowWithIndex } from './types';
import { toPercentIntegerString } from '../../../utils'; import { toPercentIntegerString } from '../../../utils';
const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) => { const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) =>
const inclusionProbability = economicDynamicsStats?.data?.active_set_inclusion_probability; 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 '-';
}
};
export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => { export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
const { economicDynamicsStats, mixNode } = useMixnodeContext(); const { economicDynamicsStats, mixNode } = useMixnodeContext();
@@ -29,7 +14,7 @@ export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
currencyToString((economicDynamicsStats?.data?.estimated_total_node_reward || '').toString()) || '-'; currencyToString((economicDynamicsStats?.data?.estimated_total_node_reward || '').toString()) || '-';
const estimatedOperatorRewards = const estimatedOperatorRewards =
currencyToString((economicDynamicsStats?.data?.estimated_operator_reward || '').toString()) || '-'; 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 const profitMargin = mixNode?.data?.profit_margin_percent
? toPercentIntegerString(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 totalBond = pledge + delegations;
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2); const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
const profitPercentage = toPercentIntegerString(item.profit_margin_percent) || 0; 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 { return {
mix_id: item.mix_id, mix_id: item.mix_id,
@@ -47,7 +47,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
layer: item?.layer || '', layer: item?.layer || '',
profit_percentage: `${profitPercentage}%`, profit_percentage: `${profitPercentage}%`,
avg_uptime: `${item.avg_uptime}%` || '-', avg_uptime: `${item.avg_uptime}%` || '-',
stake_saturation: stakeSaturation, stake_saturation: uncappedSaturation,
operating_cost: `${unymToNym(item.operating_cost.amount, 6)} NYM`, operating_cost: `${unymToNym(item.operating_cost?.amount, 6)} NYM`,
}; };
} }
+25 -8
View File
@@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { Link as RRDLink } from 'react-router-dom'; 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 { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { SelectChangeEvent } from '@mui/material/Select'; import { SelectChangeEvent } from '@mui/material/Select';
import { useMainContext } from '../../context/main'; import { useMainContext } from '../../context/main';
@@ -12,6 +12,8 @@ import { Title } from '../../components/Title';
import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid'; import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid';
import { currencyToString } from '../../utils/currency'; import { currencyToString } from '../../utils/currency';
import { Tooltip } from '../../components/Tooltip'; import { Tooltip } from '../../components/Tooltip';
import { BIG_DIPPER } from '../../api/constants';
import { splice } from '../../utils';
export const PageGateways: React.FC = () => { export const PageGateways: React.FC = () => {
const { gateways } = useMainContext(); const { gateways } = useMainContext();
@@ -69,9 +71,14 @@ export const PageGateways: React.FC = () => {
headerClassName: 'MuiDataGrid-header-override', headerClassName: 'MuiDataGrid-header-override',
headerAlign: 'left', headerAlign: 'left',
renderCell: (params: GridRenderCellParams) => ( 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)} {currencyToString(params.value)}
</Typography> </MuiLink>
), ),
}, },
{ {
@@ -81,9 +88,14 @@ export const PageGateways: React.FC = () => {
headerAlign: 'left', headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override', headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => ( 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} {params.value}
</Typography> </MuiLink>
), ),
}, },
{ {
@@ -120,9 +132,14 @@ export const PageGateways: React.FC = () => {
headerAlign: 'left', headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override', headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => ( renderCell: (params: GridRenderCellParams) => (
<Typography sx={cellStyles} data-testid="owner"> <MuiLink
{params.value} sx={{ ...cellStyles }}
</Typography> 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', field: 'self_percentage',
title: 'Self %', title: 'Bond %',
headerAlign: 'left', headerAlign: 'left',
width: 99, width: 99,
}, },
+4 -4
View File
@@ -149,7 +149,7 @@ export const PageMixnodes: React.FC = () => {
renderHeader: () => ( renderHeader: () => (
<CustomColumnHeading <CustomColumnHeading
headingTitle="Stake Saturation" 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', headerClassName: 'MuiDataGrid-header-override',
@@ -192,7 +192,7 @@ export const PageMixnodes: React.FC = () => {
renderHeader: () => ( renderHeader: () => (
<CustomColumnHeading <CustomColumnHeading
headingTitle="Profit Margin" 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', headerClassName: 'MuiDataGrid-header-override',
@@ -210,10 +210,10 @@ export const PageMixnodes: React.FC = () => {
}, },
{ {
field: 'operating_cost', field: 'operating_cost',
headerName: 'Operating cost', headerName: 'Operating Cost',
renderHeader: () => ( renderHeader: () => (
<CustomColumnHeading <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." 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; mix_node: MixNode;
avg_uptime: number; avg_uptime: number;
stake_saturation: number; stake_saturation: number;
uncapped_saturation: number;
operating_cost: Amount; operating_cost: Amount;
profit_margin_percent: string; profit_margin_percent: string;
} }
@@ -214,8 +215,9 @@ export type UptimeStoryResponse = {
export type MixNodeEconomicDynamicsStatsResponse = { export type MixNodeEconomicDynamicsStatsResponse = {
stake_saturation: number; stake_saturation: number;
uncapped_saturation: number;
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow // 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'; reserve_set_inclusion_probability: 'High' | 'Good' | 'Low';
estimated_total_node_reward: number; estimated_total_node_reward: number;
estimated_operator_reward: number; estimated_operator_reward: number;
+1 -1
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-gateway" name = "nym-gateway"
version = "1.0.2" version = "1.1.0"
authors = [ authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>", "Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.0.2" version = "1.1.0"
authors = [ authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>", "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) ## [nym-connect-v1.0.2](https://github.com/nymtech/nym/tree/nym-connect-v1.0.2) (2022-08-18)
### Changed ### Changed
+2 -4
View File
@@ -3214,7 +3214,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-connect" name = "nym-connect"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"bip39", "bip39",
"client-core", "client-core",
@@ -3247,7 +3247,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"client-core", "client-core",
@@ -3273,7 +3273,6 @@ dependencies = [
"snafu", "snafu",
"socks5-requests", "socks5-requests",
"task", "task",
"thiserror",
"tokio", "tokio",
"topology", "topology",
"url", "url",
@@ -6189,7 +6188,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"contracts-common", "contracts-common",
"cosmwasm-std", "cosmwasm-std",
"log",
"mixnet-contract-common", "mixnet-contract-common",
"schemars", "schemars",
"serde", "serde",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-connect" name = "nym-connect"
version = "1.0.2" version = "1.1.0"
description = "nym-connect" description = "nym-connect"
authors = ["Nym Technologies SA"] authors = ["Nym Technologies SA"]
license = "" license = ""
+1 -14
View File
@@ -18,8 +18,6 @@ pub type StatusReceiver = futures::channel::oneshot::Receiver<Socks5StatusMessag
pub enum Socks5StatusMessage { pub enum Socks5StatusMessage {
/// The SOCKS5 task successfully stopped /// The SOCKS5 task successfully stopped
Stopped, Stopped,
/// The SOCKS5 task failed to start
FailedToStart,
} }
/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`. /// 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 // The status channel is used to both get the state of the task, and if it's closed, to check
// for panic. // for panic.
std::thread::spawn(|| { std::thread::spawn(|| {
let result = tokio::runtime::Runtime::new() tokio::runtime::Runtime::new()
.expect("Failed to create runtime for SOCKS5 client") .expect("Failed to create runtime for SOCKS5 client")
.block_on(async move { socks5_client.run_and_listen(socks5_ctrl_rx).await }); .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"); log::info!("SOCKS5 task finished");
socks5_status_tx socks5_status_tx
.send(Socks5StatusMessage::Stopped) .send(Socks5StatusMessage::Stopped)
@@ -79,9 +69,6 @@ pub fn start_disconnect_listener(
Ok(Socks5StatusMessage::Stopped) => { Ok(Socks5StatusMessage::Stopped) => {
log::info!("SOCKS5 task reported it has finished"); log::info!("SOCKS5 task reported it has finished");
} }
Ok(Socks5StatusMessage::FailedToStart) => {
log::info!("SOCKS5 task reported it failed to start");
}
Err(_) => { Err(_) => {
log::info!("SOCKS5 task appears to have stopped abruptly"); log::info!("SOCKS5 task appears to have stopped abruptly");
// TODO: we should probably generate some events here, or otherwise signal to the // TODO: we should probably generate some events here, or otherwise signal to the
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"package": { "package": {
"productName": "nym-connect", "productName": "nym-connect",
"version": "1.0.2" "version": "1.1.0"
}, },
"build": { "build": {
"distDir": "../dist", "distDir": "../dist",
BIN
View File
Binary file not shown.
+15 -3
View File
@@ -1,8 +1,21 @@
# Changelog # 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) ## [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: compound and redeem functionalities for operator rewards
- wallet: a few minor touch ups and bug fixes - 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) ## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
- wallet: dark mode - wallet: dark mode
+1 -2
View File
@@ -2877,7 +2877,7 @@ dependencies = [
[[package]] [[package]]
name = "nym_wallet" name = "nym_wallet"
version = "1.0.9" version = "1.1.0"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"argon2 0.3.4", "argon2 0.3.4",
@@ -5423,7 +5423,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"contracts-common", "contracts-common",
"cosmwasm-std", "cosmwasm-std",
"log",
"mixnet-contract-common", "mixnet-contract-common",
"schemars", "schemars",
"serde", "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 STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n1qa4hswlcjmttulj0q9qa46jf64f93pecl6tydcsjldfe0hy5ju0sdmwzya";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g"; "n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = 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) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> { pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new( vec![ValidatorDetails::new(
"https://v2-env-validator.qa.nymte.ch/", "https://qwerty-validator.qa.nymte.ch/",
Some("https://v2-env-val-api.qa.nymte.ch/api/"), Some("https://qwerty-validator-api.qa.nymte.ch/api"),
)] )]
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym_wallet" name = "nym_wallet"
version = "1.0.9" version = "1.1.0"
description = "Nym Native Wallet" description = "Nym Native Wallet"
authors = ["Nym Technologies SA"] authors = ["Nym Technologies SA"]
license = "" license = ""
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"package": { "package": {
"productName": "nym-wallet", "productName": "nym-wallet",
"version": "1.0.9" "version": "1.1.0"
}, },
"build": { "build": {
"distDir": "../dist", "distDir": "../dist",
@@ -42,7 +42,7 @@ const headers: Header[] = [
header: 'Operator rewards', header: 'Operator rewards',
id: 'operator-rewards', id: 'operator-rewards',
tooltipText: 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', header: 'No. delegators',
@@ -179,7 +179,7 @@ const AmountFormData = ({
<CurrencyFormField <CurrencyFormField
required required
fullWidth fullWidth
label="Operator cost" label="Operator Cost"
autoFocus autoFocus
onChanged={(newValue) => setValue('operatorCost', newValue, { shouldValidate: true })} onChanged={(newValue) => setValue('operatorCost', newValue, { shouldValidate: true })}
validationError={errors.operatorCost?.amount?.message} validationError={errors.operatorCost?.amount?.message}
+2 -9
View File
@@ -130,16 +130,10 @@ const TokenTransfer = () => {
</Grid> </Grid>
<Grid item> <Grid item>
<Typography variant="subtitle2" sx={{ color: (t) => t.palette.nym.text.muted, mt: 2 }}> <Typography variant="subtitle2" sx={{ color: (t) => t.palette.nym.text.muted, mt: 2 }}>
Transferable tokens Unlocked transferable tokens
</Typography> </Typography>
<Typography <Typography data-testid="refresh-success" sx={{ color: 'text.primary' }} variant="h5" textTransform="uppercase">
data-testid="refresh-success"
sx={{ color: 'text.primary' }}
variant="h5"
fontWeight="700"
textTransform="uppercase"
>
{userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()} {userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()}
</Typography> </Typography>
</Grid> </Grid>
@@ -167,7 +161,6 @@ export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise<void> })
<NymCard <NymCard
title="Vesting Schedule" title="Vesting Schedule"
data-testid="check-unvested-tokens" data-testid="check-unvested-tokens"
Icon={<InfoOutlined />}
Action={ Action={
<IconButton <IconButton
onClick={async () => { onClick={async () => {
@@ -33,10 +33,17 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => {
</Grid> </Grid>
<Grid item container direction={'column'} spacing={2} width={0.5} padding={3}> <Grid item container direction={'column'} spacing={2} width={0.5} padding={3}>
<Grid item> <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 <Error
message={`Unbonding is irreversible and it wont be possible to restore the current state of your ${ message={`Unbonding is irreversible and it wont be possible to restore the current state of your ${
isMixnode(bondedNode) ? 'node' : 'gateway' isMixnode(bondedNode) ? 'node' : 'gateway'
} again`} } again.`}
/> />
</Grid> </Grid>
<Grid item> <Grid item>

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