Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c45e8da43d | |||
| 12cc49a734 | |||
| 7e56a9e88c | |||
| 9790009eac | |||
| 379d593daf | |||
| ce75b99b6f | |||
| bcb7c41fd7 | |||
| bb091ce47f | |||
| 9b14e00653 | |||
| ec8b5e6e9d | |||
| effed4d7d6 | |||
| d4584c305a | |||
| afc53d4379 | |||
| 4278e88d3c | |||
| e12a34ce6b | |||
| 1de64f7b52 | |||
| 66dbe09e66 | |||
| dcce269921 | |||
| c043f0096a | |||
| fe6da046dc | |||
| 99b30c2570 | |||
| 2c5d31e685 | |||
| cf65bc1295 | |||
| 2d5f851252 | |||
| 5ce087dafe | |||
| caf03a09c8 | |||
| 5db47b8931 | |||
| 27c1b29615 | |||
| c80c8ef899 | |||
| d0cd22c4da | |||
| a721e97c06 | |||
| d480ddb133 | |||
| b119820591 | |||
| e128949dc2 | |||
| 9499b987e5 | |||
| d6ac786295 | |||
| 4d09d9c3db | |||
| 8c9044adf3 | |||
| 472085ca52 | |||
| 2f089e80ff |
+27
-14
@@ -1,36 +1,49 @@
|
||||
name: Daily security audit
|
||||
|
||||
on: workflow_dispatch
|
||||
on:
|
||||
schedule:
|
||||
- cron: '5 9 * * *'
|
||||
jobs:
|
||||
security_audit:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/audit-check@v1
|
||||
- name: Checkout repository code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
toolchain: stable
|
||||
- name: Install cargo deny
|
||||
run: cargo install --locked cargo-deny
|
||||
- name: Run cargo deny
|
||||
run: cargo deny check advisories --hide-inclusion-graph &> .github/workflows/support-files/notifications/deny.message
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: report
|
||||
path: .github/workflows/support-files/notifications/deny.message
|
||||
notification:
|
||||
if: ${{ failure() }}
|
||||
needs: security_audit
|
||||
needs: cargo-deny
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
- name: Download report from previous job
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: report
|
||||
path: .github/workflows/support-files/notifications
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Keybase - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nightly
|
||||
NYM_PROJECT_NAME: "Nym daily audit"
|
||||
NYM_NOTIFICATION_KIND: security
|
||||
NYM_PROJECT_NAME: "Daily security report"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBTECH_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "test"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "security"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -3,7 +3,7 @@ require('dotenv').config();
|
||||
const Bot = require('keybase-bot');
|
||||
|
||||
let context = {
|
||||
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect'],
|
||||
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security'],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
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
|
||||
@@ -15,6 +15,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- 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).
|
||||
|
||||
@@ -50,6 +51,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1687]: https://github.com/nymtech/nym/pull/1687
|
||||
[#1702]: https://github.com/nymtech/nym/pull/1702
|
||||
[#1703]: https://github.com/nymtech/nym/pull/1703
|
||||
[#1713]: https://github.com/nymtech/nym/pull/1713
|
||||
[#1721]: https://github.com/nymtech/nym/pull/1721
|
||||
[#1724]: https://github.com/nymtech/nym/pull/1724
|
||||
[#1725]: https://github.com/nymtech/nym/pull/1725
|
||||
|
||||
Generated
+2
@@ -3147,6 +3147,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sled",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.14.0",
|
||||
"topology",
|
||||
@@ -3323,6 +3324,7 @@ dependencies = [
|
||||
"snafu 0.6.10",
|
||||
"socks5-requests",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"topology",
|
||||
"url",
|
||||
|
||||
@@ -122,3 +122,6 @@ mixnet-opt: wasm
|
||||
generate-typescript:
|
||||
cd tools/ts-rs-cli && cargo run && cd ../..
|
||||
yarn types:lint:fix
|
||||
|
||||
run-validator-tests:
|
||||
cd validator-api/tests/functional_test && yarn test
|
||||
@@ -296,7 +296,7 @@ where
|
||||
async fn on_message(&mut self, next_message: StreamMessage) {
|
||||
trace!("created new message");
|
||||
|
||||
let next_message = match next_message {
|
||||
let (next_message, fragment_id) = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
@@ -315,6 +315,7 @@ where
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
|
||||
(
|
||||
generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
@@ -324,11 +325,14 @@ where
|
||||
self.config.average_packet_delay,
|
||||
self.config.cover_packet_size,
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology")
|
||||
.expect(
|
||||
"Somehow failed to generate a loop cover message with a valid topology",
|
||||
),
|
||||
None,
|
||||
)
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
self.sent_notify(real_message.fragment_id);
|
||||
real_message.mix_packet
|
||||
(real_message.mix_packet, Some(real_message.fragment_id))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -336,6 +340,12 @@ where
|
||||
log::error!("Failed to send - channel closed: {}", err);
|
||||
}
|
||||
|
||||
// notify ack controller about sending our message only after we actually managed to push it
|
||||
// through the channel
|
||||
if let Some(fragment_id) = fragment_id {
|
||||
self.sent_notify(fragment_id);
|
||||
}
|
||||
|
||||
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
||||
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
|
||||
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
|
||||
|
||||
@@ -57,24 +57,15 @@ impl<'a> TopologyReadPermit<'a> {
|
||||
) -> Option<&'a NymTopology> {
|
||||
// Note: implicit deref with Deref for TopologyReadPermit is happening here
|
||||
let topology_ref_option = self.permit.as_ref();
|
||||
match topology_ref_option {
|
||||
None => None,
|
||||
Some(topology_ref) => {
|
||||
// see if it's possible to route the packet to both gateways
|
||||
if !topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|
||||
topology_ref_option.as_ref().filter(|topology_ref| {
|
||||
!(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|
||||
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|
||||
|| if let Some(packet_recipient) = packet_recipient {
|
||||
!topology_ref.gateway_exists(packet_recipient.gateway())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(topology_ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,6 @@ pub enum ClientCoreError {
|
||||
ListOfValidatorApisIsEmpty,
|
||||
#[error("Could not load existing gateway configuration: {0}")]
|
||||
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||
#[error("The current network topology seem to be insufficient to route any packets through")]
|
||||
InsufficientNetworkTopology,
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ pretty_env_logger = "0.4" # for formatting log messages
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
|
||||
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
|
||||
sled = "0.34" # for storage of replySURB decryption keys
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||
tokio-tungstenite = "0.14" # websocket
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use client_core::error::ClientCoreError;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
@@ -33,6 +34,7 @@ use nymsphinx::receiver::ReconstructedMessage;
|
||||
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::error::ClientError;
|
||||
use crate::websocket;
|
||||
|
||||
pub(crate) mod config;
|
||||
@@ -232,7 +234,7 @@ impl NymClient {
|
||||
&mut self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
) -> Result<(), ClientError> {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
@@ -247,14 +249,16 @@ impl NymClient {
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !topology_refresher.is_topology_routable().await {
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
log::error!(
|
||||
"The current network topology seem to be insufficient to route any packets through \
|
||||
- check if enough nodes and a gateway are online"
|
||||
);
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology.into());
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start_with_shutdown(shutdown);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
@@ -328,8 +332,8 @@ impl NymClient {
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub async fn run_forever(&mut self) {
|
||||
let shutdown = self.start().await;
|
||||
pub async fn run_forever(&mut self) -> Result<(), ClientError> {
|
||||
let shutdown = self.start().await?;
|
||||
wait_for_signal().await;
|
||||
|
||||
println!(
|
||||
@@ -346,9 +350,10 @@ impl NymClient {
|
||||
//shutdown.wait_for_shutdown().await;
|
||||
|
||||
log::info!("Stopping nym-client");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> ShutdownNotifier {
|
||||
pub async fn start(&mut self) -> Result<ShutdownNotifier, ClientError> {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -379,7 +384,7 @@ impl NymClient {
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
|
||||
.await;
|
||||
.await?;
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
@@ -443,6 +448,6 @@ impl NymClient {
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self.as_mix_recipient());
|
||||
|
||||
shutdown
|
||||
Ok(shutdown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::error::ClientError;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use completions::{fig_generate, ArgShell};
|
||||
@@ -83,16 +84,17 @@ pub(crate) struct OverrideConfig {
|
||||
enabled_credentials_mode: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) {
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> {
|
||||
let bin_name = "nym-native-client";
|
||||
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::{
|
||||
client::{config::Config, NymClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::ClientError,
|
||||
};
|
||||
|
||||
use clap::Args;
|
||||
@@ -73,14 +74,14 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
|
||||
return;
|
||||
return Err(ClientError::FailedToLoadConfig(id.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -89,8 +90,8 @@ pub(crate) async fn execute(args: &Run) {
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
return Err(ClientError::FailedLocalVersionCheck);
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever().await;
|
||||
NymClient::new(config).run_forever().await
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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,
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod websocket;
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use error::ClientError;
|
||||
use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
pub mod error;
|
||||
pub mod websocket;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> Result<(), ClientError> {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let args = commands::Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
commands::execute(&args).await;
|
||||
commands::execute(&args).await
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
|
||||
@@ -20,6 +20,7 @@ pretty_env_logger = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
|
||||
snafu = "0.6"
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
|
||||
url = "2.2"
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::error::Socks5ClientError;
|
||||
use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
@@ -23,6 +24,7 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use client_core::error::ClientCoreError;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
@@ -232,7 +234,7 @@ impl NymClient {
|
||||
&mut self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
) -> Result<(), Socks5ClientError> {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
@@ -247,14 +249,16 @@ impl NymClient {
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !topology_refresher.is_topology_routable().await {
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
log::error!(
|
||||
"The current network topology seem to be insufficient to route any packets through \
|
||||
- check if enough nodes and a gateway are online"
|
||||
);
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology.into());
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start_with_shutdown(shutdown);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
@@ -293,8 +297,8 @@ impl NymClient {
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub async fn run_forever(&mut self) {
|
||||
let mut shutdown = self.start().await;
|
||||
pub async fn run_forever(&mut self) -> Result<(), Socks5ClientError> {
|
||||
let mut shutdown = self.start().await?;
|
||||
wait_for_signal().await;
|
||||
|
||||
log::info!("Sending shutdown");
|
||||
@@ -305,11 +309,15 @@ impl NymClient {
|
||||
shutdown.wait_for_shutdown().await;
|
||||
|
||||
log::info!("Stopping nym-socks5-client");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Variant of `run_forever` that listends for remote control messages
|
||||
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
|
||||
let mut shutdown = self.start().await;
|
||||
pub async fn run_and_listen(
|
||||
&mut self,
|
||||
mut receiver: Socks5ControlMessageReceiver,
|
||||
) -> Result<(), Socks5ClientError> {
|
||||
let mut shutdown = self.start().await?;
|
||||
tokio::select! {
|
||||
message = receiver.next() => {
|
||||
log::debug!("Received message: {:?}", message);
|
||||
@@ -335,9 +343,10 @@ impl NymClient {
|
||||
shutdown.wait_for_shutdown().await;
|
||||
|
||||
log::info!("Stopping nym-socks5-client");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> ShutdownNotifier {
|
||||
pub async fn start(&mut self) -> Result<ShutdownNotifier, Socks5ClientError> {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -368,7 +377,7 @@ impl NymClient {
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
|
||||
.await;
|
||||
.await?;
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
@@ -417,6 +426,6 @@ impl NymClient {
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self.as_mix_recipient());
|
||||
|
||||
shutdown
|
||||
Ok(shutdown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::error::Socks5ClientError;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use completions::{fig_generate, ArgShell};
|
||||
@@ -83,16 +84,17 @@ pub(crate) struct OverrideConfig {
|
||||
enabled_credentials_mode: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) {
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), Socks5ClientError> {
|
||||
let bin_name = "nym-socks5-client";
|
||||
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::{
|
||||
client::{config::Config, NymClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
|
||||
use clap::Args;
|
||||
@@ -80,14 +81,14 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Socks5ClientError> {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
|
||||
return;
|
||||
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,8 +97,8 @@ pub(crate) async fn execute(args: &Run) {
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
return Err(Socks5ClientError::FailedLocalVersionCheck);
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever().await;
|
||||
NymClient::new(config).run_forever().await
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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,
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use error::Socks5ClientError;
|
||||
use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
mod commands;
|
||||
pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> Result<(), Socks5ClientError> {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let args = commands::Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
commands::execute(&args).await;
|
||||
commands::execute(&args).await
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "nym-client-wasm"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
@@ -55,6 +55,8 @@ wee_alloc = { version = "0.4", optional = true }
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
|
||||
wasm-opt = true
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
@@ -2,6 +2,11 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
performance: {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000
|
||||
},
|
||||
entry: {
|
||||
bootstrap: './bootstrap.js',
|
||||
worker: './worker.js',
|
||||
@@ -22,6 +27,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
],
|
||||
experiments: { syncWebAssembly: true },
|
||||
};
|
||||
|
||||
@@ -73,6 +73,7 @@ async function main() {
|
||||
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
|
||||
|
||||
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
|
||||
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
|
||||
|
||||
// only really useful if you want to adjust some settings like traffic rate
|
||||
// (if not needed you can just pass a null)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
@@ -11,6 +11,8 @@ use cosmwasm_std::Event;
|
||||
/// * `event`: event to search through.
|
||||
/// * `key`: key associated with the particular attribute
|
||||
pub fn must_find_attribute(event: &Event, key: &str) -> String {
|
||||
// due to how the function is supposed to work, the unwrap is fine in this instance
|
||||
#[allow(clippy::unwrap_used)]
|
||||
may_find_attribute(event, key).unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod events;
|
||||
pub mod types;
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ impl Percent {
|
||||
|
||||
impl Display for Percent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0;
|
||||
let adjusted = Decimal::from_ratio(100u32, 1u32) * self.0;
|
||||
write!(f, "{}%", adjusted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
|
||||
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
|
||||
// This is equivalent of representing our display coin with 6 decimal places.
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::constants::TOKEN_SUPPLY;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::{Addr, MixId};
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use cosmwasm_std::{Coin, Decimal, StdResult};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -60,6 +62,11 @@ impl Delegation {
|
||||
height: u64,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
amount.amount <= TOKEN_SUPPLY,
|
||||
"delegation cannot be larger than the token supply"
|
||||
);
|
||||
|
||||
Delegation {
|
||||
owner,
|
||||
mix_id,
|
||||
@@ -87,10 +94,8 @@ impl Delegation {
|
||||
(mix_id, owner_proxy_subkey)
|
||||
}
|
||||
|
||||
pub fn dec_amount(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
Decimal::from_atomics(self.amount.amount, 0).unwrap()
|
||||
pub fn dec_amount(&self) -> StdResult<Decimal> {
|
||||
self.amount.amount.into_base_decimal()
|
||||
}
|
||||
|
||||
pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use cosmwasm_std::{Decimal, StdError, StdResult, Uint128};
|
||||
|
||||
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
|
||||
let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128));
|
||||
@@ -11,3 +11,23 @@ pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
|
||||
assert!(b - a < epsilon, "{} != {}", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_base_decimal(val: impl Into<Uint128>) -> StdResult<Decimal> {
|
||||
val.into_base_decimal()
|
||||
}
|
||||
|
||||
pub trait IntoBaseDecimal {
|
||||
fn into_base_decimal(self) -> StdResult<Decimal>;
|
||||
}
|
||||
|
||||
impl<T> IntoBaseDecimal for T
|
||||
where
|
||||
T: Into<Uint128>,
|
||||
{
|
||||
fn into_base_decimal(self) -> StdResult<Decimal> {
|
||||
let atomics = self.into();
|
||||
Decimal::from_atomics(atomics, 0).map_err(|_| StdError::GenericErr {
|
||||
msg: format!("Decimal range exceeded for {atomics} with 0 decimal places."),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,14 +145,17 @@ impl JsonSchema for Interval {
|
||||
impl Interval {
|
||||
/// Initialize epoch in the contract with default values.
|
||||
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self {
|
||||
// if this fails it means the value provided from the chain itself (via cosmwasm) is invalid,
|
||||
// so we really have to panic here as anything beyond that point would be invalid anyway
|
||||
#[allow(clippy::expect_used)]
|
||||
let current_epoch_start =
|
||||
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
|
||||
.expect("The timestamp provided via env.block.time is invalid");
|
||||
|
||||
Interval {
|
||||
id: 0,
|
||||
epochs_in_interval,
|
||||
// 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_start,
|
||||
current_epoch_id: 0,
|
||||
epoch_length,
|
||||
total_elapsed_epochs: 0,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
mod constants;
|
||||
pub mod delegation;
|
||||
pub mod error;
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::constants::UNIT_DELEGATION_BASE;
|
||||
use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::reward_params::{NodeRewardParams, RewardingParams};
|
||||
use crate::rewarding::helpers::truncate_reward;
|
||||
use crate::rewarding::RewardDistribution;
|
||||
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
@@ -64,7 +65,7 @@ impl MixNodeDetails {
|
||||
self.rewarding_details.pending_operator_reward(pledge)
|
||||
}
|
||||
|
||||
pub fn pending_detailed_operator_reward(&self) -> Decimal {
|
||||
pub fn pending_detailed_operator_reward(&self) -> StdResult<Decimal> {
|
||||
let pledge = self.original_pledge();
|
||||
self.rewarding_details
|
||||
.pending_detailed_operator_reward(pledge)
|
||||
@@ -107,16 +108,21 @@ impl MixNodeRewarding {
|
||||
cost_params: MixNodeCostParams,
|
||||
initial_pledge: &Coin,
|
||||
current_epoch: EpochId,
|
||||
) -> Self {
|
||||
MixNodeRewarding {
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
assert!(
|
||||
initial_pledge.amount <= TOKEN_SUPPLY,
|
||||
"pledge cannot be larger than the token supply"
|
||||
);
|
||||
|
||||
Ok(MixNodeRewarding {
|
||||
cost_params,
|
||||
operator: Decimal::from_atomics(initial_pledge.amount, 0).unwrap(),
|
||||
operator: initial_pledge.amount.into_base_decimal()?,
|
||||
delegates: Decimal::zero(),
|
||||
total_unit_reward: Decimal::zero(),
|
||||
unit_delegation: UNIT_DELEGATION_BASE,
|
||||
last_rewarded_epoch: current_epoch,
|
||||
unique_delegations: 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Determines whether this node is still bonded. This is performed via a simple check,
|
||||
@@ -135,27 +141,30 @@ impl MixNodeRewarding {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> Decimal {
|
||||
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
|
||||
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> StdResult<Decimal> {
|
||||
let initial_dec = original_pledge.amount.into_base_decimal()?;
|
||||
if initial_dec > self.operator {
|
||||
panic!(
|
||||
"seems slashing has occurred while it has not been implemented nor accounted for!"
|
||||
)
|
||||
}
|
||||
self.operator - initial_dec
|
||||
Ok(self.operator - initial_dec)
|
||||
}
|
||||
|
||||
pub fn operator_pledge_with_reward(&self, denom: impl Into<String>) -> Coin {
|
||||
truncate_reward(self.operator, denom)
|
||||
}
|
||||
|
||||
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> Coin {
|
||||
let delegator_reward = self.determine_delegation_reward(delegation);
|
||||
truncate_reward(delegator_reward, &delegation.amount.denom)
|
||||
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
|
||||
let delegator_reward = self.determine_delegation_reward(delegation)?;
|
||||
Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
|
||||
}
|
||||
|
||||
pub fn withdraw_operator_reward(&mut self, original_pledge: &Coin) -> Coin {
|
||||
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
|
||||
pub fn withdraw_operator_reward(
|
||||
&mut self,
|
||||
original_pledge: &Coin,
|
||||
) -> Result<Coin, MixnetContractError> {
|
||||
let initial_dec = original_pledge.amount.into_base_decimal()?;
|
||||
if initial_dec > self.operator {
|
||||
panic!(
|
||||
"seems slashing has occurred while it has not been implemented nor accounted for!"
|
||||
@@ -164,14 +173,14 @@ impl MixNodeRewarding {
|
||||
let diff = self.operator - initial_dec;
|
||||
self.operator = initial_dec;
|
||||
|
||||
truncate_reward(diff, &original_pledge.denom)
|
||||
Ok(truncate_reward(diff, &original_pledge.denom))
|
||||
}
|
||||
|
||||
pub fn withdraw_delegator_reward(
|
||||
&mut self,
|
||||
delegation: &mut Delegation,
|
||||
) -> Result<Coin, MixnetContractError> {
|
||||
let reward = self.determine_delegation_reward(delegation);
|
||||
let reward = self.determine_delegation_reward(delegation)?;
|
||||
self.decrease_delegates_decimal(reward)?;
|
||||
|
||||
delegation.cumulative_reward_ratio = self.full_reward_ratio();
|
||||
@@ -301,23 +310,27 @@ impl MixNodeRewarding {
|
||||
self.distribute_rewards(reward_distribution, absolute_epoch_id)
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
|
||||
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> StdResult<Decimal> {
|
||||
let starting_ratio = delegation.cumulative_reward_ratio;
|
||||
let ending_ratio = self.full_reward_ratio();
|
||||
let adjust = starting_ratio + self.unit_delegation;
|
||||
|
||||
(ending_ratio - starting_ratio) * delegation.dec_amount() / adjust
|
||||
Ok((ending_ratio - starting_ratio) * delegation.dec_amount()? / adjust)
|
||||
}
|
||||
|
||||
// this updates `unique_delegations` field
|
||||
pub fn add_base_delegation(&mut self, amount: Uint128) {
|
||||
self.increase_delegates_uint128(amount);
|
||||
pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> {
|
||||
self.increase_delegates_uint128(amount)?;
|
||||
self.unique_delegations += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn increase_delegates_uint128(&mut self, amount: Uint128) {
|
||||
// the unwrap here is fine as the value is guaranteed to fit under provided constraints
|
||||
self.delegates += Decimal::from_atomics(amount, 0).unwrap()
|
||||
pub fn increase_delegates_uint128(
|
||||
&mut self,
|
||||
amount: Uint128,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
self.delegates += amount.into_base_decimal()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// this updates `unique_delegations` field
|
||||
@@ -335,7 +348,7 @@ impl MixNodeRewarding {
|
||||
&mut self,
|
||||
amount: Uint128,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let amount_dec = Decimal::from_atomics(amount, 0).unwrap();
|
||||
let amount_dec = amount.into_base_decimal()?;
|
||||
self.decrease_delegates_decimal(amount_dec)
|
||||
}
|
||||
|
||||
@@ -368,8 +381,8 @@ impl MixNodeRewarding {
|
||||
}
|
||||
|
||||
pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> {
|
||||
let reward = self.determine_delegation_reward(delegation);
|
||||
let full_amount = reward + delegation.dec_amount();
|
||||
let reward = self.determine_delegation_reward(delegation)?;
|
||||
let full_amount = reward + delegation.dec_amount()?;
|
||||
self.remove_delegation_decimal(full_amount)?;
|
||||
Ok(truncate_reward(full_amount, &delegation.amount.denom))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::delegation::OwnerProxySubKey;
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use crate::reward_params::{
|
||||
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
|
||||
@@ -41,14 +43,17 @@ pub struct InitialRewardingParams {
|
||||
}
|
||||
|
||||
impl InitialRewardingParams {
|
||||
pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams {
|
||||
pub fn into_rewarding_params(
|
||||
self,
|
||||
epochs_in_interval: u32,
|
||||
) -> Result<RewardingParams, MixnetContractError> {
|
||||
let epoch_reward_budget = self.initial_reward_pool
|
||||
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
|
||||
/ epochs_in_interval.into_base_decimal()?
|
||||
* self.interval_pool_emission;
|
||||
let stake_saturation_point =
|
||||
self.initial_staking_supply / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
|
||||
self.initial_staking_supply / self.rewarded_set_size.into_base_decimal()?;
|
||||
|
||||
RewardingParams {
|
||||
Ok(RewardingParams {
|
||||
interval: IntervalRewardParams {
|
||||
reward_pool: self.initial_reward_pool,
|
||||
staking_supply: self.initial_staking_supply,
|
||||
@@ -61,7 +66,7 @@ impl InitialRewardingParams {
|
||||
},
|
||||
rewarded_set_size: self.rewarded_set_size,
|
||||
active_set_size: self.active_set_size,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::{error::MixnetContractError, Percent};
|
||||
use cosmwasm_std::Decimal;
|
||||
use schemars::JsonSchema;
|
||||
@@ -110,24 +111,33 @@ impl RewardingParams {
|
||||
pub fn dec_rewarded_set_size(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
Decimal::from_atomics(self.rewarded_set_size, 0).unwrap()
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.rewarded_set_size.into_base_decimal().unwrap()
|
||||
}
|
||||
|
||||
pub fn dec_active_set_size(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
Decimal::from_atomics(self.active_set_size, 0).unwrap()
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.active_set_size.into_base_decimal().unwrap()
|
||||
}
|
||||
|
||||
fn dec_standby_set_size(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap()
|
||||
#[allow(clippy::unwrap_used)]
|
||||
(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) {
|
||||
self.interval.epoch_reward_budget = self.interval.reward_pool
|
||||
/ Decimal::from_atomics(new_epochs_in_interval, 0).unwrap()
|
||||
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let new_epochs_in_interval = new_epochs_in_interval.into_base_decimal().unwrap();
|
||||
|
||||
self.interval.epoch_reward_budget = self.interval.reward_pool / new_epochs_in_interval
|
||||
* self.interval.interval_pool_emission;
|
||||
}
|
||||
|
||||
@@ -200,13 +210,13 @@ impl RewardingParams {
|
||||
|
||||
if recompute_epoch_budget {
|
||||
self.interval.epoch_reward_budget = self.interval.reward_pool
|
||||
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
|
||||
/ epochs_in_interval.into_base_decimal()?
|
||||
* self.interval.interval_pool_emission;
|
||||
}
|
||||
|
||||
if recompute_saturation_point {
|
||||
self.interval.stake_saturation_point = self.interval.staking_supply
|
||||
/ Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
|
||||
self.interval.stake_saturation_point =
|
||||
self.interval.staking_supply / self.rewarded_set_size.into_base_decimal()?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -29,7 +29,7 @@ pub struct RewardEstimate {
|
||||
pub operating_cost: Decimal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct RewardDistribution {
|
||||
pub operator: Decimal,
|
||||
pub delegates: Decimal,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::reward_params::NodeRewardParams;
|
||||
use crate::rewarding::simulator::simulated_node::SimulatedNode;
|
||||
use crate::rewarding::RewardDistribution;
|
||||
@@ -33,7 +34,7 @@ impl Simulator {
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_epoch(&mut self) {
|
||||
fn advance_epoch(&mut self) -> Result<(), MixnetContractError> {
|
||||
let updated = self.interval.advance_epoch();
|
||||
|
||||
// we rolled over an interval
|
||||
@@ -47,10 +48,13 @@ impl Simulator {
|
||||
.staking_supply_scale_factor
|
||||
* self.pending_reward_pool_emission;
|
||||
let epoch_reward_budget = reward_pool
|
||||
/ Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap()
|
||||
/ self.interval.epochs_in_interval().into_base_decimal()?
|
||||
* old.interval_pool_emission.value();
|
||||
let stake_saturation_point = staking_supply
|
||||
/ Decimal::from_atomics(self.system_rewarding_params.rewarded_set_size, 0).unwrap();
|
||||
/ self
|
||||
.system_rewarding_params
|
||||
.rewarded_set_size
|
||||
.into_base_decimal()?;
|
||||
|
||||
let updated_params = RewardingParams {
|
||||
interval: IntervalRewardParams {
|
||||
@@ -71,9 +75,15 @@ impl Simulator {
|
||||
self.pending_reward_pool_emission = Decimal::zero();
|
||||
}
|
||||
self.interval = updated;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bond(&mut self, pledge: Coin, cost_params: MixNodeCostParams) -> MixId {
|
||||
pub fn bond(
|
||||
&mut self,
|
||||
pledge: Coin,
|
||||
cost_params: MixNodeCostParams,
|
||||
) -> Result<MixId, MixnetContractError> {
|
||||
let mix_id = self.next_mix_id;
|
||||
|
||||
self.nodes.insert(
|
||||
@@ -83,16 +93,24 @@ impl Simulator {
|
||||
cost_params,
|
||||
&pledge,
|
||||
self.interval.current_epoch_absolute_id(),
|
||||
),
|
||||
)?,
|
||||
);
|
||||
|
||||
self.next_mix_id += 1;
|
||||
|
||||
mix_id
|
||||
Ok(mix_id)
|
||||
}
|
||||
|
||||
pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin, mix_id: MixId) {
|
||||
let node = self.nodes.get_mut(&mix_id).expect("node doesn't exist");
|
||||
pub fn delegate<S: Into<String>>(
|
||||
&mut self,
|
||||
delegator: S,
|
||||
delegation: Coin,
|
||||
mix_id: MixId,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let node = self
|
||||
.nodes
|
||||
.get_mut(&mix_id)
|
||||
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
|
||||
node.delegate(delegator, delegation)
|
||||
}
|
||||
|
||||
@@ -103,23 +121,35 @@ impl Simulator {
|
||||
delegator: S,
|
||||
mix_id: MixId,
|
||||
) -> Result<(Coin, Coin), MixnetContractError> {
|
||||
let node = self.nodes.get_mut(&mix_id).expect("node not found");
|
||||
let node = self
|
||||
.nodes
|
||||
.get_mut(&mix_id)
|
||||
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
|
||||
node.undelegate(delegator)
|
||||
}
|
||||
|
||||
pub fn simulate_epoch_single_node(&mut self, params: NodeRewardParams) -> RewardDistribution {
|
||||
pub fn simulate_epoch_single_node(
|
||||
&mut self,
|
||||
params: NodeRewardParams,
|
||||
) -> Result<RewardDistribution, MixnetContractError> {
|
||||
assert_eq!(self.nodes.len(), 1);
|
||||
|
||||
let id = *self.nodes.keys().next().unwrap();
|
||||
if let Some(&id) = self.nodes.keys().next() {
|
||||
let mut params_map = BTreeMap::new();
|
||||
params_map.insert(id, params);
|
||||
self.simulate_epoch(¶ms_map).remove(&id).unwrap()
|
||||
Ok(self
|
||||
.simulate_epoch(¶ms_map)?
|
||||
.remove(&id)
|
||||
.unwrap_or_default())
|
||||
} else {
|
||||
Ok(RewardDistribution::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simulate_epoch(
|
||||
&mut self,
|
||||
node_params: &BTreeMap<MixId, NodeRewardParams>,
|
||||
) -> BTreeMap<MixId, RewardDistribution> {
|
||||
) -> Result<BTreeMap<MixId, RewardDistribution>, MixnetContractError> {
|
||||
let mut params_keys = node_params.keys().copied().collect::<Vec<_>>();
|
||||
params_keys.sort_unstable();
|
||||
let mut node_keys = self.nodes.keys().copied().collect::<Vec<_>>();
|
||||
@@ -147,34 +177,41 @@ impl Simulator {
|
||||
dist.insert(*mix_id, reward_distribution);
|
||||
}
|
||||
|
||||
self.advance_epoch();
|
||||
dist
|
||||
self.advance_epoch()?;
|
||||
Ok(dist)
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
|
||||
self.nodes[&delegation.mix_id]
|
||||
pub fn determine_delegation_reward(
|
||||
&self,
|
||||
delegation: &Delegation,
|
||||
) -> Result<Decimal, MixnetContractError> {
|
||||
Ok(self.nodes[&delegation.mix_id]
|
||||
.rewarding_details
|
||||
.determine_delegation_reward(delegation)
|
||||
.determine_delegation_reward(delegation)?)
|
||||
}
|
||||
|
||||
pub fn determine_total_delegation_reward(&self) -> Decimal {
|
||||
pub fn determine_total_delegation_reward(&self) -> Result<Decimal, MixnetContractError> {
|
||||
let mut total = Decimal::zero();
|
||||
|
||||
for node in self.nodes.values() {
|
||||
for delegation in node.delegations.values() {
|
||||
total += node
|
||||
.rewarding_details
|
||||
.determine_delegation_reward(delegation)
|
||||
.determine_delegation_reward(delegation)?
|
||||
}
|
||||
}
|
||||
total
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
// assume node state doesn't change in the interval (kinda unrealistic)
|
||||
pub fn simulate_full_interval(&mut self, node_params: &BTreeMap<MixId, NodeRewardParams>) {
|
||||
pub fn simulate_full_interval(
|
||||
&mut self,
|
||||
node_params: &BTreeMap<MixId, NodeRewardParams>,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
for _ in 0..self.interval.epochs_in_interval() {
|
||||
self.simulate_epoch(node_params);
|
||||
self.simulate_epoch(node_params)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +234,10 @@ mod tests {
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use std::time::Duration;
|
||||
|
||||
// explicitly marking this as part of #[allow(clippy::unwrap_used)] until
|
||||
// https://github.com/rust-lang/rust-clippy/pull/9686
|
||||
// is merged into a release
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn base_simulator(initial_pledge: u128) -> Simulator {
|
||||
let profit_margin = Percent::from_percentage_value(10).unwrap();
|
||||
let interval_operating_cost = Coin::new(40_000_000, "unym");
|
||||
@@ -238,20 +279,32 @@ mod tests {
|
||||
profit_margin_percent: profit_margin,
|
||||
interval_operating_cost,
|
||||
};
|
||||
simulator.bond(initial_pledge, cost_params);
|
||||
simulator.bond(initial_pledge, cost_params).unwrap();
|
||||
simulator
|
||||
}
|
||||
|
||||
// essentially our delegations + estimated rewards HAVE TO equal to what we actually determined
|
||||
//
|
||||
// explicitly marking this as part of #[allow(clippy::unwrap_used)] until
|
||||
// https://github.com/rust-lang/rust-clippy/pull/9686
|
||||
// is merged into a release
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn check_rewarding_invariant(simulator: &Simulator) {
|
||||
for node in simulator.nodes.values() {
|
||||
let delegation_sum: Decimal =
|
||||
node.delegations.values().map(|d| d.dec_amount()).sum();
|
||||
let delegation_sum: Decimal = node
|
||||
.delegations
|
||||
.values()
|
||||
.map(|d| d.dec_amount().unwrap())
|
||||
.sum();
|
||||
|
||||
let reward_sum: Decimal = node
|
||||
.delegations
|
||||
.values()
|
||||
.map(|d| node.rewarding_details.determine_delegation_reward(d))
|
||||
.map(|d| {
|
||||
node.rewarding_details
|
||||
.determine_delegation_reward(d)
|
||||
.unwrap()
|
||||
})
|
||||
.sum();
|
||||
|
||||
// let reward_sum = simulator.determine_total_delegation_reward();
|
||||
@@ -269,7 +322,7 @@ mod tests {
|
||||
|
||||
let epoch_params =
|
||||
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
|
||||
let rewards = simulator.simulate_epoch_single_node(epoch_params);
|
||||
let rewards = simulator.simulate_epoch_single_node(epoch_params).unwrap();
|
||||
|
||||
assert_eq!(rewards.delegates, Decimal::zero());
|
||||
compare_decimals(
|
||||
@@ -282,11 +335,13 @@ mod tests {
|
||||
#[test]
|
||||
fn single_delegation_at_genesis() {
|
||||
let mut simulator = base_simulator(10000_000000);
|
||||
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
|
||||
simulator
|
||||
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
|
||||
let node_params =
|
||||
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
|
||||
let rewards = simulator.simulate_epoch_single_node(node_params);
|
||||
let rewards = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
|
||||
compare_decimals(
|
||||
rewards.delegates,
|
||||
@@ -297,7 +352,7 @@ mod tests {
|
||||
|
||||
compare_decimals(
|
||||
rewards.delegates,
|
||||
simulator.determine_total_delegation_reward(),
|
||||
simulator.determine_total_delegation_reward().unwrap(),
|
||||
None,
|
||||
);
|
||||
let node = &simulator.nodes[&0];
|
||||
@@ -317,20 +372,22 @@ mod tests {
|
||||
let node_params =
|
||||
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
|
||||
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params);
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let expected_operator1 = "1128452.5416104363".parse().unwrap();
|
||||
assert_eq!(rewards1.delegates, Decimal::zero());
|
||||
compare_decimals(rewards1.operator, expected_operator1, None);
|
||||
|
||||
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
|
||||
simulator
|
||||
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params);
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let expected_operator2 = "1363843.413584609".parse().unwrap();
|
||||
let expected_delegator_reward1 = "1795952.25874404".parse().unwrap();
|
||||
compare_decimals(rewards2.delegates, expected_delegator_reward1, None);
|
||||
compare_decimals(rewards2.operator, expected_operator2, None);
|
||||
|
||||
let rewards3 = simulator.simulate_epoch_single_node(node_params);
|
||||
let rewards3 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let expected_operator3 = "1364017.7824440491".parse().unwrap();
|
||||
let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap();
|
||||
compare_decimals(rewards3.delegates, expected_delegator_reward2, None);
|
||||
@@ -364,11 +421,15 @@ mod tests {
|
||||
|
||||
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
|
||||
// delegating at different times still work)
|
||||
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
|
||||
simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
|
||||
simulator
|
||||
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
|
||||
// "normal", sanity check rewarding
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params);
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let expected_operator1 = "1411087.1007647323".parse().unwrap();
|
||||
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
|
||||
compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
|
||||
@@ -378,14 +439,15 @@ mod tests {
|
||||
let node = simulator.nodes.get_mut(&0).unwrap();
|
||||
let reward = node
|
||||
.rewarding_details
|
||||
.withdraw_operator_reward(&original_pledge);
|
||||
.withdraw_operator_reward(&original_pledge)
|
||||
.unwrap();
|
||||
assert_eq!(reward.amount, truncate_reward_amount(expected_operator1));
|
||||
assert_eq!(
|
||||
node.rewarding_details.operator,
|
||||
Decimal::from_atomics(original_pledge.amount, 0).unwrap()
|
||||
);
|
||||
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params);
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let expected_operator2 = "1411113.0004067947".parse().unwrap();
|
||||
let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap();
|
||||
compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
|
||||
@@ -402,11 +464,15 @@ mod tests {
|
||||
|
||||
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
|
||||
// delegating at different times still work)
|
||||
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
|
||||
simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
|
||||
simulator
|
||||
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
|
||||
// "normal", sanity check rewarding
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params);
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let expected_operator1 = "1411087.1007647323".parse().unwrap();
|
||||
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
|
||||
compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
|
||||
@@ -424,7 +490,7 @@ mod tests {
|
||||
assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward));
|
||||
|
||||
// new reward after withdrawal
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params);
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let expected_operator2 = "1411250.1907492676".parse().unwrap();
|
||||
let expected_delegator_reward2 = "2200004.051009689".parse().unwrap();
|
||||
compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
|
||||
@@ -467,22 +533,30 @@ mod tests {
|
||||
let mut performance = Percent::from_percentage_value(100).unwrap();
|
||||
for epoch in 0..720 {
|
||||
if epoch == 0 {
|
||||
simulator.delegate("a", Coin::new(18000_000000, "unym"), 0)
|
||||
simulator
|
||||
.delegate("a", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
}
|
||||
if epoch == 42 {
|
||||
simulator.delegate("b", Coin::new(2000_000000, "unym"), 0)
|
||||
simulator
|
||||
.delegate("b", Coin::new(2000_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
}
|
||||
if epoch == 89 {
|
||||
is_active = false;
|
||||
}
|
||||
if epoch == 123 {
|
||||
simulator.delegate("c", Coin::new(6666_000000, "unym"), 0)
|
||||
simulator
|
||||
.delegate("c", Coin::new(6666_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
}
|
||||
if epoch == 167 {
|
||||
performance = Percent::from_percentage_value(90).unwrap();
|
||||
}
|
||||
if epoch == 245 {
|
||||
simulator.delegate("d", Coin::new(2050_000000, "unym"), 0)
|
||||
simulator
|
||||
.delegate("d", Coin::new(2050_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
}
|
||||
if epoch == 264 {
|
||||
let (delegation, _reward) = simulator.undelegate("b", 0).unwrap();
|
||||
@@ -503,13 +577,15 @@ mod tests {
|
||||
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
|
||||
}
|
||||
if epoch == 545 {
|
||||
simulator.delegate("e", Coin::new(5000_000000, "unym"), 0)
|
||||
simulator
|
||||
.delegate("e", Coin::new(5000_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// this has to always hold
|
||||
check_rewarding_invariant(&simulator);
|
||||
let node_params = NodeRewardParams::new(performance, is_active);
|
||||
simulator.simulate_epoch_single_node(node_params);
|
||||
simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
}
|
||||
|
||||
// after everyone undelegates, there should be nothing left in the delegates pool
|
||||
@@ -563,95 +639,135 @@ mod tests {
|
||||
|
||||
let mut simulator = Simulator::new(rewarding_params, interval);
|
||||
|
||||
let n0 = simulator.bond(
|
||||
let n0 = simulator
|
||||
.bond(
|
||||
Coin::new(11_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0)
|
||||
.unwrap();
|
||||
|
||||
let n1 = simulator.bond(
|
||||
let n1 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1)
|
||||
.unwrap();
|
||||
|
||||
let n2 = simulator.bond(
|
||||
let n2 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2)
|
||||
.unwrap();
|
||||
|
||||
let n3 = simulator.bond(
|
||||
let n3 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
|
||||
interval_operating_cost: Coin::new(500_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3)
|
||||
.unwrap();
|
||||
|
||||
let n4 = simulator.bond(
|
||||
let n4 = simulator
|
||||
.bond(
|
||||
Coin::new(1000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4)
|
||||
.unwrap();
|
||||
|
||||
let n5 = simulator.bond(
|
||||
let n5 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5)
|
||||
.unwrap();
|
||||
|
||||
let n6 = simulator.bond(
|
||||
let n6 = simulator
|
||||
.bond(
|
||||
Coin::new(11_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6)
|
||||
.unwrap();
|
||||
|
||||
let n7 = simulator.bond(
|
||||
let n7 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7)
|
||||
.unwrap();
|
||||
|
||||
let n8 = simulator.bond(
|
||||
let n8 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
|
||||
interval_operating_cost: Coin::new(500_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8)
|
||||
.unwrap();
|
||||
|
||||
let n9 = simulator.bond(
|
||||
let n9 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9);
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9)
|
||||
.unwrap();
|
||||
|
||||
let uptime_1 = Percent::from_percentage_value(100).unwrap();
|
||||
let uptime_09 = Percent::from_percentage_value(90).unwrap();
|
||||
@@ -673,7 +789,7 @@ mod tests {
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
for _ in 0..23 {
|
||||
simulator.simulate_full_interval(&node_params);
|
||||
simulator.simulate_full_interval(&node_params).unwrap();
|
||||
}
|
||||
|
||||
// we allow the delta to be within 0.1unym,
|
||||
|
||||
+20
-12
@@ -20,21 +20,25 @@ impl SimulatedNode {
|
||||
cost_params: MixNodeCostParams,
|
||||
initial_pledge: &Coin,
|
||||
current_epoch: EpochId,
|
||||
) -> Self {
|
||||
SimulatedNode {
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
Ok(SimulatedNode {
|
||||
mix_id,
|
||||
rewarding_details: MixNodeRewarding::initialise_new(
|
||||
cost_params,
|
||||
initial_pledge,
|
||||
current_epoch,
|
||||
),
|
||||
)?,
|
||||
delegations: HashMap::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin) {
|
||||
pub fn delegate<S: Into<String>>(
|
||||
&mut self,
|
||||
delegator: S,
|
||||
delegation: Coin,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
self.rewarding_details
|
||||
.add_base_delegation(delegation.amount);
|
||||
.add_base_delegation(delegation.amount)?;
|
||||
|
||||
let delegator = delegator.into();
|
||||
let delegation = Delegation::new(
|
||||
@@ -47,6 +51,7 @@ impl SimulatedNode {
|
||||
);
|
||||
|
||||
self.delegations.insert(delegator, delegation);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn undelegate<S: Into<String>>(
|
||||
@@ -54,16 +59,19 @@ impl SimulatedNode {
|
||||
delegator: S,
|
||||
) -> Result<(Coin, Coin), MixnetContractError> {
|
||||
let delegator = delegator.into();
|
||||
let delegation = self
|
||||
.delegations
|
||||
.remove(&delegator)
|
||||
.expect("delegation not found");
|
||||
let delegation = self.delegations.remove(&delegator).ok_or(
|
||||
MixnetContractError::NoMixnodeDelegationFound {
|
||||
mix_id: MixId::MAX,
|
||||
address: delegator,
|
||||
proxy: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
let reward = self
|
||||
.rewarding_details
|
||||
.determine_delegation_reward(&delegation);
|
||||
.determine_delegation_reward(&delegation)?;
|
||||
self.rewarding_details
|
||||
.remove_delegation_decimal(delegation.dec_amount() + reward)?;
|
||||
.remove_delegation_decimal(delegation.dec_amount()? + reward)?;
|
||||
|
||||
let reward_denom = &delegation.amount.denom;
|
||||
let truncated_reward = truncate_reward(reward, reward_denom);
|
||||
|
||||
@@ -35,6 +35,10 @@ impl LayerDistribution {
|
||||
(Layer::Two, self.layer2),
|
||||
(Layer::Three, self.layer3),
|
||||
];
|
||||
|
||||
// we explicitly put 3 elements into the iterator, so the iterator is DEFINITELY
|
||||
// not empty and thus the unwrap cannot fail
|
||||
#[allow(clippy::unwrap_used)]
|
||||
layers.iter().min_by_key(|x| x.1).unwrap().0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
|
||||
use log::warn;
|
||||
use mixnet_contract_common::MixId;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ impl SecretKey {
|
||||
// x || ys.len() || ys
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ys_len = self.ys.len();
|
||||
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
|
||||
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.x.to_bytes());
|
||||
bytes.extend_from_slice(&ys_len.to_le_bytes());
|
||||
@@ -162,7 +162,7 @@ impl TryFrom<&[u8]> for VerificationKey {
|
||||
let mut beta_g1_end: u64 = 0;
|
||||
for i in 0..betas_len {
|
||||
let start = (104 + i * 48) as usize;
|
||||
let end = (start + 48) as usize;
|
||||
let end = start + 48;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g1_projective(
|
||||
&beta_i_bytes,
|
||||
@@ -178,7 +178,7 @@ impl TryFrom<&[u8]> for VerificationKey {
|
||||
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
|
||||
for i in 0..betas_len {
|
||||
let start = (beta_g1_end + i * 96) as usize;
|
||||
let end = (start + 96) as usize;
|
||||
let end = start + 96;
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g2_projective(
|
||||
&beta_i_bytes,
|
||||
|
||||
@@ -94,10 +94,10 @@ fn prepare_unlinked_fragmented_set(
|
||||
|
||||
for i in 1..(pre_casted_frags + 1) {
|
||||
// we can't use u8 directly here as upper (NON-INCLUSIVE, so it would always fit) bound could be u8::max_value() + 1
|
||||
let lb = (i as usize - 1) * unlinked_fragment_payload_max_len(max_plaintext_size);
|
||||
let lb = (i - 1) * unlinked_fragment_payload_max_len(max_plaintext_size);
|
||||
let ub = usize::min(
|
||||
message.len(),
|
||||
i as usize * unlinked_fragment_payload_max_len(max_plaintext_size),
|
||||
i * unlinked_fragment_payload_max_len(max_plaintext_size),
|
||||
);
|
||||
fragments.push(
|
||||
Fragment::try_new(
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
@@ -63,7 +63,7 @@ pub fn instantiate(
|
||||
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
|
||||
let reward_params = msg
|
||||
.initial_rewarding_params
|
||||
.into_rewarding_params(msg.epochs_in_interval);
|
||||
.into_rewarding_params(msg.epochs_in_interval)?;
|
||||
|
||||
interval_storage::initialise_storage(deps.storage, starting_interval)?;
|
||||
mixnet_params_storage::initialise_storage(deps.storage, state)?;
|
||||
|
||||
@@ -107,7 +107,7 @@ pub(crate) fn delegate(
|
||||
};
|
||||
|
||||
// add the amount we're intending to delegate (whether it's fresh or we're adding to the existing one)
|
||||
mix_rewarding.add_base_delegation(stored_delegation_amount.amount);
|
||||
mix_rewarding.add_base_delegation(stored_delegation_amount.amount)?;
|
||||
|
||||
let cosmos_event = new_delegation_event(
|
||||
created_at,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
mod constants;
|
||||
pub mod contract;
|
||||
mod delegations;
|
||||
|
||||
@@ -78,7 +78,7 @@ pub(crate) fn save_new_mixnode(
|
||||
let mix_id = next_mixnode_id_counter(storage)?;
|
||||
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
|
||||
|
||||
let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch);
|
||||
let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?;
|
||||
let mixnode_bond = MixNodeBond::new(
|
||||
mix_id,
|
||||
owner,
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
use super::storage;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use cosmwasm_std::{Coin, Decimal, Storage};
|
||||
use cosmwasm_std::{Coin, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::helpers::IntoBaseDecimal;
|
||||
use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding};
|
||||
use mixnet_contract_common::Delegation;
|
||||
|
||||
@@ -22,11 +23,10 @@ pub(crate) fn apply_reward_pool_changes(
|
||||
+ pending_pool_change.added;
|
||||
let staking_supply = rewarding_params.interval.staking_supply
|
||||
+ rewarding_params.interval.staking_supply_scale_factor * pending_pool_change.removed;
|
||||
let epoch_reward_budget = reward_pool
|
||||
/ Decimal::from_atomics(interval.epochs_in_interval(), 0).unwrap()
|
||||
let epoch_reward_budget = reward_pool / interval.epochs_in_interval().into_base_decimal()?
|
||||
* rewarding_params.interval.interval_pool_emission;
|
||||
let stake_saturation_point =
|
||||
staking_supply / Decimal::from_atomics(rewarding_params.rewarded_set_size, 0).unwrap();
|
||||
staking_supply / rewarding_params.rewarded_set_size.into_base_decimal()?;
|
||||
|
||||
rewarding_params.interval.reward_pool = reward_pool;
|
||||
rewarding_params.interval.staking_supply = staking_supply;
|
||||
@@ -46,7 +46,7 @@ pub(crate) fn withdraw_operator_reward(
|
||||
let mix_id = mix_details.mix_id();
|
||||
let mut mix_rewarding = mix_details.rewarding_details;
|
||||
let original_pledge = mix_details.bond_information.original_pledge;
|
||||
let reward = mix_rewarding.withdraw_operator_reward(&original_pledge);
|
||||
let reward = mix_rewarding.withdraw_operator_reward(&original_pledge)?;
|
||||
|
||||
// save updated rewarding info
|
||||
storage::MIXNODE_REWARDING.save(store, mix_id, &mix_rewarding)?;
|
||||
@@ -87,7 +87,7 @@ mod tests {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let epochs_in_interval = test.current_interval().epochs_in_interval();
|
||||
let epochs_in_interval_dec = Decimal::from_atomics(epochs_in_interval, 0).unwrap();
|
||||
let epochs_in_interval_dec = epochs_in_interval.into_base_decimal().unwrap();
|
||||
let start_rewarding_params = test.rewarding_params();
|
||||
|
||||
// nothing changes if pending changes are empty
|
||||
@@ -95,7 +95,7 @@ mod tests {
|
||||
assert_eq!(start_rewarding_params, test.rewarding_params());
|
||||
|
||||
// normal case of having distributed some rewards
|
||||
let distributed_rewards = Decimal::from_atomics(100_000_000u32, 0).unwrap();
|
||||
let distributed_rewards = 100_000_000u32.into_base_decimal().unwrap();
|
||||
storage::PENDING_REWARD_POOL_CHANGE
|
||||
.save(
|
||||
test.deps_mut().storage,
|
||||
@@ -132,7 +132,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
updated_rewarding_params.interval.stake_saturation_point,
|
||||
updated_rewarding_params.interval.staking_supply
|
||||
/ Decimal::from_atomics(updated_rewarding_params.rewarded_set_size, 0).unwrap()
|
||||
/ updated_rewarding_params
|
||||
.rewarded_set_size
|
||||
.into_base_decimal()
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// resets changes back to 0
|
||||
@@ -144,7 +147,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// future case of having to also increase the reward pool
|
||||
let added_credentials = Decimal::from_atomics(50_000_000u32, 0).unwrap();
|
||||
let added_credentials = 50_000_000u32.into_base_decimal().unwrap();
|
||||
storage::PENDING_REWARD_POOL_CHANGE
|
||||
.save(
|
||||
test.deps_mut().storage,
|
||||
@@ -181,7 +184,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
updated_rewarding_params2.interval.stake_saturation_point,
|
||||
updated_rewarding_params2.interval.staking_supply
|
||||
/ Decimal::from_atomics(updated_rewarding_params2.rewarded_set_size, 0).unwrap()
|
||||
/ updated_rewarding_params2
|
||||
.rewarded_set_size
|
||||
.into_base_decimal()
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// resets changes back to 0
|
||||
@@ -198,7 +204,7 @@ mod tests {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
let pledge_dec = Decimal::from_atomics(250_000_000u32, 0).unwrap();
|
||||
let pledge_dec = 250_000_000u32.into_base_decimal().unwrap();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", Some(pledge));
|
||||
|
||||
// no rewards
|
||||
@@ -235,7 +241,7 @@ mod tests {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let delegation_amount = Uint128::new(2500_000_000);
|
||||
let delegation_dec = Decimal::from_atomics(2500_000_000u32, 0).unwrap();
|
||||
let delegation_dec = 2500_000_000u32.into_base_decimal().unwrap();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
let delegator = "delegator";
|
||||
test.add_immediate_delegation(delegator, delegation_amount, mix_id);
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::interval::storage as interval_storage;
|
||||
use crate::mixnodes;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use cosmwasm_std::{coin, Coin, Decimal, Deps, StdResult};
|
||||
use mixnet_contract_common::helpers::into_base_decimal;
|
||||
use mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams};
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward;
|
||||
@@ -19,16 +20,18 @@ pub(crate) fn query_rewarding_params(deps: Deps<'_>) -> StdResult<RewardingParam
|
||||
storage::REWARDING_PARAMS.load(deps.storage)
|
||||
}
|
||||
|
||||
fn pending_operator_reward(mix_details: Option<MixNodeDetails>) -> PendingRewardResponse {
|
||||
match mix_details {
|
||||
fn pending_operator_reward(
|
||||
mix_details: Option<MixNodeDetails>,
|
||||
) -> StdResult<PendingRewardResponse> {
|
||||
Ok(match mix_details {
|
||||
Some(mix_details) => PendingRewardResponse {
|
||||
amount_staked: Some(mix_details.original_pledge().clone()),
|
||||
amount_earned: Some(mix_details.pending_operator_reward()),
|
||||
amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()),
|
||||
amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()?),
|
||||
mixnode_still_fully_bonded: !mix_details.is_unbonding(),
|
||||
},
|
||||
None => PendingRewardResponse::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn query_pending_operator_reward(
|
||||
@@ -39,7 +42,7 @@ pub fn query_pending_operator_reward(
|
||||
// in order to determine operator's reward we need to know its original pledge and thus
|
||||
// we have to load the entire thing
|
||||
let mix_details = mixnodes::helpers::get_mixnode_details_by_owner(deps.storage, owner_address)?;
|
||||
Ok(pending_operator_reward(mix_details))
|
||||
pending_operator_reward(mix_details)
|
||||
}
|
||||
|
||||
pub fn query_pending_mixnode_operator_reward(
|
||||
@@ -49,7 +52,7 @@ pub fn query_pending_mixnode_operator_reward(
|
||||
// in order to determine operator's reward we need to know its original pledge and thus
|
||||
// we have to load the entire thing
|
||||
let mix_details = mixnodes::helpers::get_mixnode_details_by_id(deps.storage, mix_id)?;
|
||||
Ok(pending_operator_reward(mix_details))
|
||||
pending_operator_reward(mix_details)
|
||||
}
|
||||
|
||||
pub fn query_pending_delegator_reward(
|
||||
@@ -74,8 +77,8 @@ pub fn query_pending_delegator_reward(
|
||||
None => return Ok(PendingRewardResponse::default()),
|
||||
};
|
||||
|
||||
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation);
|
||||
let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation);
|
||||
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation)?;
|
||||
let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation)?;
|
||||
|
||||
// check if the mixnode isnt in the process of unbonding (or has already unbonded)
|
||||
let is_bonded = matches!(mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)?, Some(mix_bond) if !mix_bond.is_unbonding);
|
||||
@@ -177,8 +180,8 @@ pub(crate) fn query_estimated_current_epoch_delegator_reward(
|
||||
None => return Ok(EstimatedCurrentEpochRewardResponse::empty_response()),
|
||||
};
|
||||
|
||||
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
|
||||
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
|
||||
let staked_dec = into_base_decimal(delegation.amount.amount)?;
|
||||
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation)?;
|
||||
let amount_staked = delegation.amount;
|
||||
|
||||
// check if the mixnode isnt in the process of unbonding (or has already unbonded)
|
||||
@@ -792,7 +795,10 @@ mod tests {
|
||||
let delegation = test.delegation(mix_id, owner, &None);
|
||||
|
||||
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
|
||||
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
|
||||
let current_value = staked_dec
|
||||
+ mix_rewarding
|
||||
.determine_delegation_reward(&delegation)
|
||||
.unwrap();
|
||||
let amount_staked = delegation.amount;
|
||||
|
||||
EstimatedCurrentEpochRewardResponse {
|
||||
|
||||
@@ -744,7 +744,7 @@ pub mod tests {
|
||||
performance,
|
||||
in_active_set: true,
|
||||
};
|
||||
let sim_res = sim.simulate_epoch_single_node(node_params);
|
||||
let sim_res = sim.simulate_epoch_single_node(node_params).unwrap();
|
||||
assert_eq!(sim_res, dist);
|
||||
}
|
||||
test.skip_to_next_epoch_end();
|
||||
@@ -768,7 +768,7 @@ pub mod tests {
|
||||
performance,
|
||||
in_active_set: true,
|
||||
};
|
||||
let sim_res = sim.simulate_epoch_single_node(node_params);
|
||||
let sim_res = sim.simulate_epoch_single_node(node_params).unwrap();
|
||||
assert_eq!(sim_res, dist);
|
||||
}
|
||||
test.skip_to_next_epoch_end();
|
||||
@@ -809,8 +809,8 @@ pub mod tests {
|
||||
in_active_set: true,
|
||||
};
|
||||
|
||||
let dist1 = sim1.simulate_epoch_single_node(node_params);
|
||||
let dist2 = sim2.simulate_epoch_single_node(node_params);
|
||||
let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap();
|
||||
let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap();
|
||||
|
||||
let env = test.env();
|
||||
|
||||
@@ -858,15 +858,17 @@ pub mod tests {
|
||||
let unit_delegation_base = actual_prior1.unit_delegation;
|
||||
|
||||
// recompute the state of fully compounded delegation from before this rewarding was distributed
|
||||
let pre_rewarding_del11 = del11.dec_amount()
|
||||
+ (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
|
||||
let pre_rewarding_del11 = del11.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del11.cumulative_reward_ratio)
|
||||
* del11.dec_amount().unwrap()
|
||||
/ (del11.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del11_reward =
|
||||
pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
|
||||
|
||||
let pre_rewarding_del12 = del12.dec_amount()
|
||||
+ (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
|
||||
let pre_rewarding_del12 = del12.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del12.cumulative_reward_ratio)
|
||||
* del12.dec_amount().unwrap()
|
||||
/ (del12.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del12_reward =
|
||||
@@ -920,8 +922,9 @@ pub mod tests {
|
||||
let unit_delegation_base = actual_prior2.unit_delegation;
|
||||
|
||||
// recompute the state of fully compounded delegation from before this rewarding was distributed
|
||||
let pre_rewarding_del21 = del21.dec_amount()
|
||||
+ (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
|
||||
let pre_rewarding_del21 = del21.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del21.cumulative_reward_ratio)
|
||||
* del21.dec_amount().unwrap()
|
||||
/ (del21.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del21_reward =
|
||||
@@ -949,8 +952,8 @@ pub mod tests {
|
||||
in_active_set: true,
|
||||
};
|
||||
|
||||
let dist1 = sim1.simulate_epoch_single_node(node_params);
|
||||
let dist2 = sim2.simulate_epoch_single_node(node_params);
|
||||
let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap();
|
||||
let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap();
|
||||
|
||||
let env = test.env();
|
||||
|
||||
@@ -998,22 +1001,25 @@ pub mod tests {
|
||||
let unit_delegation_base = actual_prior1.unit_delegation;
|
||||
|
||||
// recompute the state of fully compounded delegation from before this rewarding was distributed
|
||||
let pre_rewarding_del11 = del11.dec_amount()
|
||||
+ (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
|
||||
let pre_rewarding_del11 = del11.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del11.cumulative_reward_ratio)
|
||||
* del11.dec_amount().unwrap()
|
||||
/ (del11.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del11_reward =
|
||||
pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
|
||||
|
||||
let pre_rewarding_del12 = del12.dec_amount()
|
||||
+ (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
|
||||
let pre_rewarding_del12 = del12.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del12.cumulative_reward_ratio)
|
||||
* del12.dec_amount().unwrap()
|
||||
/ (del12.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del12_reward =
|
||||
pre_rewarding_del12 / prior_delegates1 * delegates_reward1;
|
||||
|
||||
let pre_rewarding_del13 = del13.dec_amount()
|
||||
+ (prior_unit_reward - del13.cumulative_reward_ratio) * del13.dec_amount()
|
||||
let pre_rewarding_del13 = del13.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del13.cumulative_reward_ratio)
|
||||
* del13.dec_amount().unwrap()
|
||||
/ (del13.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del13_reward =
|
||||
@@ -1067,15 +1073,17 @@ pub mod tests {
|
||||
let unit_delegation_base = actual_prior2.unit_delegation;
|
||||
|
||||
// recompute the state of fully compounded delegation from before this rewarding was distributed
|
||||
let pre_rewarding_del21 = del21.dec_amount()
|
||||
+ (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
|
||||
let pre_rewarding_del21 = del21.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del21.cumulative_reward_ratio)
|
||||
* del21.dec_amount().unwrap()
|
||||
/ (del21.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del21_reward =
|
||||
pre_rewarding_del21 / prior_delegates2 * delegates_reward2;
|
||||
|
||||
let pre_rewarding_del23 = del23.dec_amount()
|
||||
+ (prior_unit_reward - del23.cumulative_reward_ratio) * del23.dec_amount()
|
||||
let pre_rewarding_del23 = del23.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del23.cumulative_reward_ratio)
|
||||
* del23.dec_amount().unwrap()
|
||||
/ (del23.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del23_reward =
|
||||
|
||||
@@ -69,6 +69,10 @@ 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())
|
||||
}
|
||||
|
||||
@@ -106,6 +110,10 @@ pub(crate) fn validate_delegation_stake(
|
||||
return Err(MixnetContractError::EmptyDelegation);
|
||||
}
|
||||
|
||||
// throughout this function we've been using the value at `delegation[0]` without problems
|
||||
// (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine,
|
||||
// since it cannot possibly fail without UB
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(delegation.pop().unwrap())
|
||||
}
|
||||
|
||||
|
||||
@@ -589,7 +589,7 @@ pub fn try_get_current_vesting_period(
|
||||
env: Env,
|
||||
) -> Result<Period, ContractError> {
|
||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||
Ok(account.get_current_vesting_period(env.block.time))
|
||||
account.get_current_vesting_period(env.block.time)
|
||||
}
|
||||
|
||||
/// Loads mixnode bond from vesting contract storage.
|
||||
@@ -691,7 +691,7 @@ pub fn try_get_original_vesting(
|
||||
deps: Deps<'_>,
|
||||
) -> Result<OriginalVestingResponse, ContractError> {
|
||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||
Ok(account.get_original_vesting())
|
||||
account.get_original_vesting()
|
||||
}
|
||||
|
||||
/// See [crate::traits::VestingAccount::get_delegated_free]
|
||||
|
||||
@@ -50,4 +50,6 @@ pub enum ContractError {
|
||||
MinVestingFunds { sent: u128, need: u128 },
|
||||
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
|
||||
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
|
||||
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
|
||||
UnpopulatedVestingPeriods { owner: Addr },
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#![allow(rustdoc::private_intra_doc_links)]
|
||||
//! Nym vesting contract, providing vesting accounts with ability to stake unvested tokens
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod contract;
|
||||
mod errors;
|
||||
mod queued_migrations;
|
||||
|
||||
@@ -55,7 +55,7 @@ pub trait VestingAccount {
|
||||
|
||||
/// Returns amount of coins set at account creation
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl
|
||||
fn get_original_vesting(&self) -> OriginalVestingResponse;
|
||||
fn get_original_vesting(&self) -> Result<OriginalVestingResponse, ContractError>;
|
||||
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_delegated_free] for impl
|
||||
fn get_delegated_free(
|
||||
|
||||
@@ -69,7 +69,7 @@ impl Account {
|
||||
pub fn absolute_pledge_cap(&self) -> Result<Uint128, ContractError> {
|
||||
match self.pledge_cap() {
|
||||
PledgeCap::Absolute(cap) => Ok(cap),
|
||||
PledgeCap::Percent(p) => Ok(p * self.get_original_vesting().amount.amount),
|
||||
PledgeCap::Percent(p) => Ok(p * self.get_original_vesting()?.amount.amount),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,13 @@ impl Account {
|
||||
self.periods.len()
|
||||
}
|
||||
|
||||
pub fn period_duration(&self) -> u64 {
|
||||
self.periods.get(0).unwrap().period_seconds
|
||||
pub fn period_duration(&self) -> Result<u64, ContractError> {
|
||||
self.periods
|
||||
.get(0)
|
||||
.ok_or(ContractError::UnpopulatedVestingPeriods {
|
||||
owner: self.owner_address.clone(),
|
||||
})
|
||||
.map(|p| p.period_seconds)
|
||||
}
|
||||
|
||||
pub fn storage_key(&self) -> u32 {
|
||||
@@ -119,11 +124,28 @@ impl Account {
|
||||
|
||||
/// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet.
|
||||
/// In case vesting is over it will always return NUM_VESTING_PERIODS.
|
||||
pub fn get_current_vesting_period(&self, block_time: Timestamp) -> Period {
|
||||
if block_time.seconds() < self.periods.first().unwrap().start_time {
|
||||
Period::Before
|
||||
} else if self.periods.last().unwrap().end_time() < block_time {
|
||||
Period::After
|
||||
pub fn get_current_vesting_period(
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
) -> Result<Period, ContractError> {
|
||||
let first_period =
|
||||
self.periods
|
||||
.first()
|
||||
.ok_or(ContractError::UnpopulatedVestingPeriods {
|
||||
owner: self.owner_address.clone(),
|
||||
})?;
|
||||
|
||||
let last_period = self
|
||||
.periods
|
||||
.last()
|
||||
.ok_or(ContractError::UnpopulatedVestingPeriods {
|
||||
owner: self.owner_address.clone(),
|
||||
})?;
|
||||
|
||||
if block_time.seconds() < first_period.start_time {
|
||||
Ok(Period::Before)
|
||||
} else if last_period.end_time() < block_time {
|
||||
Ok(Period::After)
|
||||
} else {
|
||||
let mut index = 0;
|
||||
for period in &self.periods {
|
||||
@@ -132,7 +154,7 @@ impl Account {
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
Period::In(index)
|
||||
Ok(Period::In(index))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ impl VestingAccount for Account {
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
let period = self.get_current_vesting_period(block_time)?;
|
||||
let denom = MIX_DENOM.load(storage)?;
|
||||
|
||||
let amount = match period {
|
||||
@@ -94,7 +94,7 @@ impl VestingAccount for Account {
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError> {
|
||||
Ok(Coin {
|
||||
amount: self.get_original_vesting().amount().amount
|
||||
amount: self.get_original_vesting()?.amount().amount
|
||||
- self.get_vested_coins(block_time, env, storage)?.amount,
|
||||
denom: MIX_DENOM.load(storage)?,
|
||||
})
|
||||
@@ -108,12 +108,12 @@ impl VestingAccount for Account {
|
||||
self.periods[(self.num_vesting_periods() - 1)].end_time()
|
||||
}
|
||||
|
||||
fn get_original_vesting(&self) -> OriginalVestingResponse {
|
||||
OriginalVestingResponse::new(
|
||||
fn get_original_vesting(&self) -> Result<OriginalVestingResponse, ContractError> {
|
||||
Ok(OriginalVestingResponse::new(
|
||||
self.coin.clone(),
|
||||
self.num_vesting_periods(),
|
||||
self.period_duration(),
|
||||
)
|
||||
self.period_duration()?,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_delegated_free(
|
||||
@@ -123,13 +123,12 @@ impl VestingAccount for Account {
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let period = self.get_current_vesting_period(block_time)?;
|
||||
let withdrawn = self.load_withdrawn(storage)?;
|
||||
let max_available = self
|
||||
.get_vested_coins(Some(block_time), env, storage)?
|
||||
.amount
|
||||
.saturating_sub(withdrawn);
|
||||
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
let start_time = match period {
|
||||
Period::Before => 0,
|
||||
Period::After => u64::MAX,
|
||||
@@ -174,7 +173,7 @@ impl VestingAccount for Account {
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
let period = self.get_current_vesting_period(block_time)?;
|
||||
let max_vested = self.get_vested_coins(Some(block_time), env, storage)?;
|
||||
let start_time = match period {
|
||||
Period::Before => 0,
|
||||
|
||||
@@ -183,12 +183,14 @@ mod tests {
|
||||
|
||||
assert_eq!(account.periods().len(), num_vesting_periods as usize);
|
||||
|
||||
let current_period = account.get_current_vesting_period(Timestamp::from_seconds(0));
|
||||
let current_period = account
|
||||
.get_current_vesting_period(Timestamp::from_seconds(0))
|
||||
.unwrap();
|
||||
assert_eq!(Period::Before, current_period);
|
||||
|
||||
let block_time =
|
||||
Timestamp::from_seconds(account.start_time().seconds() + vesting_period + 1);
|
||||
let current_period = account.get_current_vesting_period(block_time);
|
||||
let current_period = account.get_current_vesting_period(block_time).unwrap();
|
||||
assert_eq!(current_period, Period::In(1));
|
||||
let vested_coins = account
|
||||
.get_vested_coins(Some(block_time), &env, &deps.storage)
|
||||
@@ -199,21 +201,37 @@ mod tests {
|
||||
assert_eq!(
|
||||
vested_coins.amount,
|
||||
Uint128::new(
|
||||
account.get_original_vesting().amount().amount.u128() / num_vesting_periods as u128
|
||||
account
|
||||
.get_original_vesting()
|
||||
.unwrap()
|
||||
.amount()
|
||||
.amount
|
||||
.u128()
|
||||
/ num_vesting_periods as u128
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
vesting_coins.amount,
|
||||
Uint128::new(
|
||||
account.get_original_vesting().amount().amount.u128()
|
||||
- account.get_original_vesting().amount().amount.u128()
|
||||
account
|
||||
.get_original_vesting()
|
||||
.unwrap()
|
||||
.amount()
|
||||
.amount
|
||||
.u128()
|
||||
- account
|
||||
.get_original_vesting()
|
||||
.unwrap()
|
||||
.amount()
|
||||
.amount
|
||||
.u128()
|
||||
/ num_vesting_periods as u128
|
||||
)
|
||||
);
|
||||
|
||||
let block_time =
|
||||
Timestamp::from_seconds(account.start_time().seconds() + 5 * vesting_period + 1);
|
||||
let current_period = account.get_current_vesting_period(block_time);
|
||||
let current_period = account.get_current_vesting_period(block_time).unwrap();
|
||||
assert_eq!(current_period, Period::In(5));
|
||||
let vested_coins = account
|
||||
.get_vested_coins(Some(block_time), &env, &deps.storage)
|
||||
@@ -224,15 +242,30 @@ mod tests {
|
||||
assert_eq!(
|
||||
vested_coins.amount,
|
||||
Uint128::new(
|
||||
5 * account.get_original_vesting().amount().amount.u128()
|
||||
5 * account
|
||||
.get_original_vesting()
|
||||
.unwrap()
|
||||
.amount()
|
||||
.amount
|
||||
.u128()
|
||||
/ num_vesting_periods as u128
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
vesting_coins.amount,
|
||||
Uint128::new(
|
||||
account.get_original_vesting().amount().amount.u128()
|
||||
- 5 * account.get_original_vesting().amount().amount.u128()
|
||||
account
|
||||
.get_original_vesting()
|
||||
.unwrap()
|
||||
.amount()
|
||||
.amount
|
||||
.u128()
|
||||
- 5 * account
|
||||
.get_original_vesting()
|
||||
.unwrap()
|
||||
.amount()
|
||||
.amount
|
||||
.u128()
|
||||
/ num_vesting_periods as u128
|
||||
)
|
||||
);
|
||||
@@ -240,7 +273,7 @@ mod tests {
|
||||
let block_time = Timestamp::from_seconds(
|
||||
account.start_time().seconds() + vesting_over_period * vesting_period + 1,
|
||||
);
|
||||
let current_period = account.get_current_vesting_period(block_time);
|
||||
let current_period = account.get_current_vesting_period(block_time).unwrap();
|
||||
assert_eq!(current_period, Period::After);
|
||||
let vested_coins = account
|
||||
.get_vested_coins(Some(block_time), &env, &deps.storage)
|
||||
@@ -250,7 +283,14 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vested_coins.amount,
|
||||
Uint128::new(account.get_original_vesting().amount().amount.u128())
|
||||
Uint128::new(
|
||||
account
|
||||
.get_original_vesting()
|
||||
.unwrap()
|
||||
.amount()
|
||||
.amount
|
||||
.u128()
|
||||
)
|
||||
);
|
||||
assert_eq!(vesting_coins.amount, Uint128::zero());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# 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 = [""]
|
||||
Generated
+1
@@ -3273,6 +3273,7 @@ dependencies = [
|
||||
"snafu",
|
||||
"socks5-requests",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"topology",
|
||||
"url",
|
||||
|
||||
@@ -18,6 +18,8 @@ pub type StatusReceiver = futures::channel::oneshot::Receiver<Socks5StatusMessag
|
||||
pub enum Socks5StatusMessage {
|
||||
/// The SOCKS5 task successfully stopped
|
||||
Stopped,
|
||||
/// The SOCKS5 task failed to start
|
||||
FailedToStart,
|
||||
}
|
||||
|
||||
/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`.
|
||||
@@ -43,10 +45,18 @@ pub fn start_nym_socks5_client(
|
||||
// The status channel is used to both get the state of the task, and if it's closed, to check
|
||||
// for panic.
|
||||
std::thread::spawn(|| {
|
||||
tokio::runtime::Runtime::new()
|
||||
let result = tokio::runtime::Runtime::new()
|
||||
.expect("Failed to create runtime for SOCKS5 client")
|
||||
.block_on(async move { socks5_client.run_and_listen(socks5_ctrl_rx).await });
|
||||
|
||||
if let Err(err) = result {
|
||||
log::error!("SOCKS5 proxy failed to start: {err}");
|
||||
socks5_status_tx
|
||||
.send(Socks5StatusMessage::FailedToStart)
|
||||
.expect("Failed to send status message back to main task");
|
||||
return;
|
||||
}
|
||||
|
||||
log::info!("SOCKS5 task finished");
|
||||
socks5_status_tx
|
||||
.send(Socks5StatusMessage::Stopped)
|
||||
@@ -69,6 +79,9 @@ pub fn start_disconnect_listener(
|
||||
Ok(Socks5StatusMessage::Stopped) => {
|
||||
log::info!("SOCKS5 task reported it has finished");
|
||||
}
|
||||
Ok(Socks5StatusMessage::FailedToStart) => {
|
||||
log::info!("SOCKS5 task reported it failed to start");
|
||||
}
|
||||
Err(_) => {
|
||||
log::info!("SOCKS5 task appears to have stopped abruptly");
|
||||
// TODO: we should probably generate some events here, or otherwise signal to the
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -160,7 +160,7 @@ mod qa {
|
||||
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
|
||||
|
||||
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n1frq2hzkjtatsupc6jtyaz67ytydk9nya437q92qg76ny3y8fcnjsw806vg";
|
||||
"n1qa4hswlcjmttulj0q9qa46jf64f93pecl6tydcsjldfe0hy5ju0sdmwzya";
|
||||
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
|
||||
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
|
||||
@@ -177,8 +177,8 @@ mod qa {
|
||||
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(
|
||||
"https://adv-epoch-qa-validator.qa.nymte.ch/",
|
||||
Some("https://adv-epoch-qa-val-api.qa.nymte.ch/api"),
|
||||
"https://v2-env-validator.qa.nymte.ch/",
|
||||
Some("https://v2-env-val-api.qa.nymte.ch/api/"),
|
||||
)]
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::allowed_hosts::{HostsStore, OutboundRequestFilter};
|
||||
use crate::connection::Connection;
|
||||
use crate::error::NetworkRequesterError;
|
||||
use crate::statistics::ServiceStatisticsCollector;
|
||||
use crate::websocket;
|
||||
use crate::websocket::TSWebsocketStream;
|
||||
@@ -21,7 +22,6 @@ use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use task::ShutdownListener;
|
||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||
use websocket::WebsocketConnectionError;
|
||||
use websocket_requests::{requests::ClientRequest, responses::ServerResponse};
|
||||
|
||||
// Since it's an atomic, it's safe to be kept static and shared across threads
|
||||
@@ -298,8 +298,8 @@ impl ServiceProvider {
|
||||
}
|
||||
|
||||
/// Start all subsystems
|
||||
pub async fn run(&mut self) {
|
||||
let websocket_stream = self.connect_websocket(&self.listening_address).await;
|
||||
pub async fn run(&mut self) -> Result<(), NetworkRequesterError> {
|
||||
let websocket_stream = self.connect_websocket(&self.listening_address).await?;
|
||||
|
||||
// split the websocket so that we could read and write from separate threads
|
||||
let (websocket_writer, mut websocket_reader) = websocket_stream.split();
|
||||
@@ -352,7 +352,7 @@ impl ServiceProvider {
|
||||
Some(msg) => msg,
|
||||
None => {
|
||||
error!("The websocket stream has finished!");
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -371,14 +371,20 @@ impl ServiceProvider {
|
||||
}
|
||||
|
||||
// Make the websocket connection so we can receive incoming Mixnet messages.
|
||||
async fn connect_websocket(&self, uri: &str) -> TSWebsocketStream {
|
||||
async fn connect_websocket(
|
||||
&self,
|
||||
uri: &str,
|
||||
) -> Result<TSWebsocketStream, NetworkRequesterError> {
|
||||
match websocket::Connection::new(uri).connect().await {
|
||||
Ok(ws_stream) => {
|
||||
info!("* connected to local websocket server at {}", uri);
|
||||
ws_stream
|
||||
Ok(ws_stream)
|
||||
}
|
||||
Err(WebsocketConnectionError::ConnectionNotEstablished) => {
|
||||
panic!("Error: websocket connection attempt failed, is the Nym client running?")
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Error: websocket connection attempt failed, is the Nym client running?"
|
||||
);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::websocket::WebsocketConnectionError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum NetworkRequesterError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("Websocket error")]
|
||||
WebsocketConnectionError(#[from] WebsocketConnectionError),
|
||||
}
|
||||
@@ -7,12 +7,14 @@ use clap::{Args, Parser};
|
||||
use completions::{fig_generate, ArgShell};
|
||||
use logging::setup_logging;
|
||||
|
||||
use error::NetworkRequesterError;
|
||||
use network_defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
|
||||
mod allowed_hosts;
|
||||
mod connection;
|
||||
mod core;
|
||||
mod error;
|
||||
mod statistics;
|
||||
mod websocket;
|
||||
|
||||
@@ -34,7 +36,7 @@ struct Run {
|
||||
}
|
||||
|
||||
impl Run {
|
||||
async fn execute(&self) {
|
||||
async fn execute(&self) -> Result<(), NetworkRequesterError> {
|
||||
if self.open_proxy {
|
||||
println!("\n\nYOU HAVE STARTED IN 'OPEN PROXY' MODE. ANYONE WITH YOUR CLIENT ADDRESS CAN MAKE REQUESTS FROM YOUR MACHINE. PLEASE QUIT IF YOU DON'T UNDERSTAND WHAT YOU'RE DOING.\n\n");
|
||||
}
|
||||
@@ -64,7 +66,7 @@ impl Run {
|
||||
self.enable_statistics,
|
||||
stats_provider_addr,
|
||||
);
|
||||
server.run().await;
|
||||
server.run().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,20 +89,21 @@ struct Cli {
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Cli) {
|
||||
pub(crate) async fn execute(args: Cli) -> Result<(), NetworkRequesterError> {
|
||||
let bin_name = "nym-network-requester";
|
||||
|
||||
match &args.command {
|
||||
Commands::Run(r) => r.execute().await,
|
||||
Commands::Run(r) => r.execute().await?,
|
||||
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> Result<(), NetworkRequesterError> {
|
||||
setup_logging();
|
||||
let args = Cli::parse();
|
||||
|
||||
execute(args).await;
|
||||
execute(args).await
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StatsError {
|
||||
#[error("Reqwuest error {0}")]
|
||||
#[error("Reqwest error {0}")]
|
||||
ReqwestError(#[from] reqwest::Error),
|
||||
|
||||
#[error("Invalid stats provider client address")]
|
||||
|
||||
@@ -22,12 +22,13 @@ impl Connection {
|
||||
pub async fn connect(&self) -> Result<TSWebsocketStream, WebsocketConnectionError> {
|
||||
match connect_async(&self.uri).await {
|
||||
Ok((ws_stream, _)) => Ok(ws_stream),
|
||||
Err(_e) => Err(WebsocketConnectionError::ConnectionNotEstablished),
|
||||
Err(e) => Err(WebsocketConnectionError::ConnectionNotEstablished(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum WebsocketConnectionError {
|
||||
ConnectionNotEstablished,
|
||||
#[error("Connection not established")]
|
||||
ConnectionNotEstablished(tokio_tungstenite::tungstenite::Error),
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -503,7 +503,7 @@ impl PacketPreparer {
|
||||
}
|
||||
|
||||
// convert our hashmap back into a vec
|
||||
let packets = all_gateway_packets.into_iter().map(|(_, v)| v).collect();
|
||||
let packets = all_gateway_packets.into_values().collect();
|
||||
|
||||
PreparedPackets {
|
||||
packets,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
node_modules
|
||||
build
|
||||
coverage
|
||||
dist
|
||||
.cache
|
||||
jest.config.js
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"import/extensions": "off",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }],
|
||||
"import/prefer-default-export": "off",
|
||||
"prettier/prettier": ["error"]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.ts", "**/*.tsx"],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": ["error"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
coverage/
|
||||
.DS_Store
|
||||
.idea/
|
||||
junit.xml
|
||||
+783
File diff suppressed because one or more lines are too long
+40
@@ -0,0 +1,40 @@
|
||||
import ContractCache from "../../../src/endpoints/ContractCache";
|
||||
import ConfigHandler from "../../../src/config/configHandler";
|
||||
|
||||
let contract: ContractCache;
|
||||
let config: ConfigHandler;
|
||||
|
||||
|
||||
describe("Get epoch info", (): void => {
|
||||
beforeAll(async (): Promise<void> => {
|
||||
contract = new ContractCache();
|
||||
config = ConfigHandler.getInstance();
|
||||
});
|
||||
|
||||
it("Get epoch reward params", async (): Promise<void> => {
|
||||
const response = await contract.getEpochRewardParams();
|
||||
expect(typeof response.interval.reward_pool).toBe('string');
|
||||
expect(typeof response.interval.staking_supply_scale_factor).toBe('string');
|
||||
expect(typeof response.interval.staking_supply).toBe('string');
|
||||
expect(typeof response.interval.epoch_reward_budget).toBe('string');
|
||||
expect(typeof response.interval.stake_saturation_point).toBe('string');
|
||||
expect(typeof response.interval.sybil_resistance).toBe('string');
|
||||
expect(typeof response.interval.active_set_work_factor).toBe('string');
|
||||
expect(typeof response.interval.interval_pool_emission).toBe('string');
|
||||
expect(typeof response.active_set_size).toBe('number');
|
||||
expect(typeof response.rewarded_set_size).toBe('number');
|
||||
});
|
||||
|
||||
it("Get current epoch", async (): Promise<void> => {
|
||||
const response = await contract.getCurrentEpoch();
|
||||
expect(typeof response.id).toBe('number');
|
||||
expect(typeof response.epochs_in_interval).toBe('number');
|
||||
expect(typeof response.current_epoch_id).toBe('number');
|
||||
expect(typeof response.current_epoch_start).toBe('string');
|
||||
expect(typeof response.epoch_length.secs).toBe('number');
|
||||
expect(typeof response.epoch_length.nanos).toBe('number');
|
||||
expect(typeof response.total_elapsed_epochs).toBe('number');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import ContractCache from "../../../../src/endpoints/ContractCache";
|
||||
import ConfigHandler from "../../../../src/config/configHandler";
|
||||
|
||||
let contract: ContractCache;
|
||||
let config: ConfigHandler;
|
||||
|
||||
|
||||
describe("Get gateway data", (): void => {
|
||||
beforeAll(async (): Promise<void> => {
|
||||
contract = new ContractCache();
|
||||
config = ConfigHandler.getInstance();
|
||||
});
|
||||
|
||||
it("Get all gateways", async (): Promise<void> => {
|
||||
const response = await contract.getGateways();
|
||||
response.forEach((gateway) => {
|
||||
//overview
|
||||
expect(typeof gateway.owner).toBe('string');
|
||||
expect(typeof gateway.block_height).toBe('number');
|
||||
|
||||
if (gateway.proxy === null) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
expect(typeof gateway.proxy).toBe('string');
|
||||
}
|
||||
|
||||
//pledge_amount
|
||||
expect(typeof gateway.pledge_amount.denom).toBe('string');
|
||||
expect(typeof gateway.pledge_amount.amount).toBe('string');
|
||||
|
||||
//gateway
|
||||
expect(typeof gateway.gateway.host).toBe('string');
|
||||
expect(typeof gateway.gateway.mix_port).toBe('number');
|
||||
expect(typeof gateway.gateway.clients_port).toBe('number');
|
||||
expect(typeof gateway.gateway.location).toBe('string');
|
||||
expect(typeof gateway.gateway.sphinx_key).toBe('string');
|
||||
expect(typeof gateway.gateway.identity_key).toBe('string');
|
||||
expect(typeof gateway.gateway.version).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
it("Get blacklisted gateways", async (): Promise<void> => {
|
||||
const response = await contract.getBlacklistedGateways();
|
||||
response.forEach(function (value) {
|
||||
expect(typeof value).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import ContractCache from "../../../../src/endpoints/ContractCache";
|
||||
import ConfigHandler from "../../../../src/config/configHandler";
|
||||
|
||||
let contract: ContractCache;
|
||||
let config: ConfigHandler;
|
||||
|
||||
|
||||
describe("Get mixnode data", (): void => {
|
||||
beforeAll(async (): Promise<void> => {
|
||||
contract = new ContractCache();
|
||||
config = ConfigHandler.getInstance();
|
||||
});
|
||||
|
||||
it("Get all mixnodes", async (): Promise<void> => {
|
||||
const response = await contract.getMixnodes();
|
||||
|
||||
response.forEach((mixnode) => {
|
||||
//bond information overview
|
||||
expect(typeof mixnode.bond_information.mix_id).toBe('number');
|
||||
expect(typeof mixnode.bond_information.owner).toBe('string');
|
||||
expect(typeof mixnode.bond_information.original_pledge.amount).toBe('string');
|
||||
expect(typeof mixnode.bond_information.original_pledge.denom).toBe('string');
|
||||
expect(typeof mixnode.bond_information.layer).toBe('number');
|
||||
expect(typeof mixnode.bond_information.bonding_height).toBe('number');
|
||||
expect(typeof mixnode.bond_information.is_unbonding).toBe('boolean');
|
||||
|
||||
if (mixnode.bond_information.proxy === null) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
expect(typeof mixnode.bond_information.proxy).toBe('string');
|
||||
}
|
||||
|
||||
//mixnode
|
||||
expect(typeof mixnode.bond_information.mix_node.host).toBe('string')
|
||||
expect(mixnode.bond_information.mix_node.http_api_port).toStrictEqual(8000);
|
||||
expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe('number')
|
||||
expect(typeof mixnode.bond_information.mix_node.mix_port).toBe('number')
|
||||
expect(mixnode.bond_information.mix_node.mix_port).toStrictEqual(1789);
|
||||
expect(mixnode.bond_information.mix_node.verloc_port).toStrictEqual(1790)
|
||||
|
||||
let identitykey = mixnode.bond_information.mix_node.identity_key
|
||||
if (typeof identitykey === 'string') {
|
||||
if (identitykey.length === 43) {
|
||||
return true
|
||||
}
|
||||
else expect(identitykey).toHaveLength(44);
|
||||
}
|
||||
|
||||
let sphinx = mixnode.bond_information.mix_node.sphinx_key
|
||||
if (typeof sphinx === 'string') {
|
||||
if (sphinx.length === 43) {
|
||||
return true
|
||||
}
|
||||
else expect(sphinx).toHaveLength(44);
|
||||
}
|
||||
|
||||
//rewarding details
|
||||
expect(typeof mixnode.rewarding_details.cost_params.profit_margin_percent).toBe('string')
|
||||
expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.denom).toBe('string')
|
||||
expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.amount).toBe('string')
|
||||
expect(typeof mixnode.rewarding_details.operator).toBe('string')
|
||||
expect(typeof mixnode.rewarding_details.delegates).toBe('string')
|
||||
expect(typeof mixnode.rewarding_details.total_unit_reward).toBe('string')
|
||||
expect(typeof mixnode.rewarding_details.unit_delegation).toBe('string')
|
||||
expect(typeof mixnode.rewarding_details.last_rewarded_epoch).toBe('number')
|
||||
expect(typeof mixnode.rewarding_details.unique_delegations).toBe('number')
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it("Get all mixnodes detailed", async (): Promise<void> => {
|
||||
const response = await contract.getMixnodesDetailed();
|
||||
response.forEach((mixnode) => {
|
||||
// overview details
|
||||
expect(typeof mixnode.estimated_delegators_apy).toBe('string');
|
||||
expect(typeof mixnode.estimated_operator_apy).toBe('string');
|
||||
expect(typeof mixnode.performance).toBe('string');
|
||||
expect(typeof mixnode.uncapped_stake_saturation).toBe('string');
|
||||
expect(typeof mixnode.stake_saturation).toBe('string');
|
||||
|
||||
//mixnode details bond info
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_id).toBe('number')
|
||||
expect(typeof mixnode.mixnode_details.bond_information.owner).toBe('string');
|
||||
expect(typeof mixnode.mixnode_details.bond_information.original_pledge.amount).toBe('string');
|
||||
expect(typeof mixnode.mixnode_details.bond_information.original_pledge.denom).toBe('string');
|
||||
expect(typeof mixnode.mixnode_details.bond_information.layer).toBe('number');
|
||||
expect(typeof mixnode.mixnode_details.bond_information.bonding_height).toBe('number');
|
||||
expect(typeof mixnode.mixnode_details.bond_information.is_unbonding).toBe('boolean');
|
||||
|
||||
if (mixnode.mixnode_details.bond_information.proxy === null) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
expect(typeof mixnode.mixnode_details.bond_information.proxy).toBe('string');
|
||||
}
|
||||
|
||||
//mixnode
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_node.host).toBe('string')
|
||||
expect(mixnode.mixnode_details.bond_information.mix_node.http_api_port).toStrictEqual(8000);
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port).toBe('number')
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_node.mix_port).toBe('number')
|
||||
expect(mixnode.mixnode_details.bond_information.mix_node.mix_port).toStrictEqual(1789);
|
||||
expect(mixnode.mixnode_details.bond_information.mix_node.verloc_port).toStrictEqual(1790)
|
||||
|
||||
let identitykey2 = mixnode.mixnode_details.bond_information.mix_node.identity_key
|
||||
if (typeof identitykey2 === 'string') {
|
||||
if (identitykey2.length === 43) {
|
||||
return true
|
||||
}
|
||||
else expect(identitykey2).toHaveLength(44);
|
||||
}
|
||||
|
||||
let sphinx2 = mixnode.mixnode_details.bond_information.mix_node.sphinx_key
|
||||
if (typeof sphinx2 === 'string') {
|
||||
if (sphinx2.length === 43) {
|
||||
return true
|
||||
}
|
||||
else expect(sphinx2).toHaveLength(44);
|
||||
}
|
||||
|
||||
//mixnode rewarding info
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBe('string')
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.denom).toBe('string')
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.amount).toBe('string')
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.operator).toBe('string')
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.delegates).toBe('string')
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.total_unit_reward).toBe('string')
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.unit_delegation).toBe('string')
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBe('number')
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.unique_delegations).toBe('number')
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it("Get active mixnodes", async (): Promise<void> => {
|
||||
const response = await contract.getActiveMixnodes();
|
||||
response.forEach(function (mixnode) {
|
||||
expect(mixnode.rewarding_details.cost_params.profit_margin_percent).toBeTruthy()
|
||||
});
|
||||
});
|
||||
|
||||
it("Get active mixnodes detailed", async (): Promise<void> => {
|
||||
const response = await contract.getActiveMixnodesDetailed();
|
||||
response.forEach(function (mixnode) {
|
||||
expect(mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBeTruthy()
|
||||
});
|
||||
});
|
||||
|
||||
it("Get rewarded mixnodes", async (): Promise<void> => {
|
||||
const response = await contract.getRewardedMixnodes();
|
||||
response.forEach(function (mixnode) {
|
||||
expect(mixnode.rewarding_details.last_rewarded_epoch).toBeTruthy()
|
||||
});
|
||||
});
|
||||
|
||||
it("Get rewarded mixnodes detailed", async (): Promise<void> => {
|
||||
const response = await contract.getRewardedMixnodesDetailed();
|
||||
response.forEach(function (mixnode) {
|
||||
expect(mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBeTruthy()
|
||||
});
|
||||
});
|
||||
|
||||
it("Get blacklisted mixnodes", async (): Promise<void> => {
|
||||
const response = await contract.getBlacklistedMixnodes();
|
||||
response.forEach(function (value) {
|
||||
expect(typeof value).toBe('number');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import Status from "../../../src/endpoints/Status";
|
||||
import ConfigHandler from "../../../src/config/configHandler";
|
||||
|
||||
let status: Status;
|
||||
let config: ConfigHandler;
|
||||
|
||||
describe("Get gateway data", (): void => {
|
||||
beforeAll(async (): Promise<void> => {
|
||||
status = new Status();
|
||||
config = ConfigHandler.getInstance();
|
||||
});
|
||||
|
||||
it("Get a gateway history", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.gateway_identity;
|
||||
const response = await status.getGatewayHistory(identity_key);
|
||||
|
||||
response.history.forEach((x) => {
|
||||
expect(typeof x.date).toBe("string");
|
||||
expect(typeof x.uptime).toBe("number");
|
||||
});
|
||||
|
||||
expect(identity_key).toStrictEqual(response.identity);
|
||||
expect(typeof response.owner).toBe("string");
|
||||
});
|
||||
|
||||
it("Get gateway core status count", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.gateway_identity;
|
||||
const response = await status.getGatewayCoreCount(identity_key);
|
||||
|
||||
expect(identity_key).toStrictEqual(response.identity);
|
||||
expect(typeof response.count).toBe("number");
|
||||
});
|
||||
|
||||
it("Get a gateway status report", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.gateway_identity;
|
||||
const response = await status.getGatewayStatusReport(identity_key);
|
||||
|
||||
expect(identity_key).toStrictEqual(response.identity);
|
||||
expect(typeof response.owner).toBe("string");
|
||||
expect(typeof response.most_recent).toBe("number");
|
||||
expect(typeof response.last_hour).toBe("number");
|
||||
expect(typeof response.last_day).toBe("number");
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -11,113 +11,158 @@ describe("Get mixnode data", (): void => {
|
||||
});
|
||||
|
||||
it("Get a mixnode stake saturation", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.mixnode_identity;
|
||||
const response = await status.getMixnodeStakeSaturation(identity_key);
|
||||
|
||||
console.log(response.as_at);
|
||||
console.log(response.saturation);
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeStakeSaturation(mix_id);
|
||||
|
||||
expect(typeof response.as_at).toBe("number");
|
||||
expect(typeof response.saturation).toBe("number");
|
||||
expect(typeof response.saturation).toBe("string");
|
||||
expect(typeof response.uncapped_saturation).toBe("string");
|
||||
});
|
||||
|
||||
it("Get a mixnode status report", async (): Promise<void> => {
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeStatusReport(mix_id);
|
||||
|
||||
expect(mix_id).toStrictEqual(response.mix_id);
|
||||
expect(typeof response.owner).toBe("string");
|
||||
expect(typeof response.most_recent).toBe("number");
|
||||
expect(typeof response.last_hour).toBe("number");
|
||||
expect(typeof response.last_day).toBe("number");
|
||||
});
|
||||
|
||||
it("Get a mixnode average uptime", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.mixnode_identity;
|
||||
const response = await status.getMixnodeAverageUptime(identity_key);
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeAverageUptime(mix_id);
|
||||
|
||||
console.log(response.avg_uptime);
|
||||
console.log(response.identity);
|
||||
|
||||
expect(identity_key).toStrictEqual(response.identity);
|
||||
expect(mix_id).toStrictEqual(response.mix_id);
|
||||
expect(typeof response.avg_uptime).toBe("number");
|
||||
});
|
||||
|
||||
|
||||
it("Get a mixnode history", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.mixnode_identity;
|
||||
const response = await status.getMixnodeHistory(identity_key);
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeHistory(mix_id);
|
||||
|
||||
response.history.forEach((x) => {
|
||||
console.log(x.date);
|
||||
console.log(x.uptime);
|
||||
});
|
||||
console.log(response.identity);
|
||||
console.log(response.owner);
|
||||
|
||||
expect(identity_key).toStrictEqual(response.identity);
|
||||
expect(typeof x.date).toBe("string");
|
||||
expect(typeof x.uptime).toBe("number");
|
||||
});
|
||||
|
||||
expect(mix_id).toStrictEqual(response.mix_id);
|
||||
expect(typeof response.owner).toBe("string");
|
||||
});
|
||||
|
||||
it("Get a gateway history", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.gateway_identity;
|
||||
const response = await status.getGatewayHistory(identity_key);
|
||||
it("Get mixnode core status count", async (): Promise<void> => {
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeCoreCount(mix_id);
|
||||
|
||||
response.history.forEach((x) => {
|
||||
console.log(x.date);
|
||||
console.log(x.uptime);
|
||||
});
|
||||
console.log(response.identity);
|
||||
console.log(response.owner);
|
||||
|
||||
expect(identity_key).toStrictEqual(response.identity);
|
||||
expect(typeof response.owner).toBe("string");
|
||||
});
|
||||
|
||||
it("Get a gateway history", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.gateway_identity;
|
||||
const response = await status.getGatewayCoreCount(identity_key);
|
||||
|
||||
console.log(response.count);
|
||||
console.log(response.identity);
|
||||
|
||||
expect(identity_key).toStrictEqual(response.identity);
|
||||
expect(typeof response.count).toBe("number");
|
||||
});
|
||||
|
||||
it("Get a gateway history", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.mixnode_identity;
|
||||
const response = await status.getMixnodeCoreCount(identity_key);
|
||||
|
||||
console.log(response.count);
|
||||
console.log(response.identity);
|
||||
|
||||
expect(identity_key).toStrictEqual(response.identity);
|
||||
expect(mix_id).toStrictEqual(response.mix_id);
|
||||
expect(typeof response.count).toBe("number");
|
||||
});
|
||||
|
||||
it("Get a mixnode status", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.mixnode_identity;
|
||||
const response = await status.getMixnodeStatus(identity_key);
|
||||
|
||||
console.log(response.status);
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeStatus(mix_id);
|
||||
|
||||
expect(response.status).toStrictEqual("active");
|
||||
});
|
||||
|
||||
it("Get a mixnode reward estimation", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.mixnode_identity;
|
||||
const response = await status.getMixnodeRewardComputation(identity_key);
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeRewardComputation(mix_id);
|
||||
|
||||
//estimation
|
||||
expect(typeof response.estimation.total_node_reward).toBe("string");
|
||||
expect(typeof response.estimation.operator).toBe("string");
|
||||
expect(typeof response.estimation.delegates).toBe("string");
|
||||
expect(typeof response.estimation.operating_cost).toBe("string");
|
||||
|
||||
//reward_params
|
||||
expect(typeof response.reward_params.interval.reward_pool).toBe("string");
|
||||
expect(typeof response.reward_params.interval.staking_supply).toBe("string");
|
||||
expect(typeof response.reward_params.interval.staking_supply_scale_factor).toBe("string");
|
||||
expect(typeof response.reward_params.interval.epoch_reward_budget).toBe("string");
|
||||
expect(typeof response.reward_params.interval.stake_saturation_point).toBe("string");
|
||||
expect(typeof response.reward_params.interval.sybil_resistance).toBe("string");
|
||||
expect(typeof response.reward_params.interval.active_set_work_factor).toBe("string");
|
||||
expect(typeof response.reward_params.interval.interval_pool_emission).toBe("string");
|
||||
expect(typeof response.reward_params.rewarded_set_size).toBe("number");
|
||||
expect(typeof response.reward_params.active_set_size).toBe("number");
|
||||
|
||||
//epoch
|
||||
expect(typeof response.epoch.id).toBe("number");
|
||||
expect(typeof response.epoch.epochs_in_interval).toBe("number");
|
||||
expect(typeof response.epoch.current_epoch_start).toBe("string");
|
||||
expect(typeof response.epoch.current_epoch_id).toBe("number");
|
||||
expect(typeof response.epoch.epoch_length.secs).toBe("number");
|
||||
expect(typeof response.epoch.epoch_length.nanos).toBe("number");
|
||||
expect(typeof response.epoch.total_elapsed_epochs).toBe("number");
|
||||
|
||||
expect(typeof response.as_at).toBe("number");
|
||||
|
||||
console.log(response.estimated_delegators_reward);
|
||||
console.log(response.estimated_node_profit);
|
||||
console.log(response.estimated_operator_cost);
|
||||
console.log(response.estimated_operator_reward);
|
||||
console.log(response.estimated_total_node_reward);
|
||||
console.log(response.reward_params);
|
||||
console.log(response.as_at);
|
||||
console.log(response);
|
||||
|
||||
//assertions to come
|
||||
//expect(response).toStrictEqual('something');
|
||||
});
|
||||
|
||||
it("Get a mixnode inclusion probability", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.mixnode_identity;
|
||||
const response = await status.getMixnodeInclusionProbability(identity_key);
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeInclusionProbability(mix_id);
|
||||
|
||||
console.log(response.in_active);
|
||||
console.log(response.in_reserve);
|
||||
|
||||
//assertions to come
|
||||
//expect(response).toStrictEqual('something');
|
||||
expect(typeof response.in_active).toBe("string");
|
||||
expect(typeof response.in_reserve).toBe("string");
|
||||
});
|
||||
|
||||
it("Post to compute mixnode reward estimation", async ():Promise<void> => {
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const payload = {"performance": "0.2"}
|
||||
const response = await status.getMixnodeRewardEstimatedComputation(mix_id, payload);
|
||||
|
||||
//estimation
|
||||
expect(typeof response.estimation.total_node_reward).toBe("string");
|
||||
expect(typeof response.estimation.operator).toBe("string");
|
||||
expect(typeof response.estimation.delegates).toBe("string");
|
||||
expect(typeof response.estimation.operating_cost).toBe("string");
|
||||
|
||||
//reward_params
|
||||
expect(typeof response.reward_params.interval.reward_pool).toBe("string");
|
||||
expect(typeof response.reward_params.interval.staking_supply).toBe("string");
|
||||
expect(typeof response.reward_params.interval.staking_supply_scale_factor).toBe("string");
|
||||
expect(typeof response.reward_params.interval.epoch_reward_budget).toBe("string");
|
||||
expect(typeof response.reward_params.interval.stake_saturation_point).toBe("string");
|
||||
expect(typeof response.reward_params.interval.sybil_resistance).toBe("string");
|
||||
expect(typeof response.reward_params.interval.active_set_work_factor).toBe("string");
|
||||
expect(typeof response.reward_params.interval.interval_pool_emission).toBe("string");
|
||||
expect(typeof response.reward_params.rewarded_set_size).toBe("number");
|
||||
expect(typeof response.reward_params.active_set_size).toBe("number");
|
||||
|
||||
//epoch
|
||||
expect(typeof response.epoch.id).toBe("number");
|
||||
expect(typeof response.epoch.epochs_in_interval).toBe("number");
|
||||
expect(typeof response.epoch.current_epoch_start).toBe("string");
|
||||
expect(typeof response.epoch.current_epoch_id).toBe("number");
|
||||
expect(typeof response.epoch.epoch_length.secs).toBe("number");
|
||||
expect(typeof response.epoch.epoch_length.nanos).toBe("number");
|
||||
expect(typeof response.epoch.total_elapsed_epochs).toBe("number");
|
||||
|
||||
expect(typeof response.as_at).toBe("number");
|
||||
})
|
||||
|
||||
|
||||
it.skip("Post to compute mixnode reward estimation", async ():Promise<void> => {
|
||||
const mix_id = config.environmnetConfig.mix_id;
|
||||
const payload = {"performance": "0.7"}
|
||||
const response = await status.getMixnodeRewardEstimatedComputation(mix_id, payload);
|
||||
|
||||
// TO-DO this test needs calculations to ensure than when passing through different performance values, the reward is also changing as expected
|
||||
expect(response.estimation.total_node_reward).toContain("986360");
|
||||
|
||||
})
|
||||
|
||||
it("Get mixnode history using identity key", async (): Promise<void> => {
|
||||
const identity_key = config.environmnetConfig.identity_key;
|
||||
const response = await status.getMixnodeHistoryWrong(identity_key);
|
||||
expect(response).toStrictEqual(404)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -3,13 +3,14 @@ common:
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
qa:
|
||||
api_base_url: https://qa-validator-api.nymtech.net/api/v1
|
||||
mixnode_identity: DLdMKLPywEy1vnu3yPrtXvzY7fw1puiiHpA9n9UQatiQ
|
||||
gateway_identity: CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM
|
||||
log_level: debug
|
||||
api_base_url: https://qwerty-validator-api.qa.nymte.ch/api/v1
|
||||
mix_id: 7
|
||||
identity_key: 4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz
|
||||
gateway_identity: 336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9
|
||||
log_level: error
|
||||
prod:
|
||||
api_base_url: https://qa-validator-api.nymtech.net/api/v1
|
||||
mixnode_identity: DLdMKLPywEy1vnu3yPrtXvzY7fw1puiiHpA9n9UQatiQ
|
||||
gateway_identity: CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM
|
||||
log_level: debug
|
||||
log_level: error
|
||||
time_zone: utc
|
||||
|
||||
@@ -16,13 +16,14 @@ class ConfigHandler {
|
||||
log_level: TLogLevelName;
|
||||
time_zone: string;
|
||||
api_base_url: string;
|
||||
mixnode_identity: string;
|
||||
mix_id: number;
|
||||
identity_key: string;
|
||||
gateway_identity: string;
|
||||
};
|
||||
|
||||
private constructor() {
|
||||
this.setCommonConfig();
|
||||
this.setEnvironmentConfig(process.env.TEST_ENV || "prod");
|
||||
this.setEnvironmentConfig(process.env.TEST_ENV || "qa");
|
||||
}
|
||||
|
||||
public static getInstance(): ConfigHandler {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
MixnodesDetailed,
|
||||
AllGateways,
|
||||
AllMixnodes,
|
||||
EpochRewardParams,
|
||||
BlacklistedGateways,
|
||||
BlacklistedMixnodes,
|
||||
CurrentEpoch,
|
||||
Mixnode
|
||||
} from "../types/ContractCacheTypes";
|
||||
import { APIClient } from "./abstracts/APIClient";
|
||||
|
||||
export default class ContractCache extends APIClient {
|
||||
constructor() {
|
||||
super("/");
|
||||
}
|
||||
|
||||
public async getMixnodes(): Promise<AllMixnodes[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `mixnodes`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodesDetailed(): Promise<MixnodesDetailed[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `mixnodes/detailed`,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getGateways(): Promise<AllGateways[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `gateways`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getActiveMixnodes(): Promise<AllMixnodes[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `mixnodes/active`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getActiveMixnodesDetailed(): Promise<MixnodesDetailed[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `mixnodes/active/detailed`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getRewardedMixnodes(): Promise<AllMixnodes[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `mixnodes/rewarded`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getRewardedMixnodesDetailed(): Promise<MixnodesDetailed[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `mixnodes/rewarded/detailed`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getBlacklistedMixnodes(): Promise<BlacklistedMixnodes[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `mixnodes/blacklisted`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getBlacklistedGateways(): Promise<BlacklistedGateways[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `gateways/blacklisted`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getEpochRewardParams(): Promise<EpochRewardParams> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `epoch/reward_params`
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getCurrentEpoch(): Promise<CurrentEpoch> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `epoch/current`
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { AxiosResponse } from "axios";
|
||||
import {
|
||||
ActiveStatus,
|
||||
AvgUptime,
|
||||
CoreCount,
|
||||
GatewayCoreCount,
|
||||
EstimatedReward,
|
||||
RewardEstimation,
|
||||
InclusionProbability,
|
||||
NodeHistory,
|
||||
Report,
|
||||
StakeSaturation,
|
||||
} from "../../src/interfaces/StatusInterfaces";
|
||||
} from "../types/StatusTypes";
|
||||
import { APIClient } from "./abstracts/APIClient";
|
||||
|
||||
export default class Status extends APIClient {
|
||||
@@ -16,18 +17,12 @@ export default class Status extends APIClient {
|
||||
super("/status");
|
||||
}
|
||||
|
||||
public async getMixnodeStatusReport(identity_key: string): Promise<Report> {
|
||||
public async getMixnodeStatusReport(mix_id: number): Promise<Report> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${identity_key}/report`,
|
||||
route: `/mixnode/${mix_id}/report`,
|
||||
});
|
||||
|
||||
return <Report>{
|
||||
identity: response.data.identity,
|
||||
owner: response.data.owner,
|
||||
most_recent: response.data.most_recent,
|
||||
last_hour: response.data.last_hour,
|
||||
last_day: response.data.last_day,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getGatewayStatusReport(identity_key: string): Promise<Report> {
|
||||
@@ -35,13 +30,7 @@ export default class Status extends APIClient {
|
||||
route: `/gateway/${identity_key}/report`,
|
||||
});
|
||||
|
||||
return <Report>{
|
||||
identity: response.data.identity,
|
||||
owner: response.data.owner,
|
||||
most_recent: response.data.most_recent,
|
||||
last_hour: response.data.last_hour,
|
||||
last_day: response.data.last_day,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getGatewayHistory(identity_key: string): Promise<NodeHistory> {
|
||||
@@ -49,129 +38,99 @@ export default class Status extends APIClient {
|
||||
route: `/gateway/${identity_key}/history`,
|
||||
});
|
||||
|
||||
return <NodeHistory>{
|
||||
identity: response.data.identity,
|
||||
owner: response.data.owner,
|
||||
history: response.data.history,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodeHistory(mix_id: number): Promise<NodeHistory> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${mix_id}/history`,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodeStakeSaturation(
|
||||
identity_key: string
|
||||
mix_id: number
|
||||
): Promise<StakeSaturation> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${identity_key}/stake-saturation`,
|
||||
route: `/mixnode/${mix_id}/stake-saturation`,
|
||||
});
|
||||
|
||||
return <StakeSaturation>{
|
||||
as_at: response.data.as_at,
|
||||
saturation: response.data.saturation,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodeCoreCount(identity_key: string): Promise<CoreCount> {
|
||||
public async getMixnodeCoreCount(mix_id: number): Promise<CoreCount> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${identity_key}/core-status-count`,
|
||||
route: `/mixnode/${mix_id}/core-status-count`,
|
||||
});
|
||||
|
||||
return <CoreCount>{
|
||||
identity: response.data.identity,
|
||||
count: response.data.count,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getGatewayCoreCount(identity_key: string): Promise<CoreCount> {
|
||||
public async getGatewayCoreCount(identity_key: string): Promise<GatewayCoreCount> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/gateway/${identity_key}/core-status-count`,
|
||||
});
|
||||
|
||||
return <CoreCount>{
|
||||
identity: response.data.identity,
|
||||
count: response.data.count,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodeRewardComputation(
|
||||
identity_key: string
|
||||
): Promise<EstimatedReward> {
|
||||
mix_id: number
|
||||
): Promise<RewardEstimation> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${identity_key}/reward-estimation`,
|
||||
route: `/mixnode/${mix_id}/reward-estimation`,
|
||||
});
|
||||
|
||||
return <EstimatedReward>{
|
||||
estimated_total_node_reward: response.data.estimated_total_node_reward,
|
||||
estimated_operator_reward: response.data.estimated_operator_reward,
|
||||
estimated_delegators_reward: response.data.estimated_delegators_reward,
|
||||
estimated_node_profit: response.data.estimated_node_profit,
|
||||
estimated_operator_cost: response.data.estimated_operator_cost,
|
||||
reward_params: response.data.reward_params,
|
||||
as_at: response.data.as_at,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodeRewardEstimatedComputation(
|
||||
identity_key: string
|
||||
): Promise<EstimatedReward> {
|
||||
mix_id: number,
|
||||
payload: object
|
||||
): Promise<RewardEstimation> {
|
||||
const response = await this.restClient.sendPost({
|
||||
route: `/mixnode/${identity_key}/compute-reward-estimation`,
|
||||
route: `/mixnode/${mix_id}/compute-reward-estimation`,
|
||||
data: payload
|
||||
});
|
||||
|
||||
return <EstimatedReward>{
|
||||
estimated_total_node_reward: response.data.estimated_total_node_reward,
|
||||
estimated_operator_reward: response.data.estimated_operator_reward,
|
||||
estimated_delegators_reward: response.data.estimated_delegators_reward,
|
||||
estimated_node_profit: response.data.estimated_node_profit,
|
||||
estimated_operator_cost: response.data.estimated_operator_cost,
|
||||
reward_params: response.data.reward_params,
|
||||
as_at: response.data.as_at,
|
||||
};
|
||||
}
|
||||
|
||||
public async getMixnodeHistory(identity_key: string): Promise<NodeHistory> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${identity_key}/history`,
|
||||
});
|
||||
|
||||
return <NodeHistory>{
|
||||
identity: response.data.identity,
|
||||
owner: response.data.owner,
|
||||
history: response.data.history,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodeAverageUptime(
|
||||
identity_key: string
|
||||
mix_id: number
|
||||
): Promise<AvgUptime> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${identity_key}/avg_uptime`,
|
||||
route: `/mixnode/${mix_id}/avg_uptime`,
|
||||
});
|
||||
|
||||
return <AvgUptime>{
|
||||
identity: response.data.identity,
|
||||
avg_uptime: response.data.avg_uptime,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodeInclusionProbability(
|
||||
identity_key: string
|
||||
mix_id: number
|
||||
): Promise<InclusionProbability> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${identity_key}/inclusion-probability`,
|
||||
route: `/mixnode/${mix_id}/inclusion-probability`,
|
||||
});
|
||||
|
||||
return <InclusionProbability>{
|
||||
in_active: response.data.in_active,
|
||||
in_reserve: response.data.in_reserve,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMixnodeStatus(identity_key: string): Promise<ActiveStatus> {
|
||||
public async getMixnodeHistoryWrong(
|
||||
identity_key: string
|
||||
): Promise<NodeHistory> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${identity_key}/status`,
|
||||
route: `/status/mixnode/${identity_key}/history`,
|
||||
});
|
||||
return response.status;
|
||||
}
|
||||
|
||||
public async getMixnodeStatus(mix_id: number): Promise<ActiveStatus> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnode/${mix_id}/status`,
|
||||
});
|
||||
|
||||
return <ActiveStatus>{
|
||||
status: response.data.status,
|
||||
};
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
export type Epoch = {
|
||||
epoch_reward_pool: string;
|
||||
rewarded_set_size: string;
|
||||
active_set_size: string;
|
||||
staking_supply: string;
|
||||
sybil_resistance_percent: number;
|
||||
active_set_work_factor: number;
|
||||
};
|
||||
|
||||
export type Node = {
|
||||
reward_blockstamp: number;
|
||||
uptime: string;
|
||||
in_active_set: boolean;
|
||||
};
|
||||
|
||||
export type RewardParams = {
|
||||
epoch: Epoch;
|
||||
node: Node;
|
||||
};
|
||||
|
||||
export type EstimatedReward = {
|
||||
estimated_total_node_reward: number;
|
||||
estimated_operator_reward: number;
|
||||
estimated_delegators_reward: number;
|
||||
estimated_node_profit: number;
|
||||
estimated_operator_cost: number;
|
||||
reward_params: RewardParams;
|
||||
as_at: number;
|
||||
};
|
||||
|
||||
export type StakeSaturation = {
|
||||
saturation: number;
|
||||
as_at: number;
|
||||
};
|
||||
|
||||
export type AvgUptime = {
|
||||
identity: string;
|
||||
avg_uptime: number;
|
||||
};
|
||||
|
||||
export type InclusionProbability = {
|
||||
in_active: string;
|
||||
in_reserve: string;
|
||||
};
|
||||
|
||||
export type Report = {
|
||||
identity: string;
|
||||
owner: string;
|
||||
most_recent: number;
|
||||
last_hour: number;
|
||||
last_day: number;
|
||||
};
|
||||
|
||||
export type History = {
|
||||
date: string;
|
||||
uptime: number;
|
||||
};
|
||||
|
||||
export type NodeHistory = {
|
||||
identity: string;
|
||||
owner: string;
|
||||
history: History[];
|
||||
};
|
||||
|
||||
export type CoreCount = {
|
||||
identity: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type ActiveStatus = {
|
||||
status: string;
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
export type AllMixnodes = {
|
||||
bond_information: BondInformation;
|
||||
rewarding_details: RewardingDetails;
|
||||
};
|
||||
|
||||
export type BondInformation = {
|
||||
mix_id: number;
|
||||
owner: string;
|
||||
original_pledge: OriginalPledge;
|
||||
layer: string;
|
||||
mix_node: Mixnode;
|
||||
proxy: string;
|
||||
bonding_height: number;
|
||||
is_unbonding: boolean;
|
||||
}
|
||||
|
||||
export type RewardingDetails = {
|
||||
cost_params: CostParams;
|
||||
operator: string;
|
||||
delegates: string;
|
||||
total_unit_reward: string;
|
||||
unit_delegation: string;
|
||||
last_rewarded_epoch: number;
|
||||
unique_delegations: number;
|
||||
}
|
||||
|
||||
export type CostParams = {
|
||||
profit_margin_percent: string;
|
||||
interval_operating_cost: IntervalOperatingCost;
|
||||
}
|
||||
|
||||
export type IntervalOperatingCost = {
|
||||
denom: string;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
export type OriginalPledge = {
|
||||
denom: string;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
export type TotalDelegation = {
|
||||
denom: string;
|
||||
amount: string;
|
||||
};
|
||||
|
||||
export type Mixnode = {
|
||||
host: string;
|
||||
mix_port: number;
|
||||
verloc_port: number;
|
||||
http_api_port: number;
|
||||
sphinx_key: string;
|
||||
identity_key: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type MixnodeBond = {
|
||||
pledge_amount: OriginalPledge;
|
||||
total_delegation: TotalDelegation;
|
||||
owner: string;
|
||||
layer: string;
|
||||
block_height: string;
|
||||
mix_node: Mixnode;
|
||||
proxy: string;
|
||||
accumulated_rewards: string;
|
||||
}
|
||||
|
||||
export type MixnodesDetailed = {
|
||||
mixnode_details: AllMixnodes;
|
||||
stake_saturation: string;
|
||||
uncapped_stake_saturation: string;
|
||||
performance: string;
|
||||
estimated_operator_apy: string
|
||||
estimated_delegators_apy: string;
|
||||
};
|
||||
|
||||
export type BlacklistedMixnodes = {
|
||||
};
|
||||
|
||||
export type BlacklistedGateways = {
|
||||
};
|
||||
|
||||
export interface Gateway {
|
||||
host: string;
|
||||
mix_port: number;
|
||||
clients_port: number;
|
||||
location: string;
|
||||
sphinx_key: string;
|
||||
identity_key: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface AllGateways {
|
||||
pledge_amount: OriginalPledge;
|
||||
owner: string;
|
||||
block_height: number;
|
||||
gateway: Gateway;
|
||||
proxy: string;
|
||||
}
|
||||
|
||||
export type EpochRewardParams = {
|
||||
interval: Interval;
|
||||
rewarded_set_size: number;
|
||||
active_set_size: number;
|
||||
};
|
||||
|
||||
export type Interval = {
|
||||
reward_pool: string;
|
||||
staking_supply: string;
|
||||
staking_supply_scale_factor: string;
|
||||
epoch_reward_budget: string;
|
||||
stake_saturation_point: string;
|
||||
sybil_resistance: string;
|
||||
active_set_work_factor: string;
|
||||
interval_pool_emission: string;
|
||||
}
|
||||
|
||||
export type CurrentEpoch = {
|
||||
id: number;
|
||||
epochs_in_interval: number;
|
||||
current_epoch_start: string;
|
||||
current_epoch_id: number;
|
||||
epoch_length: EpochLength;
|
||||
total_elapsed_epochs: number;
|
||||
};
|
||||
|
||||
export type EpochLength = {
|
||||
secs: number;
|
||||
nanos: number;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
export interface Estimation {
|
||||
total_node_reward: string;
|
||||
operator: string;
|
||||
delegates: string;
|
||||
operating_cost: string;
|
||||
}
|
||||
|
||||
export interface Interval {
|
||||
reward_pool: string;
|
||||
staking_supply: string;
|
||||
staking_supply_scale_factor: string;
|
||||
epoch_reward_budget: string;
|
||||
stake_saturation_point: string;
|
||||
sybil_resistance: string;
|
||||
active_set_work_factor: string;
|
||||
interval_pool_emission: string;
|
||||
}
|
||||
|
||||
export interface RewardParams {
|
||||
interval: Interval;
|
||||
rewarded_set_size: number;
|
||||
active_set_size: number;
|
||||
}
|
||||
|
||||
export interface EpochLength {
|
||||
secs: number;
|
||||
nanos: number;
|
||||
}
|
||||
|
||||
export interface Epoch {
|
||||
id: number;
|
||||
epochs_in_interval: number;
|
||||
current_epoch_start: string;
|
||||
current_epoch_id: number;
|
||||
epoch_length: EpochLength;
|
||||
total_elapsed_epochs: number;
|
||||
}
|
||||
|
||||
export interface RewardEstimation {
|
||||
estimation: Estimation;
|
||||
reward_params: RewardParams;
|
||||
epoch: Epoch;
|
||||
as_at: number;
|
||||
}
|
||||
|
||||
export type EstimatedReward = {
|
||||
estimated_total_node_reward: number;
|
||||
estimated_operator_reward: number;
|
||||
estimated_delegators_reward: number;
|
||||
estimated_node_profit: number;
|
||||
estimated_operator_cost: number;
|
||||
reward_params: RewardParams;
|
||||
as_at: number;
|
||||
};
|
||||
|
||||
export type StakeSaturation = {
|
||||
saturation: string;
|
||||
uncapped_saturation: string;
|
||||
as_at: number;
|
||||
};
|
||||
|
||||
export type AvgUptime = {
|
||||
mix_id: number;
|
||||
avg_uptime: number;
|
||||
};
|
||||
|
||||
export type Report = {
|
||||
mix_id: number
|
||||
identity: string;
|
||||
owner: string;
|
||||
most_recent: number;
|
||||
last_hour: number;
|
||||
last_day: number;
|
||||
};
|
||||
|
||||
export type GatewayReport = {
|
||||
identity: string;
|
||||
owner: string;
|
||||
most_recent: number;
|
||||
last_hour: number;
|
||||
last_day: number;
|
||||
};
|
||||
|
||||
export type History = {
|
||||
date: string;
|
||||
uptime: number;
|
||||
};
|
||||
|
||||
export type NodeHistory = {
|
||||
mix_id: number;
|
||||
identity: string;
|
||||
owner: string;
|
||||
history: History[];
|
||||
};
|
||||
|
||||
export type GatewayHistory = {
|
||||
identity: string;
|
||||
owner: string;
|
||||
history: History[];
|
||||
};
|
||||
|
||||
export type CoreCount = {
|
||||
mix_id: number;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type GatewayCoreCount = {
|
||||
identity: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type ActiveStatus = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export interface InclusionProbabilities {
|
||||
inclusion_probabilities: InclusionProbability[];
|
||||
samples: number;
|
||||
elapsed: Elapsed;
|
||||
delta_max: number;
|
||||
delta_l2: number;
|
||||
as_at: number;
|
||||
}
|
||||
|
||||
export interface InclusionProbability {
|
||||
mix_id: number;
|
||||
in_active: number;
|
||||
in_reserve: number;
|
||||
}
|
||||
|
||||
export interface Elapsed {
|
||||
secs: number;
|
||||
nanos: number;
|
||||
}
|
||||
|
||||
export interface SingleInclusionProbability {
|
||||
in_active: number;
|
||||
in_reserve: number;
|
||||
}
|
||||
@@ -292,7 +292,7 @@
|
||||
|
||||
"@eslint/eslintrc@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
|
||||
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz"
|
||||
integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==
|
||||
dependencies:
|
||||
ajv "^6.12.4"
|
||||
@@ -307,7 +307,7 @@
|
||||
|
||||
"@humanwhocodes/config-array@^0.10.4":
|
||||
version "0.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c"
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz"
|
||||
integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==
|
||||
dependencies:
|
||||
"@humanwhocodes/object-schema" "^1.2.1"
|
||||
@@ -316,12 +316,12 @@
|
||||
|
||||
"@humanwhocodes/gitignore-to-minimatch@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d"
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz"
|
||||
integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==
|
||||
|
||||
"@humanwhocodes/object-schema@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz"
|
||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||
|
||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||
@@ -734,7 +734,7 @@
|
||||
|
||||
"@typescript-eslint/parser@^5.33.0":
|
||||
version "5.33.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.33.0.tgz#26ec3235b74f0667414613727cb98f9b69dc5383"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz"
|
||||
integrity sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.33.0"
|
||||
@@ -799,17 +799,17 @@
|
||||
|
||||
acorn-jsx@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
acorn@^8.8.0:
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz"
|
||||
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
|
||||
|
||||
ajv@^6.10.0, ajv@^6.12.4:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
@@ -865,7 +865,7 @@ argparse@^1.0.7:
|
||||
|
||||
argparse@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
array-union@^2.1.0:
|
||||
@@ -1140,7 +1140,7 @@ dedent@^0.7.0:
|
||||
|
||||
deep-is@^0.1.3:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
|
||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||
|
||||
deepmerge@^4.2.2:
|
||||
@@ -1172,7 +1172,7 @@ dir-glob@^3.0.1:
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||
resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz"
|
||||
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
@@ -1216,7 +1216,7 @@ escape-string-regexp@^2.0.0:
|
||||
|
||||
escape-string-regexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
eslint-config-prettier@^8.4.0:
|
||||
@@ -1241,7 +1241,7 @@ eslint-scope@^5.1.1:
|
||||
|
||||
eslint-scope@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
|
||||
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz"
|
||||
integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==
|
||||
dependencies:
|
||||
esrecurse "^4.3.0"
|
||||
@@ -1311,7 +1311,7 @@ eslint@^8.21.0:
|
||||
|
||||
espree@^9.3.2, espree@^9.3.3:
|
||||
version "9.3.3"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d"
|
||||
resolved "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz"
|
||||
integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==
|
||||
dependencies:
|
||||
acorn "^8.8.0"
|
||||
@@ -1325,7 +1325,7 @@ esprima@^4.0.0:
|
||||
|
||||
esquery@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
|
||||
resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz"
|
||||
integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
|
||||
dependencies:
|
||||
estraverse "^5.1.0"
|
||||
@@ -1349,7 +1349,7 @@ estraverse@^5.1.0, estraverse@^5.2.0:
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
|
||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
|
||||
execa@^5.0.0:
|
||||
@@ -1411,7 +1411,7 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
|
||||
|
||||
fast-levenshtein@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
|
||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||
|
||||
fastq@^1.6.0:
|
||||
@@ -1430,7 +1430,7 @@ fb-watchman@^2.0.0:
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
||||
resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
|
||||
integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
|
||||
dependencies:
|
||||
flat-cache "^3.0.4"
|
||||
@@ -1452,7 +1452,7 @@ find-up@^4.0.0, find-up@^4.1.0:
|
||||
|
||||
find-up@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
|
||||
resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
|
||||
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
|
||||
dependencies:
|
||||
locate-path "^6.0.0"
|
||||
@@ -1460,7 +1460,7 @@ find-up@^5.0.0:
|
||||
|
||||
flat-cache@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
|
||||
resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz"
|
||||
integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
|
||||
dependencies:
|
||||
flatted "^3.1.0"
|
||||
@@ -1468,7 +1468,7 @@ flat-cache@^3.0.4:
|
||||
|
||||
flatted@^3.1.0:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2"
|
||||
resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz"
|
||||
integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==
|
||||
|
||||
follow-redirects@^1.14.9:
|
||||
@@ -1492,7 +1492,7 @@ fs.realpath@^1.0.0:
|
||||
|
||||
fsevents@^2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
@@ -1534,7 +1534,7 @@ glob-parent@^5.1.2:
|
||||
|
||||
glob-parent@^6.0.1:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
|
||||
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
|
||||
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
|
||||
dependencies:
|
||||
is-glob "^4.0.3"
|
||||
@@ -1558,7 +1558,7 @@ globals@^11.1.0:
|
||||
|
||||
globals@^13.15.0:
|
||||
version "13.17.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4"
|
||||
resolved "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz"
|
||||
integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==
|
||||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
@@ -1582,7 +1582,7 @@ graceful-fs@^4.2.9:
|
||||
|
||||
grapheme-splitter@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
|
||||
resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz"
|
||||
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
@@ -1619,7 +1619,7 @@ ignore@^5.2.0:
|
||||
|
||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"
|
||||
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
|
||||
dependencies:
|
||||
parent-module "^1.0.0"
|
||||
@@ -2130,7 +2130,7 @@ js-yaml@^3.13.1:
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
@@ -2147,12 +2147,12 @@ json-parse-even-better-errors@^2.3.0:
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-stable-stringify-without-jsonify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
|
||||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||
|
||||
json-stringify-safe@5.0.1:
|
||||
@@ -2177,7 +2177,7 @@ leven@^3.1.0:
|
||||
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||
resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
|
||||
integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
@@ -2197,7 +2197,7 @@ locate-path@^5.0.0:
|
||||
|
||||
locate-path@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
|
||||
resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
|
||||
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
@@ -2209,7 +2209,7 @@ lodash.memoize@4.x:
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
@@ -2333,7 +2333,7 @@ onetime@^5.1.2:
|
||||
|
||||
optionator@^0.9.1:
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
||||
resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz"
|
||||
integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
|
||||
dependencies:
|
||||
deep-is "^0.1.3"
|
||||
@@ -2366,7 +2366,7 @@ p-locate@^4.1.0:
|
||||
|
||||
p-locate@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
|
||||
resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz"
|
||||
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
|
||||
dependencies:
|
||||
p-limit "^3.0.2"
|
||||
@@ -2378,7 +2378,7 @@ p-try@^2.0.0:
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
|
||||
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
@@ -2442,7 +2442,7 @@ pkg-dir@^4.2.0:
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prettier-linter-helpers@^1.0.0:
|
||||
@@ -2482,7 +2482,7 @@ prompts@^2.0.1:
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
@@ -2514,7 +2514,7 @@ resolve-cwd@^3.0.0:
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve-from@^5.0.0:
|
||||
@@ -2724,7 +2724,7 @@ test-exclude@^6.0.0:
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
|
||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||
|
||||
tmpl@1.0.5:
|
||||
@@ -2779,7 +2779,7 @@ tsutils@^3.21.0:
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
|
||||
integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
@@ -2791,7 +2791,7 @@ type-detect@4.0.8:
|
||||
|
||||
type-fest@^0.20.2:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
|
||||
type-fest@^0.21.3:
|
||||
@@ -2801,7 +2801,7 @@ type-fest@^0.21.3:
|
||||
|
||||
typescript@^4.7.4:
|
||||
version "4.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz"
|
||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||
|
||||
update-browserslist-db@^1.0.5:
|
||||
@@ -2814,7 +2814,7 @@ update-browserslist-db@^1.0.5:
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||
resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
|
||||
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
@@ -2834,7 +2834,7 @@ uuidv4@^6.2.12:
|
||||
|
||||
v8-compile-cache@^2.0.3:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||
resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"
|
||||
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
|
||||
|
||||
v8-to-istanbul@^9.0.1:
|
||||
@@ -2862,7 +2862,7 @@ which@^2.0.1:
|
||||
|
||||
word-wrap@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
|
||||
Reference in New Issue
Block a user