Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 471534a727 | |||
| b9cafa699b | |||
| 3ae16fbf1d | |||
| b84486c0f4 | |||
| 8f52f34bc4 | |||
| b7aa84cd5a | |||
| bfbd509e4b | |||
| 09b9601c7e | |||
| 2a1dd138e0 | |||
| 9874daa061 | |||
| baa61c07d5 | |||
| 62e9c8236a | |||
| cf268ffcd5 | |||
| b28ff17c30 | |||
| a7cd7a58f2 | |||
| 8bbdb94b13 | |||
| e32601ab86 | |||
| 161138bdff | |||
| 0529e84a31 | |||
| 95f98016de | |||
| 4967bbb5bd | |||
| 2952144d32 | |||
| 80c21b3ed9 | |||
| 1f0d5f8ad0 | |||
| 49ce56c367 | |||
| 4ab6f4c3a9 | |||
| 3727370b9e | |||
| b3272097f9 | |||
| ebc13c4327 | |||
| ec3a6b3e27 | |||
| 19f3c76f72 | |||
| 90cc239999 | |||
| c1bd5db902 | |||
| fb1649bab5 | |||
| b21ca41e16 | |||
| 8656abcbde | |||
| 3ae9ea5de6 | |||
| 8bcec241a2 | |||
| 306e9b9dc2 | |||
| d36e349cc6 | |||
| 4990a4745f | |||
| 0d399f7d70 | |||
| 56cf181770 | |||
| f0aa2feb76 | |||
| 4df927cc3d | |||
| 3f4373eb98 | |||
| cf10bb12ef | |||
| cb1e93e58d | |||
| f4f98027a0 |
@@ -1,16 +1,21 @@
|
||||
name: Build release of Nym smart contracts
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: contracts
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Check the release tag starts with `nym-contracts-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-contracts-') == false && github.event_name != 'workflow_dispatch'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Release tag did not start with nym-contracts-...')
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -21,7 +26,7 @@ jobs:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build release contracts
|
||||
run: RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
|
||||
run: make wasm
|
||||
|
||||
- name: Upload Mixnet Contract Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -36,3 +41,11 @@ jobs:
|
||||
name: vesting_contract.wasm
|
||||
path: contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
files: |
|
||||
contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
|
||||
contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
name: CI for Network Explorer API
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
|
||||
- name: Check the release tag starts with `nym-explorer-api-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-explorer-api-') == false && github.event_name != 'workflow_dispatch'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Release tag did not start with nym-explorer-api-...')
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build all explorer-api
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path explorer-api/Cargo.toml --workspace --release
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: my-artifact
|
||||
path: |
|
||||
target/release/explorer-api
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
files: |
|
||||
target/release/explorer-api
|
||||
@@ -25,6 +25,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
|
||||
- name: Check the release tag starts with `nym-binaries-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
|
||||
uses: actions/github-script@v3
|
||||
|
||||
+26
-14
@@ -2,33 +2,40 @@
|
||||
|
||||
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
|
||||
|
||||
### Added
|
||||
|
||||
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
|
||||
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
|
||||
- common/ledger: new library for communicating with a Ledger device ([#1640])
|
||||
- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
|
||||
- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
|
||||
- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
|
||||
- native-client/socks5-client/wasm-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
|
||||
- native-client/socks5-client/wasm-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
|
||||
- native-client/socks5-client/wasm-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
|
||||
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
|
||||
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to compute reward estimation endpoint
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
|
||||
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to cmpute reward estimation endpoint
|
||||
|
||||
### Fixed
|
||||
|
||||
- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
|
||||
- socks5-client: fix bug where in some cases packet reordering could trigger a connection being closed too early ([#1702])
|
||||
- socks5-client: fix bug where in some cases packet reordering could trigger a connection being closed too early ([#1702],[#1724])
|
||||
- validator-api: mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
|
||||
- validator-api: should now correctly update historical uptimes for all mixnodes and gateways every 24h ([#1721])
|
||||
|
||||
### Changed
|
||||
|
||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
|
||||
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
|
||||
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703],[#1725])
|
||||
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
|
||||
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703])
|
||||
- moved `Percent` struct to `contracts-common`, change affects explorer-api
|
||||
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
|
||||
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
|
||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
|
||||
- validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472])
|
||||
- validator-api: changes to internal data structures due to the mixnet contract revamp ([#1472])
|
||||
- validator-api: split epoch-operations into multiple separate transactions ([#1472])
|
||||
|
||||
[#1472]: https://github.com/nymtech/nym/pull/1472
|
||||
[#1541]: https://github.com/nymtech/nym/pull/1541
|
||||
[#1558]: https://github.com/nymtech/nym/pull/1558
|
||||
[#1577]: https://github.com/nymtech/nym/pull/1577
|
||||
@@ -36,13 +43,18 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1591]: https://github.com/nymtech/nym/pull/1591
|
||||
[#1640]: https://github.com/nymtech/nym/pull/1640
|
||||
[#1645]: https://github.com/nymtech/nym/pull/1645
|
||||
[#1611]: https://github.com/nymtech/nym/pull/1611
|
||||
[#1664]: https://github.com/nymtech/nym/pull/1664
|
||||
[#1666]: https://github.com/nymtech/nym/pull/1645
|
||||
[#1669]: https://github.com/nymtech/nym/pull/1669
|
||||
[#1671]: https://github.com/nymtech/nym/pull/1671
|
||||
[#1673]: https://github.com/nymtech/nym/pull/1673
|
||||
[#1681]: https://github.com/nymtech/nym/pull/1681
|
||||
[#1702]: https://github.com/nymtech/nym/pull/1702
|
||||
[#1703]: https://github.com/nymtech/nym/pull/1703
|
||||
[#1721]: https://github.com/nymtech/nym/pull/1721
|
||||
[#1724]: https://github.com/nymtech/nym/pull/1724
|
||||
[#1725]: https://github.com/nymtech/nym/pull/1725
|
||||
|
||||
|
||||
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
|
||||
|
||||
Generated
+35
-9
@@ -578,7 +578,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"crypto",
|
||||
@@ -737,7 +737,10 @@ name = "contracts-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1579,15 +1582,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 3.2.8",
|
||||
"contracts-common",
|
||||
"dotenv",
|
||||
"humantime-serde",
|
||||
"isocountry",
|
||||
"itertools",
|
||||
"log",
|
||||
"logging",
|
||||
"maxminddb",
|
||||
"mixnet-contract-common",
|
||||
"network-defaults",
|
||||
@@ -2726,6 +2731,14 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logging"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.5.4"
|
||||
@@ -3057,7 +3070,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -3068,6 +3081,7 @@ dependencies = [
|
||||
"clap_complete_fig",
|
||||
"dotenv",
|
||||
"log",
|
||||
"logging",
|
||||
"network-defaults",
|
||||
"nym-cli-commands",
|
||||
"pretty_env_logger",
|
||||
@@ -3108,7 +3122,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"client-core",
|
||||
@@ -3123,6 +3137,7 @@ dependencies = [
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"log",
|
||||
"logging",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"pemstore",
|
||||
@@ -3144,7 +3159,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3164,6 +3179,7 @@ dependencies = [
|
||||
"gateway-requests",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"logging",
|
||||
"mixnet-client",
|
||||
"mixnode-common",
|
||||
"network-defaults",
|
||||
@@ -3190,7 +3206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
@@ -3206,6 +3222,7 @@ dependencies = [
|
||||
"humantime-serde",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"logging",
|
||||
"mixnet-client",
|
||||
"mixnode-common",
|
||||
"nonexhaustive-delayqueue",
|
||||
@@ -3231,7 +3248,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap 3.2.8",
|
||||
@@ -3240,6 +3257,7 @@ dependencies = [
|
||||
"futures",
|
||||
"ipnetwork 0.20.0",
|
||||
"log",
|
||||
"logging",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"ordered-buffer",
|
||||
@@ -3265,6 +3283,7 @@ version = "1.0.2"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
"logging",
|
||||
"pretty_env_logger",
|
||||
"rocket",
|
||||
"serde",
|
||||
@@ -3276,7 +3295,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"client-core",
|
||||
@@ -3291,6 +3310,7 @@ dependencies = [
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"log",
|
||||
"logging",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"ordered-buffer",
|
||||
@@ -3339,7 +3359,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-api"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3349,6 +3369,7 @@ dependencies = [
|
||||
"coconut-interface",
|
||||
"config",
|
||||
"console-subscriber",
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"credential-storage",
|
||||
"credentials",
|
||||
@@ -3363,6 +3384,7 @@ dependencies = [
|
||||
"humantime-serde",
|
||||
"inclusion-probability",
|
||||
"log",
|
||||
"logging",
|
||||
"mixnet-contract-common",
|
||||
"multisig-contract-common",
|
||||
"nymcoconut",
|
||||
@@ -6386,6 +6408,7 @@ dependencies = [
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
"contracts-common",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"cw3",
|
||||
@@ -6474,12 +6497,14 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
name = "vesting-contract"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"vergen 5.1.17",
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
@@ -6487,6 +6512,7 @@ dependencies = [
|
||||
name = "vesting-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
|
||||
@@ -41,6 +41,7 @@ members = [
|
||||
"common/execute",
|
||||
"common/inclusion-probability",
|
||||
"common/ledger",
|
||||
"common/logging",
|
||||
"common/mixnode-common",
|
||||
"common/network-defaults",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
|
||||
@@ -92,6 +92,9 @@ build-wallet:
|
||||
build-connect:
|
||||
cargo build --manifest-path nym-connect/Cargo.toml --workspace
|
||||
|
||||
build-explorer-api:
|
||||
cargo build --manifest-path explorer-api/Cargo.toml --workspace
|
||||
|
||||
build-wasm-client:
|
||||
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -2,10 +2,8 @@ use futures::channel::mpsc;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
|
||||
//pub type InputMessageSender = mpsc::Sender<InputMessage>;
|
||||
//pub type InputMessageReceiver = mpsc::UnboundedReceiver<InputMessage>;
|
||||
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
|
||||
pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
|
||||
pub type InputMessageSender = mpsc::UnboundedSender<InputMessage>;
|
||||
pub type InputMessageReceiver = mpsc::UnboundedReceiver<InputMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputMessage {
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ impl AcknowledgementListener {
|
||||
}
|
||||
|
||||
async fn on_ack(&mut self, ack_content: Vec<u8>) {
|
||||
trace!("Received an ack");
|
||||
debug!("Received an ack");
|
||||
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
|
||||
.map(FragmentIdentifier::try_from_bytes)
|
||||
{
|
||||
|
||||
+4
-9
@@ -15,7 +15,6 @@ use nymsphinx::preparer::MessagePreparer;
|
||||
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use std::thread::panicking;
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
@@ -183,13 +182,9 @@ where
|
||||
// there's no point in trying to send nothing
|
||||
if let Some(real_messages) = real_messages {
|
||||
// tells real message sender (with the poisson timer) to send this to the mix network
|
||||
log::info!("real_message_sender capacity: {}", self.real_message_sender.capacity());
|
||||
if let Err(err) = self.real_message_sender
|
||||
.send(real_messages)
|
||||
.await {
|
||||
log::error!("Failed to send");
|
||||
panic!();
|
||||
}
|
||||
self.real_message_sender
|
||||
.unbounded_send(real_messages)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +194,7 @@ where
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
input_msg = self.input_receiver.recv() => match input_msg {
|
||||
input_msg = self.input_receiver.next() => match input_msg {
|
||||
Some(input_msg) => {
|
||||
self.on_input_message(input_msg).await;
|
||||
},
|
||||
|
||||
+3
-8
@@ -112,17 +112,12 @@ where
|
||||
.unwrap();
|
||||
|
||||
// send to `OutQueueControl` to eventually send to the mix network
|
||||
if let Err(err) = self
|
||||
.real_message_sender
|
||||
.send(vec![RealMessage::new(
|
||||
self.real_message_sender
|
||||
.unbounded_send(vec![RealMessage::new(
|
||||
prepared_fragment.mix_packet,
|
||||
frag_id,
|
||||
)])
|
||||
.await
|
||||
{
|
||||
log::error!("Failed to send");
|
||||
panic!();
|
||||
}
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
@@ -113,8 +113,7 @@ impl RealMessagesController<OsRng> {
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
//let (real_message_sender, real_message_receiver) = mpsc::unbounded();
|
||||
let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(16);
|
||||
let (real_message_sender, real_message_receiver) = mpsc::unbounded();
|
||||
let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
|
||||
|
||||
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
|
||||
|
||||
@@ -242,10 +242,8 @@ impl RealMessage {
|
||||
|
||||
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic
|
||||
// after sufficient delay
|
||||
//pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender<Vec<RealMessage>>;
|
||||
//type BatchRealMessageReceiver = mpsc::UnboundedReceiver<Vec<RealMessage>>;
|
||||
pub(crate) type BatchRealMessageSender = tokio::sync::mpsc::Sender<Vec<RealMessage>>;
|
||||
type BatchRealMessageReceiver = tokio::sync::mpsc::Receiver<Vec<RealMessage>>;
|
||||
pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender<Vec<RealMessage>>;
|
||||
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<Vec<RealMessage>>;
|
||||
|
||||
pub(crate) enum StreamMessage {
|
||||
Cover,
|
||||
@@ -417,7 +415,7 @@ where
|
||||
}
|
||||
|
||||
// decide what kind of message to send
|
||||
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
@@ -464,7 +462,7 @@ where
|
||||
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
|
||||
}
|
||||
|
||||
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
@@ -208,7 +208,7 @@ impl ReceivedMessagesBuffer {
|
||||
}
|
||||
|
||||
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
trace!(
|
||||
debug!(
|
||||
"Processing {:?} new message that might get added to the buffer!",
|
||||
msgs.len()
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use config::NymConfig;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
@@ -247,8 +248,8 @@ impl<T: NymConfig> Config<T> {
|
||||
self.debug.disable_main_poisson_packet_distribution
|
||||
}
|
||||
|
||||
pub fn get_use_extended_packet_size(&self) -> bool {
|
||||
self.debug.use_extended_packet_size
|
||||
pub fn get_use_extended_packet_size(&self) -> Option<ExtendedPacketSize> {
|
||||
self.debug.use_extended_packet_size.clone()
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> &str {
|
||||
@@ -470,8 +471,16 @@ pub struct Debug {
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: bool,
|
||||
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: Option<ExtendedPacketSize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ExtendedPacketSize {
|
||||
Extended8,
|
||||
Extended16,
|
||||
Extended32,
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
@@ -488,7 +497,17 @@ impl Default for Debug {
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_loop_cover_traffic_stream: false,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
use_extended_packet_size: false,
|
||||
use_extended_packet_size: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtendedPacketSize> for PacketSize {
|
||||
fn from(size: ExtendedPacketSize) -> PacketSize {
|
||||
match size {
|
||||
ExtendedPacketSize::Extended8 => PacketSize::ExtendedPacket8,
|
||||
ExtendedPacketSize::Extended16 => PacketSize::ExtendedPacket16,
|
||||
ExtendedPacketSize::Extended32 => PacketSize::ExtendedPacket32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
@@ -38,6 +38,7 @@ completions = { path = "../../common/completions" }
|
||||
credential-storage = { path = "../../common/credential-storage" }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
@@ -29,7 +29,6 @@ use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
|
||||
|
||||
@@ -101,8 +100,9 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
);
|
||||
|
||||
if self.config.get_base().get_use_extended_packet_size() {
|
||||
stream.set_custom_packet_size(PacketSize::ExtendedPacket)
|
||||
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
stream.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
stream.start_with_shutdown(shutdown);
|
||||
@@ -130,8 +130,9 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
if self.config.get_base().get_use_extended_packet_size() {
|
||||
controller_config.set_custom_packet_size(PacketSize::ExtendedPacket)
|
||||
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
controller_config.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
info!("Starting real traffic stream...");
|
||||
@@ -283,41 +284,31 @@ impl NymClient {
|
||||
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
|
||||
}
|
||||
|
||||
// EXPERIMENTAL DIRECT RUST API
|
||||
// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
// well enough in local tests)
|
||||
//pub async fn send_message(&mut self, recipient: Recipient, message: Vec<u8>, with_reply_surb: bool) {
|
||||
// let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub fn send_message(&mut self, recipient: Recipient, message: Vec<u8>, with_reply_surb: bool) {
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
|
||||
|
||||
// //self.input_tx
|
||||
// // .as_ref()
|
||||
// // .expect("start method was not called before!")
|
||||
// // .unbounded_send(input_msg)
|
||||
// // .unwrap();
|
||||
// log::info!("input_tx capacity: {}", self.input_tx.capacity());
|
||||
// if let Err(err) = self
|
||||
// .input_tx
|
||||
// .as_ref()
|
||||
// .expect("start method was not called before!")
|
||||
// .send(input_msg).await
|
||||
// {
|
||||
// log::error!("failed to send");
|
||||
// panic!();
|
||||
// }
|
||||
//}
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
.expect("start method was not called before!")
|
||||
.unbounded_send(input_msg)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// EXPERIMENTAL DIRECT RUST API
|
||||
// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
// well enough in local tests)
|
||||
//pub fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
|
||||
// let input_msg = InputMessage::new_reply(reply_surb, message);
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
|
||||
let input_msg = InputMessage::new_reply(reply_surb, message);
|
||||
|
||||
// self.input_tx
|
||||
// .as_ref()
|
||||
// .expect("start method was not called before!")
|
||||
// .unbounded_send(input_msg)
|
||||
// .unwrap();
|
||||
//}
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
.expect("start method was not called before!")
|
||||
.unbounded_send(input_msg)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
@@ -372,8 +363,7 @@ impl NymClient {
|
||||
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
|
||||
|
||||
// channels responsible for controlling real messages
|
||||
//let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
|
||||
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(16);
|
||||
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
|
||||
|
||||
// channels responsible for controlling ack messages
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
@@ -34,25 +35,3 @@ fn banner() -> String {
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("handlebars", log::LevelFilter::Warn)
|
||||
.filter_module("sled", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_send(
|
||||
fn handle_send(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
@@ -84,31 +84,18 @@ impl Handler {
|
||||
) -> Option<ServerResponse> {
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
|
||||
log::info!("msg_input capacity: {}", self.msg_input.capacity());
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
log::error!("send failed");
|
||||
panic!();
|
||||
}
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
async fn handle_reply(
|
||||
&mut self,
|
||||
reply_surb: ReplySurb,
|
||||
message: Vec<u8>,
|
||||
) -> Option<ServerResponse> {
|
||||
fn handle_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) -> Option<ServerResponse> {
|
||||
if message.len() > ReplySurb::max_msg_len(Default::default()) {
|
||||
return Some(ServerResponse::new_error(format!("too long message to put inside a reply SURB. Received: {} bytes and maximum is {} bytes", message.len(), ReplySurb::max_msg_len(Default::default()))));
|
||||
}
|
||||
|
||||
let input_msg = InputMessage::new_reply(reply_surb, message);
|
||||
//self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
log::info!("msg_input capacity: {}", self.msg_input.capacity());
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
log::error!("send failed");
|
||||
panic!();
|
||||
}
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
|
||||
None
|
||||
}
|
||||
@@ -117,22 +104,22 @@ impl Handler {
|
||||
ServerResponse::SelfAddress(self.self_full_address)
|
||||
}
|
||||
|
||||
async fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
|
||||
fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
|
||||
match request {
|
||||
ClientRequest::Send {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
} => self.handle_send(recipient, message, with_reply_surb).await,
|
||||
} => self.handle_send(recipient, message, with_reply_surb),
|
||||
ClientRequest::Reply {
|
||||
message,
|
||||
reply_surb,
|
||||
} => self.handle_reply(reply_surb, message).await,
|
||||
} => self.handle_reply(reply_surb, message),
|
||||
ClientRequest::SelfAddress => Some(self.handle_self_address()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_text_message(&mut self, msg: String) -> Option<WsMessage> {
|
||||
fn handle_text_message(&mut self, msg: String) -> Option<WsMessage> {
|
||||
debug!("Handling text message request");
|
||||
trace!("Content: {:?}", msg);
|
||||
|
||||
@@ -141,13 +128,13 @@ impl Handler {
|
||||
|
||||
let response = match client_request {
|
||||
Err(err) => Some(ServerResponse::Error(err)),
|
||||
Ok(req) => self.handle_request(req).await,
|
||||
Ok(req) => self.handle_request(req),
|
||||
};
|
||||
|
||||
response.map(|resp| WsMessage::text(resp.into_text()))
|
||||
}
|
||||
|
||||
async fn handle_binary_message(&mut self, msg: Vec<u8>) -> Option<WsMessage> {
|
||||
fn handle_binary_message(&mut self, msg: Vec<u8>) -> Option<WsMessage> {
|
||||
debug!("Handling binary message request");
|
||||
|
||||
self.received_response_type = ReceivedResponseType::Binary;
|
||||
@@ -155,19 +142,19 @@ impl Handler {
|
||||
|
||||
let response = match client_request {
|
||||
Err(err) => Some(ServerResponse::Error(err)),
|
||||
Ok(req) => self.handle_request(req).await,
|
||||
Ok(req) => self.handle_request(req),
|
||||
};
|
||||
|
||||
response.map(|resp| WsMessage::Binary(resp.into_binary()))
|
||||
}
|
||||
|
||||
async fn handle_ws_request(&mut self, raw_request: WsMessage) -> Option<WsMessage> {
|
||||
fn handle_ws_request(&mut self, raw_request: WsMessage) -> Option<WsMessage> {
|
||||
// apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore
|
||||
// them and let's test that claim. If that's not the case, just copy code from
|
||||
// old version of this file.
|
||||
match raw_request {
|
||||
WsMessage::Text(text_message) => self.handle_text_message(text_message).await,
|
||||
WsMessage::Binary(binary_message) => self.handle_binary_message(binary_message).await,
|
||||
WsMessage::Text(text_message) => self.handle_text_message(text_message),
|
||||
WsMessage::Binary(binary_message) => self.handle_binary_message(binary_message),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -257,7 +244,7 @@ impl Handler {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(response) = self.handle_ws_request(socket_msg).await {
|
||||
if let Some(response) = self.handle_ws_request(socket_msg) {
|
||||
if let Err(err) = self.send_websocket_response(response).await {
|
||||
warn!(
|
||||
"Failed to send message over websocket: {}. Assuming the connection is dead.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
@@ -31,6 +31,7 @@ completions = { path = "../../common/completions" }
|
||||
credential-storage = { path = "../../common/credential-storage" }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
@@ -34,7 +34,6 @@ use gateway_client::{
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
|
||||
|
||||
pub mod config;
|
||||
@@ -101,8 +100,9 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
);
|
||||
|
||||
if self.config.get_base().get_use_extended_packet_size() {
|
||||
stream.set_custom_packet_size(PacketSize::ExtendedPacket)
|
||||
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
stream.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
stream.start_with_shutdown(shutdown);
|
||||
@@ -130,8 +130,9 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
if self.config.get_base().get_use_extended_packet_size() {
|
||||
controller_config.set_custom_packet_size(PacketSize::ExtendedPacket)
|
||||
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
controller_config.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
info!("Starting real traffic stream...");
|
||||
@@ -351,7 +352,7 @@ impl NymClient {
|
||||
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
|
||||
|
||||
// channels responsible for controlling real messages
|
||||
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(16);
|
||||
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
|
||||
|
||||
// channels responsible for controlling ack messages
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
@@ -34,23 +35,3 @@ fn banner() -> String {
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -231,12 +231,7 @@ impl SocksClient {
|
||||
let msg = Message::Request(req);
|
||||
|
||||
let input_message = InputMessage::new_fresh(self.service_provider, msg.into_bytes(), false);
|
||||
//self.input_sender.unbounded_send(input_message).unwrap();
|
||||
log::info!("input_sender capacity: {}", self.input_sender.capacity());
|
||||
if let Err(err) = self.input_sender.send(input_message).await {
|
||||
log::info!("failed to send");
|
||||
panic!();
|
||||
}
|
||||
self.input_sender.unbounded_send(input_message).unwrap();
|
||||
}
|
||||
|
||||
async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: 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.1"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
@@ -55,8 +55,6 @@ wee_alloc = { version = "0.4", optional = true }
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = true
|
||||
wasm-opt = false
|
||||
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
@@ -2,11 +2,6 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
performance: {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000
|
||||
},
|
||||
entry: {
|
||||
bootstrap: './bootstrap.js',
|
||||
worker: './worker.js',
|
||||
@@ -27,7 +22,6 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
],
|
||||
experiments: { syncWebAssembly: true },
|
||||
};
|
||||
|
||||
@@ -73,7 +73,6 @@ async function main() {
|
||||
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
|
||||
|
||||
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
|
||||
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
|
||||
|
||||
// only really useful if you want to adjust some settings like traffic rate
|
||||
// (if not needed you can just pass a null)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
|
||||
#![allow(clippy::drop_non_drop)]
|
||||
|
||||
use client_core::config::{Debug as ConfigDebug, GatewayEndpoint};
|
||||
use client_core::config::{Debug as ConfigDebug, ExtendedPacketSize, GatewayEndpoint};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
@@ -107,6 +107,11 @@ pub struct Debug {
|
||||
|
||||
impl From<Debug> for ConfigDebug {
|
||||
fn from(debug: Debug) -> Self {
|
||||
// For now we just always use the (older) 32kb extended size
|
||||
let use_extended_packet_size = debug
|
||||
.use_extended_packet_size
|
||||
.then(|| ExtendedPacketSize::Extended32);
|
||||
|
||||
ConfigDebug {
|
||||
average_packet_delay: Duration::from_millis(debug.average_packet_delay_ms),
|
||||
average_ack_delay: Duration::from_millis(debug.average_ack_delay_ms),
|
||||
@@ -126,7 +131,7 @@ impl From<Debug> for ConfigDebug {
|
||||
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
|
||||
disable_main_poisson_packet_distribution: debug
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: debug.use_extended_packet_size,
|
||||
use_extended_packet_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +153,7 @@ impl From<ConfigDebug> for Debug {
|
||||
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
|
||||
disable_main_poisson_packet_distribution: debug
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: debug.use_extended_packet_size,
|
||||
use_extended_packet_size: debug.use_extended_packet_size.is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use gateway_client::{
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use rand::rngs::OsRng;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
@@ -110,8 +109,8 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
);
|
||||
|
||||
if self.config.debug.use_extended_packet_size {
|
||||
stream.set_custom_packet_size(PacketSize::ExtendedPacket)
|
||||
if let Some(size) = &self.config.debug.use_extended_packet_size {
|
||||
stream.set_custom_packet_size(size.clone().into());
|
||||
}
|
||||
|
||||
stream.start();
|
||||
@@ -135,8 +134,8 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
if self.config.debug.use_extended_packet_size {
|
||||
controller_config.set_custom_packet_size(PacketSize::ExtendedPacket)
|
||||
if let Some(size) = &self.config.debug.use_extended_packet_size {
|
||||
controller_config.set_custom_packet_size(size.clone().into());
|
||||
}
|
||||
|
||||
console_log!("Starting real traffic stream...");
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
@@ -62,9 +62,19 @@ impl PacketRouter {
|
||||
trace!("routing regular packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket.plaintext_size() - ack_overhead
|
||||
== PacketSize::ExtendedPacket8.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing extended packet");
|
||||
trace!("routing extended8 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket16.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing extended16 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket32.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing extended32 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else {
|
||||
// this can happen if other clients are not padding their messages
|
||||
|
||||
@@ -23,6 +23,7 @@ pub struct Config {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -31,12 +32,14 @@ impl Config {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
initial_reconnection_backoff,
|
||||
maximum_reconnection_backoff,
|
||||
initial_connection_timeout,
|
||||
maximum_connection_buffer_size,
|
||||
use_legacy_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,7 +204,8 @@ impl SendWithoutResponse for Client {
|
||||
packet_mode: PacketMode,
|
||||
) -> io::Result<()> {
|
||||
trace!("Sending packet to {:?}", address);
|
||||
let framed_packet = FramedSphinxPacket::new(packet, packet_mode);
|
||||
let framed_packet =
|
||||
FramedSphinxPacket::new(packet, packet_mode, self.config.use_legacy_version);
|
||||
|
||||
if let Some(sender) = self.conn_new.get_mut(&address) {
|
||||
if let Err(err) = sender.channel.try_send(framed_packet) {
|
||||
@@ -259,6 +263,7 @@ mod tests {
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
use_legacy_version: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,14 @@ impl PacketForwarder {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
) -> (PacketForwarder, MixForwardingSender) {
|
||||
let client_config = Config::new(
|
||||
initial_reconnection_backoff,
|
||||
maximum_reconnection_backoff,
|
||||
initial_connection_timeout,
|
||||
maximum_connection_buffer_size,
|
||||
use_legacy_version,
|
||||
);
|
||||
|
||||
let (packet_sender, packet_receiver) = mpsc::unbounded();
|
||||
|
||||
@@ -13,6 +13,7 @@ colored = "2.0"
|
||||
cw3 = "0.13.1"
|
||||
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
|
||||
contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
|
||||
coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
|
||||
vesting-contract = { path = "../../../contracts/vesting" }
|
||||
|
||||
@@ -15,14 +15,12 @@ use cosmrs::rpc::query::Query;
|
||||
use cosmrs::rpc::Error as TendermintRpcError;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmrs::tx::Msg;
|
||||
use cosmwasm_std::Uint128;
|
||||
use execute::execute;
|
||||
use network_defaults::{ChainDetails, NymNetworkDetails};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::time::SystemTime;
|
||||
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
|
||||
use vesting_contract_common::QueryMsg as VestingQueryMsg;
|
||||
|
||||
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
|
||||
@@ -47,6 +45,7 @@ pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
use mixnet_contract_common::MixId;
|
||||
pub use signing_client::Client as SigningNymdClient;
|
||||
pub use traits::{VestingQueryClient, VestingSigningClient};
|
||||
use vesting_contract_common::PledgeCap;
|
||||
|
||||
pub mod coin;
|
||||
pub mod cosmwasm_client;
|
||||
@@ -482,16 +481,6 @@ impl<C> NymdClient<C> {
|
||||
self.client.get_total_supply().await
|
||||
}
|
||||
|
||||
pub async fn vesting_get_locked_pledge_cap(&self) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = VestingQueryMsg::GetLockedPledgeCap {};
|
||||
self.client
|
||||
.query_contract_smart(self.vesting_contract_address(), &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn simulate<I, M>(&self, messages: I) -> Result<SimulateResponse, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
@@ -737,12 +726,16 @@ impl<C> NymdClient<C> {
|
||||
#[execute("vesting")]
|
||||
fn _vesting_update_locked_pledge_cap(
|
||||
&self,
|
||||
amount: Uint128,
|
||||
address: String,
|
||||
cap: PledgeCap,
|
||||
fee: Option<Fee>,
|
||||
) -> (VestingExecuteMsg, Option<Fee>)
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
(VestingExecuteMsg::UpdateLockedPledgeCap { amount }, fee)
|
||||
(
|
||||
VestingExecuteMsg::UpdateLockedPledgeCap { address, cap },
|
||||
fee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::NymdClient;
|
||||
use async_trait::async_trait;
|
||||
use contracts_common::ContractBuildInformation;
|
||||
use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
|
||||
use mixnet_contract_common::MixId;
|
||||
use serde::Deserialize;
|
||||
use vesting_contract::vesting::Account;
|
||||
use vesting_contract_common::{
|
||||
messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse,
|
||||
@@ -16,6 +18,15 @@ use vesting_contract_common::{
|
||||
|
||||
#[async_trait]
|
||||
pub trait VestingQueryClient {
|
||||
async fn query_vesting_contract<T>(&self, query: VestingQueryMsg) -> Result<T, NymdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>;
|
||||
|
||||
async fn get_vesting_contract_version(&self) -> Result<ContractBuildInformation, NymdError> {
|
||||
self.query_vesting_contract(VestingQueryMsg::GetContractVersion {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn locked_coins(
|
||||
&self,
|
||||
address: &str,
|
||||
@@ -107,6 +118,15 @@ pub trait VestingQueryClient {
|
||||
|
||||
#[async_trait]
|
||||
impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
|
||||
async fn query_vesting_contract<T>(&self, query: VestingQueryMsg) -> Result<T, NymdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.client
|
||||
.query_contract_smart(self.vesting_contract_address(), &query)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn locked_coins(
|
||||
&self,
|
||||
vesting_account_address: &str,
|
||||
|
||||
@@ -9,6 +9,7 @@ use async_trait::async_trait;
|
||||
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use mixnet_contract_common::{Gateway, MixId, MixNode};
|
||||
use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification};
|
||||
use vesting_contract_common::PledgeCap;
|
||||
|
||||
#[async_trait]
|
||||
pub trait VestingSigningClient {
|
||||
@@ -105,6 +106,7 @@ pub trait VestingSigningClient {
|
||||
staking_address: Option<String>,
|
||||
vesting_spec: Option<VestingSpecification>,
|
||||
amount: Coin,
|
||||
cap: Option<PledgeCap>,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
}
|
||||
@@ -382,6 +384,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
staking_address: Option<String>,
|
||||
vesting_spec: Option<VestingSpecification>,
|
||||
amount: Coin,
|
||||
cap: Option<PledgeCap>,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
@@ -389,6 +392,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
owner_address: owner_address.to_string(),
|
||||
staking_address,
|
||||
vesting_spec,
|
||||
cap,
|
||||
};
|
||||
self.client
|
||||
.execute(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use thiserror::Error;
|
||||
use validator_api_requests::models::RequestError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ValidatorAPIError {
|
||||
@@ -10,4 +11,7 @@ pub enum ValidatorAPIError {
|
||||
|
||||
#[error("Request failed with error message - {0}")]
|
||||
GenericRequestFailure(String),
|
||||
|
||||
#[error("The validator API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())]
|
||||
ApiRequestFailure { status: u16, error: RequestError },
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::validator_api::error::ValidatorAPIError;
|
||||
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use reqwest::Response;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use validator_api_requests::coconut::{
|
||||
@@ -12,9 +13,10 @@ use validator_api_requests::coconut::{
|
||||
VerifyCredentialBody, VerifyCredentialResponse,
|
||||
};
|
||||
use validator_api_requests::models::{
|
||||
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
||||
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
@@ -47,6 +49,19 @@ impl Client {
|
||||
&self.url
|
||||
}
|
||||
|
||||
async fn send_get_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, ValidatorAPIError>
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = create_api_url(&self.url, path, params);
|
||||
Ok(self.reqwest_client.get(url).send().await?)
|
||||
}
|
||||
|
||||
async fn query_validator_api<T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -57,8 +72,36 @@ impl Client {
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = create_api_url(&self.url, path, params);
|
||||
Ok(self.reqwest_client.get(url).send().await?.json().await?)
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else {
|
||||
Err(ValidatorAPIError::GenericRequestFailure(res.text().await?))
|
||||
}
|
||||
}
|
||||
|
||||
// This works for endpoints returning Result<Json<T>, ErrorResponse>
|
||||
async fn query_validator_api_fallible<T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, ValidatorAPIError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
let status = res.status();
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else {
|
||||
let request_error: RequestError = res.json().await?;
|
||||
Err(ValidatorAPIError::ApiRequestFailure {
|
||||
status: status.as_u16(),
|
||||
error: request_error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn post_validator_api<B, T, K, V>(
|
||||
@@ -135,6 +178,74 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_report(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeStatusReportResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::REPORT,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateway_report(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<GatewayStatusReportResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAY,
|
||||
identity,
|
||||
routes::REPORT,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_history(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeUptimeHistoryResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::HISTORY,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateway_history(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<GatewayUptimeHistoryResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAY,
|
||||
identity,
|
||||
routes::HISTORY,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorAPIError> {
|
||||
@@ -234,7 +345,7 @@ impl Client {
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
self.query_validator_api_fallible(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -251,7 +362,7 @@ impl Client {
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<StakeSaturationResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
self.query_validator_api_fallible(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -268,7 +379,7 @@ impl Client {
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<InclusionProbabilityResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
self.query_validator_api_fallible(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -285,7 +396,7 @@ impl Client {
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<UptimeResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
self.query_validator_api_fallible(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
|
||||
@@ -10,7 +10,6 @@ pub const GATEWAYS: &str = "gateways";
|
||||
pub const DETAILED: &str = "detailed";
|
||||
pub const ACTIVE: &str = "active";
|
||||
pub const REWARDED: &str = "rewarded";
|
||||
|
||||
pub const COCONUT_ROUTES: &str = "coconut";
|
||||
pub const BANDWIDTH: &str = "bandwidth";
|
||||
|
||||
@@ -28,6 +27,8 @@ pub const CORE_STATUS_COUNT: &str = "core-status-count";
|
||||
pub const SINCE_ARG: &str = "since";
|
||||
|
||||
pub const STATUS: &str = "status";
|
||||
pub const REPORT: &str = "report";
|
||||
pub const HISTORY: &str = "history";
|
||||
pub const REWARD_ESTIMATION: &str = "reward-estimation";
|
||||
pub const AVG_UPTIME: &str = "avg_uptime";
|
||||
pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
|
||||
@@ -12,6 +12,7 @@ use validator_client::nymd::AccountId;
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
use validator_client::nymd::{CosmosCoin, Denom};
|
||||
use vesting_contract_common::messages::VestingSpecification;
|
||||
use vesting_contract_common::PledgeCap;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
@@ -34,6 +35,12 @@ pub struct Args {
|
||||
|
||||
#[clap(long)]
|
||||
pub staking_address: Option<String>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "Pledge cap as either absolute uNYM value or percentage, floats need to be in the 0.0 to 1.0 range and will be parsed as percentages, integers will be parsed as uNYM"
|
||||
)]
|
||||
pub pledge_cap: Option<PledgeCap>,
|
||||
}
|
||||
|
||||
pub async fn create(args: Args, client: SigningClient, network_details: &NymNetworkDetails) {
|
||||
@@ -55,6 +62,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
|
||||
args.staking_address,
|
||||
Some(vesting),
|
||||
coin.into(),
|
||||
args.pledge_cap,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -9,3 +9,8 @@ edition = "2021"
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
thiserror = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.0"
|
||||
|
||||
@@ -11,8 +11,6 @@ use cosmwasm_std::Event;
|
||||
/// * `event`: event to search through.
|
||||
/// * `key`: key associated with the particular attribute
|
||||
pub fn must_find_attribute(event: &Event, key: &str) -> String {
|
||||
// due to how the function is supposed to work, the unwrap is fine in this instance
|
||||
#[allow(clippy::unwrap_used)]
|
||||
may_find_attribute(event, key).unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod events;
|
||||
pub mod types;
|
||||
|
||||
|
||||
@@ -1,7 +1,128 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use cosmwasm_std::Decimal;
|
||||
use cosmwasm_std::Uint128;
|
||||
use schemars::JsonSchema;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::Mul;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
|
||||
amount * Uint128::new(1)
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ContractsCommonError {
|
||||
#[error("Provided percent value ({0}) is greater than 100%")]
|
||||
InvalidPercent(Decimal),
|
||||
|
||||
#[error("{source}")]
|
||||
StdErr {
|
||||
#[from]
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
}
|
||||
|
||||
/// Percent represents a value between 0 and 100%
|
||||
/// (i.e. between 0.0 and 1.0)
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Serialize, Deserialize, JsonSchema,
|
||||
)]
|
||||
pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
|
||||
|
||||
impl Percent {
|
||||
pub fn new(value: Decimal) -> Result<Self, ContractsCommonError> {
|
||||
if value > Decimal::one() {
|
||||
Err(ContractsCommonError::InvalidPercent(value))
|
||||
} else {
|
||||
Ok(Percent(value))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.0 == Decimal::zero()
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self(Decimal::zero())
|
||||
}
|
||||
|
||||
pub fn hundred() -> Self {
|
||||
Self(Decimal::one())
|
||||
}
|
||||
|
||||
pub fn from_percentage_value(value: u64) -> Result<Self, ContractsCommonError> {
|
||||
Percent::new(Decimal::percent(value))
|
||||
}
|
||||
|
||||
pub fn value(&self) -> Decimal {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn round_to_integer(&self) -> u8 {
|
||||
let hundred = Decimal::from_ratio(100u32, 1u32);
|
||||
// we know the cast from u128 to u8 is a safe one since the internal value must be within 0 - 1 range
|
||||
truncate_decimal(hundred * self.0).u128() as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Percent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0;
|
||||
write!(f, "{}%", adjusted)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Percent {
|
||||
type Err = ContractsCommonError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Percent::new(Decimal::from_str(s)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Decimal> for Percent {
|
||||
type Output = Decimal;
|
||||
|
||||
fn mul(self, rhs: Decimal) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Percent> for Decimal {
|
||||
type Output = Decimal;
|
||||
|
||||
fn mul(self, rhs: Percent) -> Self::Output {
|
||||
rhs * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Uint128> for Percent {
|
||||
type Output = Uint128;
|
||||
|
||||
fn mul(self, rhs: Uint128) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
// implement custom Deserialize because we want to validate Percent has the correct range
|
||||
fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v = Decimal::deserialize(deserializer)?;
|
||||
if v > Decimal::one() {
|
||||
Err(D::Error::custom(
|
||||
"provided decimal percent is larger than 100%",
|
||||
))
|
||||
} else {
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: there's no reason this couldn't be used for proper binaries, but in that case
|
||||
// perhaps the struct should get renamed and moved to a "more" common crate
|
||||
@@ -31,3 +152,47 @@ pub struct ContractBuildInformation {
|
||||
/// Provides the rustc version that was used for the build, for example `1.52.0-nightly`.
|
||||
pub rustc_version: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn percent_serde() {
|
||||
let valid_value = Percent::from_percentage_value(80).unwrap();
|
||||
let serialized = serde_json::to_string(&valid_value).unwrap();
|
||||
|
||||
let deserialized: Percent = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(valid_value, deserialized);
|
||||
|
||||
let invalid_values = vec!["\"42\"", "\"1.1\"", "\"1.00000001\"", "\"foomp\"", "\"1a\""];
|
||||
for invalid_value in invalid_values {
|
||||
assert!(serde_json::from_str::<'_, Percent>(invalid_value).is_err())
|
||||
}
|
||||
assert_eq!(
|
||||
serde_json::from_str::<'_, Percent>("\"0.95\"").unwrap(),
|
||||
Percent::from_percentage_value(95).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn percent_to_absolute_integer() {
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0001\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 0);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0099\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 0);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0199\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 1);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.45123\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 45);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.999999999\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 99);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"1.00\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
|
||||
pub const TOKEN_SUPPLY: Uint128 = Uint128::new(1_000_000_000_000_000);
|
||||
use cosmwasm_std::Decimal;
|
||||
|
||||
// I'm still not 100% sure how to feel about existence of this file
|
||||
// This is equivalent of representing our display coin with 6 decimal places.
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::constants::TOKEN_SUPPLY;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::{Addr, MixId};
|
||||
use cosmwasm_std::{Coin, Decimal, StdResult};
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -62,11 +60,6 @@ impl Delegation {
|
||||
height: u64,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
amount.amount <= TOKEN_SUPPLY,
|
||||
"delegation cannot be larger than the token supply"
|
||||
);
|
||||
|
||||
Delegation {
|
||||
owner,
|
||||
mix_id,
|
||||
@@ -94,8 +87,10 @@ impl Delegation {
|
||||
(mix_id, owner_proxy_subkey)
|
||||
}
|
||||
|
||||
pub fn dec_amount(&self) -> StdResult<Decimal> {
|
||||
self.amount.amount.into_base_decimal()
|
||||
pub fn dec_amount(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
Decimal::from_atomics(self.amount.amount, 0).unwrap()
|
||||
}
|
||||
|
||||
pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
|
||||
|
||||
@@ -13,9 +13,6 @@ pub enum MixnetContractError {
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
|
||||
#[error("Provided percent value is greater than 100%")]
|
||||
InvalidPercent,
|
||||
|
||||
#[error("Attempted to subtract decimals with overflow ({minuend}.sub({subtrahend}))")]
|
||||
OverflowDecimalSubtraction {
|
||||
minuend: Decimal,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Decimal, StdError, StdResult, Uint128};
|
||||
use cosmwasm_std::Decimal;
|
||||
|
||||
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
|
||||
let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128));
|
||||
@@ -11,23 +11,3 @@ pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
|
||||
assert!(b - a < epsilon, "{} != {}", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_base_decimal(val: impl Into<Uint128>) -> StdResult<Decimal> {
|
||||
val.into_base_decimal()
|
||||
}
|
||||
|
||||
pub trait IntoBaseDecimal {
|
||||
fn into_base_decimal(self) -> StdResult<Decimal>;
|
||||
}
|
||||
|
||||
impl<T> IntoBaseDecimal for T
|
||||
where
|
||||
T: Into<Uint128>,
|
||||
{
|
||||
fn into_base_decimal(self) -> StdResult<Decimal> {
|
||||
let atomics = self.into();
|
||||
Decimal::from_atomics(atomics, 0).map_err(|_| StdError::GenericErr {
|
||||
msg: format!("Decimal range exceeded for {atomics} with 0 decimal places."),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ pub struct Interval {
|
||||
// TODO add a better TS type generation
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||
#[serde(with = "string_rfc3339_offset_date_time")]
|
||||
// note: the `ts-rs failed to parse this attribute. It will be ignored.` warning emitted during
|
||||
// compilation is fine (I guess). `ts-rs` can't handle `with` serde attribute, but that's okay
|
||||
// since we explicitly specified this field should correspond to typescript's string
|
||||
current_epoch_start: OffsetDateTime,
|
||||
current_epoch_id: EpochId,
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "{ secs: number; nanos: number; }"))]
|
||||
@@ -142,17 +145,14 @@ impl JsonSchema for Interval {
|
||||
impl Interval {
|
||||
/// Initialize epoch in the contract with default values.
|
||||
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self {
|
||||
// if this fails it means the value provided from the chain itself (via cosmwasm) is invalid,
|
||||
// so we really have to panic here as anything beyond that point would be invalid anyway
|
||||
#[allow(clippy::expect_used)]
|
||||
let current_epoch_start =
|
||||
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
|
||||
.expect("The timestamp provided via env.block.time is invalid");
|
||||
|
||||
Interval {
|
||||
id: 0,
|
||||
epochs_in_interval,
|
||||
current_epoch_start,
|
||||
// I really don't see a way for this to fail, unless the blockchain is lying to us
|
||||
current_epoch_start: OffsetDateTime::from_unix_timestamp(
|
||||
env.block.time.seconds() as i64
|
||||
)
|
||||
.expect("Invalid timestamp from env.block.time"),
|
||||
current_epoch_id: 0,
|
||||
epoch_length,
|
||||
total_elapsed_epochs: 0,
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
mod constants;
|
||||
pub mod delegation;
|
||||
pub mod error;
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
|
||||
use crate::constants::UNIT_DELEGATION_BASE;
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::reward_params::{NodeRewardParams, RewardingParams};
|
||||
use crate::rewarding::helpers::truncate_reward;
|
||||
use crate::rewarding::RewardDistribution;
|
||||
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
@@ -65,7 +64,7 @@ impl MixNodeDetails {
|
||||
self.rewarding_details.pending_operator_reward(pledge)
|
||||
}
|
||||
|
||||
pub fn pending_detailed_operator_reward(&self) -> StdResult<Decimal> {
|
||||
pub fn pending_detailed_operator_reward(&self) -> Decimal {
|
||||
let pledge = self.original_pledge();
|
||||
self.rewarding_details
|
||||
.pending_detailed_operator_reward(pledge)
|
||||
@@ -108,21 +107,16 @@ impl MixNodeRewarding {
|
||||
cost_params: MixNodeCostParams,
|
||||
initial_pledge: &Coin,
|
||||
current_epoch: EpochId,
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
assert!(
|
||||
initial_pledge.amount <= TOKEN_SUPPLY,
|
||||
"pledge cannot be larger than the token supply"
|
||||
);
|
||||
|
||||
Ok(MixNodeRewarding {
|
||||
) -> Self {
|
||||
MixNodeRewarding {
|
||||
cost_params,
|
||||
operator: initial_pledge.amount.into_base_decimal()?,
|
||||
operator: Decimal::from_atomics(initial_pledge.amount, 0).unwrap(),
|
||||
delegates: Decimal::zero(),
|
||||
total_unit_reward: Decimal::zero(),
|
||||
unit_delegation: UNIT_DELEGATION_BASE,
|
||||
last_rewarded_epoch: current_epoch,
|
||||
unique_delegations: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines whether this node is still bonded. This is performed via a simple check,
|
||||
@@ -141,30 +135,27 @@ impl MixNodeRewarding {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> StdResult<Decimal> {
|
||||
let initial_dec = original_pledge.amount.into_base_decimal()?;
|
||||
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> Decimal {
|
||||
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
|
||||
if initial_dec > self.operator {
|
||||
panic!(
|
||||
"seems slashing has occurred while it has not been implemented nor accounted for!"
|
||||
)
|
||||
}
|
||||
Ok(self.operator - initial_dec)
|
||||
self.operator - initial_dec
|
||||
}
|
||||
|
||||
pub fn operator_pledge_with_reward(&self, denom: impl Into<String>) -> Coin {
|
||||
truncate_reward(self.operator, denom)
|
||||
}
|
||||
|
||||
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
|
||||
let delegator_reward = self.determine_delegation_reward(delegation)?;
|
||||
Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
|
||||
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> Coin {
|
||||
let delegator_reward = self.determine_delegation_reward(delegation);
|
||||
truncate_reward(delegator_reward, &delegation.amount.denom)
|
||||
}
|
||||
|
||||
pub fn withdraw_operator_reward(
|
||||
&mut self,
|
||||
original_pledge: &Coin,
|
||||
) -> Result<Coin, MixnetContractError> {
|
||||
let initial_dec = original_pledge.amount.into_base_decimal()?;
|
||||
pub fn withdraw_operator_reward(&mut self, original_pledge: &Coin) -> Coin {
|
||||
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
|
||||
if initial_dec > self.operator {
|
||||
panic!(
|
||||
"seems slashing has occurred while it has not been implemented nor accounted for!"
|
||||
@@ -173,14 +164,14 @@ impl MixNodeRewarding {
|
||||
let diff = self.operator - initial_dec;
|
||||
self.operator = initial_dec;
|
||||
|
||||
Ok(truncate_reward(diff, &original_pledge.denom))
|
||||
truncate_reward(diff, &original_pledge.denom)
|
||||
}
|
||||
|
||||
pub fn withdraw_delegator_reward(
|
||||
&mut self,
|
||||
delegation: &mut Delegation,
|
||||
) -> Result<Coin, MixnetContractError> {
|
||||
let reward = self.determine_delegation_reward(delegation)?;
|
||||
let reward = self.determine_delegation_reward(delegation);
|
||||
self.decrease_delegates_decimal(reward)?;
|
||||
|
||||
delegation.cumulative_reward_ratio = self.full_reward_ratio();
|
||||
@@ -310,27 +301,23 @@ impl MixNodeRewarding {
|
||||
self.distribute_rewards(reward_distribution, absolute_epoch_id)
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> StdResult<Decimal> {
|
||||
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
|
||||
let starting_ratio = delegation.cumulative_reward_ratio;
|
||||
let ending_ratio = self.full_reward_ratio();
|
||||
let adjust = starting_ratio + self.unit_delegation;
|
||||
|
||||
Ok((ending_ratio - starting_ratio) * delegation.dec_amount()? / adjust)
|
||||
(ending_ratio - starting_ratio) * delegation.dec_amount() / adjust
|
||||
}
|
||||
|
||||
// this updates `unique_delegations` field
|
||||
pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> {
|
||||
self.increase_delegates_uint128(amount)?;
|
||||
pub fn add_base_delegation(&mut self, amount: Uint128) {
|
||||
self.increase_delegates_uint128(amount);
|
||||
self.unique_delegations += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn increase_delegates_uint128(
|
||||
&mut self,
|
||||
amount: Uint128,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
self.delegates += amount.into_base_decimal()?;
|
||||
Ok(())
|
||||
pub fn increase_delegates_uint128(&mut self, amount: Uint128) {
|
||||
// the unwrap here is fine as the value is guaranteed to fit under provided constraints
|
||||
self.delegates += Decimal::from_atomics(amount, 0).unwrap()
|
||||
}
|
||||
|
||||
// this updates `unique_delegations` field
|
||||
@@ -348,7 +335,7 @@ impl MixNodeRewarding {
|
||||
&mut self,
|
||||
amount: Uint128,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let amount_dec = amount.into_base_decimal()?;
|
||||
let amount_dec = Decimal::from_atomics(amount, 0).unwrap();
|
||||
self.decrease_delegates_decimal(amount_dec)
|
||||
}
|
||||
|
||||
@@ -381,8 +368,8 @@ impl MixNodeRewarding {
|
||||
}
|
||||
|
||||
pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> {
|
||||
let reward = self.determine_delegation_reward(delegation)?;
|
||||
let full_amount = reward + delegation.dec_amount()?;
|
||||
let reward = self.determine_delegation_reward(delegation);
|
||||
let full_amount = reward + delegation.dec_amount();
|
||||
self.remove_delegation_decimal(full_amount)?;
|
||||
Ok(truncate_reward(full_amount, &delegation.amount.denom))
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::delegation::OwnerProxySubKey;
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use crate::reward_params::{
|
||||
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
|
||||
@@ -33,6 +31,7 @@ pub struct InitialRewardingParams {
|
||||
pub initial_reward_pool: Decimal,
|
||||
pub initial_staking_supply: Decimal,
|
||||
|
||||
pub staking_supply_scale_factor: Percent,
|
||||
pub sybil_resistance: Percent,
|
||||
pub active_set_work_factor: Decimal,
|
||||
pub interval_pool_emission: Percent,
|
||||
@@ -42,20 +41,18 @@ pub struct InitialRewardingParams {
|
||||
}
|
||||
|
||||
impl InitialRewardingParams {
|
||||
pub fn into_rewarding_params(
|
||||
self,
|
||||
epochs_in_interval: u32,
|
||||
) -> Result<RewardingParams, MixnetContractError> {
|
||||
pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams {
|
||||
let epoch_reward_budget = self.initial_reward_pool
|
||||
/ epochs_in_interval.into_base_decimal()?
|
||||
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
|
||||
* self.interval_pool_emission;
|
||||
let stake_saturation_point =
|
||||
self.initial_staking_supply / self.rewarded_set_size.into_base_decimal()?;
|
||||
self.initial_staking_supply / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
|
||||
|
||||
Ok(RewardingParams {
|
||||
RewardingParams {
|
||||
interval: IntervalRewardParams {
|
||||
reward_pool: self.initial_reward_pool,
|
||||
staking_supply: self.initial_staking_supply,
|
||||
staking_supply_scale_factor: self.staking_supply_scale_factor,
|
||||
epoch_reward_budget,
|
||||
stake_saturation_point,
|
||||
sybil_resistance: self.sybil_resistance,
|
||||
@@ -64,7 +61,7 @@ impl InitialRewardingParams {
|
||||
},
|
||||
rewarded_set_size: self.rewarded_set_size,
|
||||
active_set_size: self.active_set_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::{error::MixnetContractError, Percent};
|
||||
use cosmwasm_std::Decimal;
|
||||
use schemars::JsonSchema;
|
||||
@@ -27,6 +26,11 @@ pub struct IntervalRewardParams {
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||
pub staking_supply: Decimal,
|
||||
|
||||
/// Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set.
|
||||
/// Also known as `beta`.
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||
pub staking_supply_scale_factor: Percent,
|
||||
|
||||
// computed values
|
||||
/// Current value of the computed reward budget per epoch, per node.
|
||||
/// It is expected to be constant throughout the interval.
|
||||
@@ -106,33 +110,24 @@ impl RewardingParams {
|
||||
pub fn dec_rewarded_set_size(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.rewarded_set_size.into_base_decimal().unwrap()
|
||||
Decimal::from_atomics(self.rewarded_set_size, 0).unwrap()
|
||||
}
|
||||
|
||||
pub fn dec_active_set_size(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.active_set_size.into_base_decimal().unwrap()
|
||||
Decimal::from_atomics(self.active_set_size, 0).unwrap()
|
||||
}
|
||||
|
||||
fn dec_standby_set_size(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
#[allow(clippy::unwrap_used)]
|
||||
(self.rewarded_set_size - self.active_set_size)
|
||||
.into_base_decimal()
|
||||
.unwrap()
|
||||
Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap()
|
||||
}
|
||||
|
||||
pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) {
|
||||
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let new_epochs_in_interval = new_epochs_in_interval.into_base_decimal().unwrap();
|
||||
|
||||
self.interval.epoch_reward_budget = self.interval.reward_pool / new_epochs_in_interval
|
||||
self.interval.epoch_reward_budget = self.interval.reward_pool
|
||||
/ Decimal::from_atomics(new_epochs_in_interval, 0).unwrap()
|
||||
* self.interval.interval_pool_emission;
|
||||
}
|
||||
|
||||
@@ -174,6 +169,10 @@ impl RewardingParams {
|
||||
self.interval.staking_supply = staking_supply;
|
||||
}
|
||||
|
||||
if let Some(staking_supply_scale_factor) = updates.staking_supply_scale_factor {
|
||||
self.interval.staking_supply_scale_factor = staking_supply_scale_factor
|
||||
}
|
||||
|
||||
if let Some(sybil_resistance_percent) = updates.sybil_resistance_percent {
|
||||
self.interval.sybil_resistance = sybil_resistance_percent;
|
||||
}
|
||||
@@ -201,13 +200,13 @@ impl RewardingParams {
|
||||
|
||||
if recompute_epoch_budget {
|
||||
self.interval.epoch_reward_budget = self.interval.reward_pool
|
||||
/ epochs_in_interval.into_base_decimal()?
|
||||
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
|
||||
* self.interval.interval_pool_emission;
|
||||
}
|
||||
|
||||
if recompute_saturation_point {
|
||||
self.interval.stake_saturation_point =
|
||||
self.interval.staking_supply / self.rewarded_set_size.into_base_decimal()?
|
||||
self.interval.stake_saturation_point = self.interval.staking_supply
|
||||
/ Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -245,6 +244,9 @@ pub struct IntervalRewardingParamsUpdate {
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
|
||||
pub staking_supply: Option<Decimal>,
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
|
||||
pub staking_supply_scale_factor: Option<Percent>,
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
|
||||
pub sybil_resistance_percent: Option<Percent>,
|
||||
|
||||
@@ -262,6 +264,7 @@ impl IntervalRewardingParamsUpdate {
|
||||
// essentially at least a single field has to be a `Some`
|
||||
self.reward_pool.is_some()
|
||||
|| self.staking_supply.is_some()
|
||||
|| self.staking_supply_scale_factor.is_some()
|
||||
|| self.sybil_resistance_percent.is_some()
|
||||
|| self.active_set_work_factor.is_some()
|
||||
|| self.interval_pool_emission.is_some()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use contracts_common::truncate_decimal;
|
||||
use cosmwasm_std::{Coin, Decimal, Uint128};
|
||||
|
||||
/// Truncates all decimal points so that the reward would fit in a `Coin` and so that we would
|
||||
@@ -17,7 +18,3 @@ pub fn truncate_reward(reward: Decimal, denom: impl Into<String>) -> Coin {
|
||||
pub fn truncate_reward_amount(reward: Decimal) -> Uint128 {
|
||||
truncate_decimal(reward)
|
||||
}
|
||||
|
||||
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
|
||||
amount * Uint128::new(1)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub struct RewardEstimate {
|
||||
pub operating_cost: Decimal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct RewardDistribution {
|
||||
pub operator: Decimal,
|
||||
pub delegates: Decimal,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::reward_params::NodeRewardParams;
|
||||
use crate::rewarding::simulator::simulated_node::SimulatedNode;
|
||||
use crate::rewarding::RewardDistribution;
|
||||
@@ -34,27 +33,30 @@ impl Simulator {
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_epoch(&mut self) -> Result<(), MixnetContractError> {
|
||||
fn advance_epoch(&mut self) {
|
||||
let updated = self.interval.advance_epoch();
|
||||
|
||||
// we rolled over an interval
|
||||
if self.interval.current_interval_id() + 1 == updated.current_interval_id() {
|
||||
let old = self.system_rewarding_params.interval;
|
||||
let reward_pool = old.reward_pool - self.pending_reward_pool_emission;
|
||||
let staking_supply = old.staking_supply + self.pending_reward_pool_emission;
|
||||
let staking_supply = old.staking_supply
|
||||
+ self
|
||||
.system_rewarding_params
|
||||
.interval
|
||||
.staking_supply_scale_factor
|
||||
* self.pending_reward_pool_emission;
|
||||
let epoch_reward_budget = reward_pool
|
||||
/ self.interval.epochs_in_interval().into_base_decimal()?
|
||||
/ Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap()
|
||||
* old.interval_pool_emission.value();
|
||||
let stake_saturation_point = staking_supply
|
||||
/ self
|
||||
.system_rewarding_params
|
||||
.rewarded_set_size
|
||||
.into_base_decimal()?;
|
||||
/ Decimal::from_atomics(self.system_rewarding_params.rewarded_set_size, 0).unwrap();
|
||||
|
||||
let updated_params = RewardingParams {
|
||||
interval: IntervalRewardParams {
|
||||
reward_pool,
|
||||
staking_supply,
|
||||
staking_supply_scale_factor: old.staking_supply_scale_factor,
|
||||
epoch_reward_budget,
|
||||
stake_saturation_point,
|
||||
sybil_resistance: old.sybil_resistance,
|
||||
@@ -69,15 +71,9 @@ impl Simulator {
|
||||
self.pending_reward_pool_emission = Decimal::zero();
|
||||
}
|
||||
self.interval = updated;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bond(
|
||||
&mut self,
|
||||
pledge: Coin,
|
||||
cost_params: MixNodeCostParams,
|
||||
) -> Result<MixId, MixnetContractError> {
|
||||
pub fn bond(&mut self, pledge: Coin, cost_params: MixNodeCostParams) -> MixId {
|
||||
let mix_id = self.next_mix_id;
|
||||
|
||||
self.nodes.insert(
|
||||
@@ -87,24 +83,16 @@ impl Simulator {
|
||||
cost_params,
|
||||
&pledge,
|
||||
self.interval.current_epoch_absolute_id(),
|
||||
)?,
|
||||
),
|
||||
);
|
||||
|
||||
self.next_mix_id += 1;
|
||||
|
||||
Ok(mix_id)
|
||||
mix_id
|
||||
}
|
||||
|
||||
pub fn delegate<S: Into<String>>(
|
||||
&mut self,
|
||||
delegator: S,
|
||||
delegation: Coin,
|
||||
mix_id: MixId,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let node = self
|
||||
.nodes
|
||||
.get_mut(&mix_id)
|
||||
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
|
||||
pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin, mix_id: MixId) {
|
||||
let node = self.nodes.get_mut(&mix_id).expect("node doesn't exist");
|
||||
node.delegate(delegator, delegation)
|
||||
}
|
||||
|
||||
@@ -115,35 +103,23 @@ impl Simulator {
|
||||
delegator: S,
|
||||
mix_id: MixId,
|
||||
) -> Result<(Coin, Coin), MixnetContractError> {
|
||||
let node = self
|
||||
.nodes
|
||||
.get_mut(&mix_id)
|
||||
.ok_or(MixnetContractError::MixNodeBondNotFound { mix_id })?;
|
||||
let node = self.nodes.get_mut(&mix_id).expect("node not found");
|
||||
node.undelegate(delegator)
|
||||
}
|
||||
|
||||
pub fn simulate_epoch_single_node(
|
||||
&mut self,
|
||||
params: NodeRewardParams,
|
||||
) -> Result<RewardDistribution, MixnetContractError> {
|
||||
pub fn simulate_epoch_single_node(&mut self, params: NodeRewardParams) -> RewardDistribution {
|
||||
assert_eq!(self.nodes.len(), 1);
|
||||
|
||||
if let Some(&id) = self.nodes.keys().next() {
|
||||
let mut params_map = BTreeMap::new();
|
||||
params_map.insert(id, params);
|
||||
Ok(self
|
||||
.simulate_epoch(¶ms_map)?
|
||||
.remove(&id)
|
||||
.unwrap_or_default())
|
||||
} else {
|
||||
Ok(RewardDistribution::default())
|
||||
}
|
||||
let id = *self.nodes.keys().next().unwrap();
|
||||
let mut params_map = BTreeMap::new();
|
||||
params_map.insert(id, params);
|
||||
self.simulate_epoch(¶ms_map).remove(&id).unwrap()
|
||||
}
|
||||
|
||||
pub fn simulate_epoch(
|
||||
&mut self,
|
||||
node_params: &BTreeMap<MixId, NodeRewardParams>,
|
||||
) -> Result<BTreeMap<MixId, RewardDistribution>, MixnetContractError> {
|
||||
) -> BTreeMap<MixId, RewardDistribution> {
|
||||
let mut params_keys = node_params.keys().copied().collect::<Vec<_>>();
|
||||
params_keys.sort_unstable();
|
||||
let mut node_keys = self.nodes.keys().copied().collect::<Vec<_>>();
|
||||
@@ -171,41 +147,34 @@ impl Simulator {
|
||||
dist.insert(*mix_id, reward_distribution);
|
||||
}
|
||||
|
||||
self.advance_epoch()?;
|
||||
Ok(dist)
|
||||
self.advance_epoch();
|
||||
dist
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(
|
||||
&self,
|
||||
delegation: &Delegation,
|
||||
) -> Result<Decimal, MixnetContractError> {
|
||||
Ok(self.nodes[&delegation.mix_id]
|
||||
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
|
||||
self.nodes[&delegation.mix_id]
|
||||
.rewarding_details
|
||||
.determine_delegation_reward(delegation)?)
|
||||
.determine_delegation_reward(delegation)
|
||||
}
|
||||
|
||||
pub fn determine_total_delegation_reward(&self) -> Result<Decimal, MixnetContractError> {
|
||||
pub fn determine_total_delegation_reward(&self) -> Decimal {
|
||||
let mut total = Decimal::zero();
|
||||
|
||||
for node in self.nodes.values() {
|
||||
for delegation in node.delegations.values() {
|
||||
total += node
|
||||
.rewarding_details
|
||||
.determine_delegation_reward(delegation)?
|
||||
.determine_delegation_reward(delegation)
|
||||
}
|
||||
}
|
||||
Ok(total)
|
||||
total
|
||||
}
|
||||
|
||||
// assume node state doesn't change in the interval (kinda unrealistic)
|
||||
pub fn simulate_full_interval(
|
||||
&mut self,
|
||||
node_params: &BTreeMap<MixId, NodeRewardParams>,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
pub fn simulate_full_interval(&mut self, node_params: &BTreeMap<MixId, NodeRewardParams>) {
|
||||
for _ in 0..self.interval.epochs_in_interval() {
|
||||
self.simulate_epoch(node_params)?;
|
||||
self.simulate_epoch(node_params);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,10 +197,6 @@ mod tests {
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use std::time::Duration;
|
||||
|
||||
// explicitly marking this as part of #[allow(clippy::unwrap_used)] until
|
||||
// https://github.com/rust-lang/rust-clippy/pull/9686
|
||||
// is merged into a release
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn base_simulator(initial_pledge: u128) -> Simulator {
|
||||
let profit_margin = Percent::from_percentage_value(10).unwrap();
|
||||
let interval_operating_cost = Coin::new(40_000_000, "unym");
|
||||
@@ -250,6 +215,7 @@ mod tests {
|
||||
interval: IntervalRewardParams {
|
||||
reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(), // 250M * 1M (we're expressing it all in base tokens)
|
||||
staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(), // 100M * 1M
|
||||
staking_supply_scale_factor: Percent::hundred(),
|
||||
epoch_reward_budget,
|
||||
stake_saturation_point,
|
||||
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
||||
@@ -272,32 +238,20 @@ mod tests {
|
||||
profit_margin_percent: profit_margin,
|
||||
interval_operating_cost,
|
||||
};
|
||||
simulator.bond(initial_pledge, cost_params).unwrap();
|
||||
simulator.bond(initial_pledge, cost_params);
|
||||
simulator
|
||||
}
|
||||
|
||||
// essentially our delegations + estimated rewards HAVE TO equal to what we actually determined
|
||||
//
|
||||
// explicitly marking this as part of #[allow(clippy::unwrap_used)] until
|
||||
// https://github.com/rust-lang/rust-clippy/pull/9686
|
||||
// is merged into a release
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn check_rewarding_invariant(simulator: &Simulator) {
|
||||
for node in simulator.nodes.values() {
|
||||
let delegation_sum: Decimal = node
|
||||
.delegations
|
||||
.values()
|
||||
.map(|d| d.dec_amount().unwrap())
|
||||
.sum();
|
||||
let delegation_sum: Decimal =
|
||||
node.delegations.values().map(|d| d.dec_amount()).sum();
|
||||
|
||||
let reward_sum: Decimal = node
|
||||
.delegations
|
||||
.values()
|
||||
.map(|d| {
|
||||
node.rewarding_details
|
||||
.determine_delegation_reward(d)
|
||||
.unwrap()
|
||||
})
|
||||
.map(|d| node.rewarding_details.determine_delegation_reward(d))
|
||||
.sum();
|
||||
|
||||
// let reward_sum = simulator.determine_total_delegation_reward();
|
||||
@@ -315,7 +269,7 @@ mod tests {
|
||||
|
||||
let epoch_params =
|
||||
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
|
||||
let rewards = simulator.simulate_epoch_single_node(epoch_params).unwrap();
|
||||
let rewards = simulator.simulate_epoch_single_node(epoch_params);
|
||||
|
||||
assert_eq!(rewards.delegates, Decimal::zero());
|
||||
compare_decimals(
|
||||
@@ -328,13 +282,11 @@ mod tests {
|
||||
#[test]
|
||||
fn single_delegation_at_genesis() {
|
||||
let mut simulator = base_simulator(10000_000000);
|
||||
simulator
|
||||
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
|
||||
|
||||
let node_params =
|
||||
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
|
||||
let rewards = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let rewards = simulator.simulate_epoch_single_node(node_params);
|
||||
|
||||
compare_decimals(
|
||||
rewards.delegates,
|
||||
@@ -345,7 +297,7 @@ mod tests {
|
||||
|
||||
compare_decimals(
|
||||
rewards.delegates,
|
||||
simulator.determine_total_delegation_reward().unwrap(),
|
||||
simulator.determine_total_delegation_reward(),
|
||||
None,
|
||||
);
|
||||
let node = &simulator.nodes[&0];
|
||||
@@ -365,22 +317,20 @@ mod tests {
|
||||
let node_params =
|
||||
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
|
||||
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params);
|
||||
let expected_operator1 = "1128452.5416104363".parse().unwrap();
|
||||
assert_eq!(rewards1.delegates, Decimal::zero());
|
||||
compare_decimals(rewards1.operator, expected_operator1, None);
|
||||
|
||||
simulator
|
||||
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
|
||||
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params);
|
||||
let expected_operator2 = "1363843.413584609".parse().unwrap();
|
||||
let expected_delegator_reward1 = "1795952.25874404".parse().unwrap();
|
||||
compare_decimals(rewards2.delegates, expected_delegator_reward1, None);
|
||||
compare_decimals(rewards2.operator, expected_operator2, None);
|
||||
|
||||
let rewards3 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let rewards3 = simulator.simulate_epoch_single_node(node_params);
|
||||
let expected_operator3 = "1364017.7824440491".parse().unwrap();
|
||||
let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap();
|
||||
compare_decimals(rewards3.delegates, expected_delegator_reward2, None);
|
||||
@@ -414,15 +364,11 @@ mod tests {
|
||||
|
||||
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
|
||||
// delegating at different times still work)
|
||||
simulator
|
||||
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
|
||||
simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
|
||||
|
||||
// "normal", sanity check rewarding
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params);
|
||||
let expected_operator1 = "1411087.1007647323".parse().unwrap();
|
||||
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
|
||||
compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
|
||||
@@ -432,15 +378,14 @@ mod tests {
|
||||
let node = simulator.nodes.get_mut(&0).unwrap();
|
||||
let reward = node
|
||||
.rewarding_details
|
||||
.withdraw_operator_reward(&original_pledge)
|
||||
.unwrap();
|
||||
.withdraw_operator_reward(&original_pledge);
|
||||
assert_eq!(reward.amount, truncate_reward_amount(expected_operator1));
|
||||
assert_eq!(
|
||||
node.rewarding_details.operator,
|
||||
Decimal::from_atomics(original_pledge.amount, 0).unwrap()
|
||||
);
|
||||
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params);
|
||||
let expected_operator2 = "1411113.0004067947".parse().unwrap();
|
||||
let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap();
|
||||
compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
|
||||
@@ -457,15 +402,11 @@ mod tests {
|
||||
|
||||
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
|
||||
// delegating at different times still work)
|
||||
simulator
|
||||
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
|
||||
.unwrap();
|
||||
simulator.delegate("alice", Coin::new(18000_000000, "unym"), 0);
|
||||
simulator.delegate("bob", Coin::new(4000_000000, "unym"), 0);
|
||||
|
||||
// "normal", sanity check rewarding
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let rewards1 = simulator.simulate_epoch_single_node(node_params);
|
||||
let expected_operator1 = "1411087.1007647323".parse().unwrap();
|
||||
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
|
||||
compare_decimals(rewards1.delegates, expected_delegator_reward1, None);
|
||||
@@ -483,7 +424,7 @@ mod tests {
|
||||
assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward));
|
||||
|
||||
// new reward after withdrawal
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
let rewards2 = simulator.simulate_epoch_single_node(node_params);
|
||||
let expected_operator2 = "1411250.1907492676".parse().unwrap();
|
||||
let expected_delegator_reward2 = "2200004.051009689".parse().unwrap();
|
||||
compare_decimals(rewards2.delegates, expected_delegator_reward2, None);
|
||||
@@ -526,30 +467,22 @@ mod tests {
|
||||
let mut performance = Percent::from_percentage_value(100).unwrap();
|
||||
for epoch in 0..720 {
|
||||
if epoch == 0 {
|
||||
simulator
|
||||
.delegate("a", Coin::new(18000_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
simulator.delegate("a", Coin::new(18000_000000, "unym"), 0)
|
||||
}
|
||||
if epoch == 42 {
|
||||
simulator
|
||||
.delegate("b", Coin::new(2000_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
simulator.delegate("b", Coin::new(2000_000000, "unym"), 0)
|
||||
}
|
||||
if epoch == 89 {
|
||||
is_active = false;
|
||||
}
|
||||
if epoch == 123 {
|
||||
simulator
|
||||
.delegate("c", Coin::new(6666_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
simulator.delegate("c", Coin::new(6666_000000, "unym"), 0)
|
||||
}
|
||||
if epoch == 167 {
|
||||
performance = Percent::from_percentage_value(90).unwrap();
|
||||
}
|
||||
if epoch == 245 {
|
||||
simulator
|
||||
.delegate("d", Coin::new(2050_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
simulator.delegate("d", Coin::new(2050_000000, "unym"), 0)
|
||||
}
|
||||
if epoch == 264 {
|
||||
let (delegation, _reward) = simulator.undelegate("b", 0).unwrap();
|
||||
@@ -570,15 +503,13 @@ mod tests {
|
||||
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
|
||||
}
|
||||
if epoch == 545 {
|
||||
simulator
|
||||
.delegate("e", Coin::new(5000_000000, "unym"), 0)
|
||||
.unwrap()
|
||||
simulator.delegate("e", Coin::new(5000_000000, "unym"), 0)
|
||||
}
|
||||
|
||||
// this has to always hold
|
||||
check_rewarding_invariant(&simulator);
|
||||
let node_params = NodeRewardParams::new(performance, is_active);
|
||||
simulator.simulate_epoch_single_node(node_params).unwrap();
|
||||
simulator.simulate_epoch_single_node(node_params);
|
||||
}
|
||||
|
||||
// after everyone undelegates, there should be nothing left in the delegates pool
|
||||
@@ -613,6 +544,7 @@ mod tests {
|
||||
interval: IntervalRewardParams {
|
||||
reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(),
|
||||
staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(),
|
||||
staking_supply_scale_factor: Percent::hundred(),
|
||||
epoch_reward_budget,
|
||||
stake_saturation_point,
|
||||
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
||||
@@ -631,135 +563,95 @@ mod tests {
|
||||
|
||||
let mut simulator = Simulator::new(rewarding_params, interval);
|
||||
|
||||
let n0 = simulator
|
||||
.bond(
|
||||
Coin::new(11_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0)
|
||||
.unwrap();
|
||||
let n0 = simulator.bond(
|
||||
Coin::new(11_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0);
|
||||
|
||||
let n1 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1)
|
||||
.unwrap();
|
||||
let n1 = simulator.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1);
|
||||
|
||||
let n2 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2)
|
||||
.unwrap();
|
||||
let n2 = simulator.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2);
|
||||
|
||||
let n3 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
|
||||
interval_operating_cost: Coin::new(500_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3)
|
||||
.unwrap();
|
||||
let n3 = simulator.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
|
||||
interval_operating_cost: Coin::new(500_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3);
|
||||
|
||||
let n4 = simulator
|
||||
.bond(
|
||||
Coin::new(1000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4)
|
||||
.unwrap();
|
||||
let n4 = simulator.bond(
|
||||
Coin::new(1000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4);
|
||||
|
||||
let n5 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5)
|
||||
.unwrap();
|
||||
let n5 = simulator.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5);
|
||||
|
||||
let n6 = simulator
|
||||
.bond(
|
||||
Coin::new(11_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6)
|
||||
.unwrap();
|
||||
let n6 = simulator.bond(
|
||||
Coin::new(11_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6);
|
||||
|
||||
let n7 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7)
|
||||
.unwrap();
|
||||
let n7 = simulator.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7);
|
||||
|
||||
let n8 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
|
||||
interval_operating_cost: Coin::new(500_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8)
|
||||
.unwrap();
|
||||
let n8 = simulator.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
|
||||
interval_operating_cost: Coin::new(500_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8);
|
||||
|
||||
let n9 = simulator
|
||||
.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9)
|
||||
.unwrap();
|
||||
let n9 = simulator.bond(
|
||||
Coin::new(1_000_000_000000, "unym"),
|
||||
MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: Coin::new(40_000_000, "unym"),
|
||||
},
|
||||
);
|
||||
simulator.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9);
|
||||
|
||||
let uptime_1 = Percent::from_percentage_value(100).unwrap();
|
||||
let uptime_09 = Percent::from_percentage_value(90).unwrap();
|
||||
@@ -781,7 +673,7 @@ mod tests {
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
for _ in 0..23 {
|
||||
simulator.simulate_full_interval(&node_params).unwrap();
|
||||
simulator.simulate_full_interval(&node_params);
|
||||
}
|
||||
|
||||
// we allow the delta to be within 0.1unym,
|
||||
|
||||
+12
-20
@@ -20,25 +20,21 @@ impl SimulatedNode {
|
||||
cost_params: MixNodeCostParams,
|
||||
initial_pledge: &Coin,
|
||||
current_epoch: EpochId,
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
Ok(SimulatedNode {
|
||||
) -> Self {
|
||||
SimulatedNode {
|
||||
mix_id,
|
||||
rewarding_details: MixNodeRewarding::initialise_new(
|
||||
cost_params,
|
||||
initial_pledge,
|
||||
current_epoch,
|
||||
)?,
|
||||
),
|
||||
delegations: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delegate<S: Into<String>>(
|
||||
&mut self,
|
||||
delegator: S,
|
||||
delegation: Coin,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
pub fn delegate<S: Into<String>>(&mut self, delegator: S, delegation: Coin) {
|
||||
self.rewarding_details
|
||||
.add_base_delegation(delegation.amount)?;
|
||||
.add_base_delegation(delegation.amount);
|
||||
|
||||
let delegator = delegator.into();
|
||||
let delegation = Delegation::new(
|
||||
@@ -51,7 +47,6 @@ impl SimulatedNode {
|
||||
);
|
||||
|
||||
self.delegations.insert(delegator, delegation);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn undelegate<S: Into<String>>(
|
||||
@@ -59,19 +54,16 @@ impl SimulatedNode {
|
||||
delegator: S,
|
||||
) -> Result<(Coin, Coin), MixnetContractError> {
|
||||
let delegator = delegator.into();
|
||||
let delegation = self.delegations.remove(&delegator).ok_or(
|
||||
MixnetContractError::NoMixnodeDelegationFound {
|
||||
mix_id: MixId::MAX,
|
||||
address: delegator,
|
||||
proxy: None,
|
||||
},
|
||||
)?;
|
||||
let delegation = self
|
||||
.delegations
|
||||
.remove(&delegator)
|
||||
.expect("delegation not found");
|
||||
|
||||
let reward = self
|
||||
.rewarding_details
|
||||
.determine_delegation_reward(&delegation)?;
|
||||
.determine_delegation_reward(&delegation);
|
||||
self.rewarding_details
|
||||
.remove_delegation_decimal(delegation.dec_amount()? + reward)?;
|
||||
.remove_delegation_decimal(delegation.dec_amount() + reward)?;
|
||||
|
||||
let reward_denom = &delegation.amount.denom;
|
||||
let truncated_reward = truncate_reward(reward, reward_denom);
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::rewarding::helpers::truncate_decimal;
|
||||
use crate::{Layer, RewardedSetNodeStatus};
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use cosmwasm_std::Addr;
|
||||
use cosmwasm_std::Coin;
|
||||
use schemars::JsonSchema;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::{Index, Mul};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Index;
|
||||
|
||||
// type aliases for better reasoning about available data
|
||||
pub type IdentityKey = String;
|
||||
@@ -24,87 +21,6 @@ pub type BlockHeight = u64;
|
||||
pub type EpochEventId = u32;
|
||||
pub type IntervalEventId = u32;
|
||||
|
||||
/// Percent represents a value between 0 and 100%
|
||||
/// (i.e. between 0.0 and 1.0)
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Serialize, Deserialize, JsonSchema,
|
||||
)]
|
||||
pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
|
||||
|
||||
impl Percent {
|
||||
pub fn new(value: Decimal) -> Result<Self, MixnetContractError> {
|
||||
if value > Decimal::one() {
|
||||
Err(MixnetContractError::InvalidPercent)
|
||||
} else {
|
||||
Ok(Percent(value))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.0 == Decimal::zero()
|
||||
}
|
||||
|
||||
pub fn from_percentage_value(value: u64) -> Result<Self, MixnetContractError> {
|
||||
Percent::new(Decimal::percent(value))
|
||||
}
|
||||
|
||||
pub fn value(&self) -> Decimal {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn round_to_integer(&self) -> u8 {
|
||||
let hundred = Decimal::from_ratio(100u32, 1u32);
|
||||
// we know the cast from u128 to u8 is a safe one since the internal value must be within 0 - 1 range
|
||||
truncate_decimal(hundred * self.0).u128() as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Percent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let adjusted = Decimal::from_ratio(100u32, 1u32) * self.0;
|
||||
write!(f, "{}%", adjusted)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Decimal> for Percent {
|
||||
type Output = Decimal;
|
||||
|
||||
fn mul(self, rhs: Decimal) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Percent> for Decimal {
|
||||
type Output = Decimal;
|
||||
|
||||
fn mul(self, rhs: Percent) -> Self::Output {
|
||||
rhs * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Uint128> for Percent {
|
||||
type Output = Uint128;
|
||||
|
||||
fn mul(self, rhs: Uint128) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
// implement custom Deserialize because we want to validate Percent has the correct range
|
||||
fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v = Decimal::deserialize(deserializer)?;
|
||||
if v > Decimal::one() {
|
||||
Err(D::Error::custom(
|
||||
"provided decimal percent is larger than 100%",
|
||||
))
|
||||
} else {
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct LayerDistribution {
|
||||
pub layer1: u64,
|
||||
@@ -119,10 +35,6 @@ impl LayerDistribution {
|
||||
(Layer::Two, self.layer2),
|
||||
(Layer::Three, self.layer3),
|
||||
];
|
||||
|
||||
// we explicitly put 3 elements into the iterator, so the iterator is DEFINITELY
|
||||
// not empty and thus the unwrap cannot fail
|
||||
#[allow(clippy::unwrap_used)]
|
||||
layers.iter().min_by_key(|x| x.1).unwrap().0
|
||||
}
|
||||
|
||||
@@ -210,48 +122,3 @@ pub struct PagedRewardedSetResponse {
|
||||
pub nodes: Vec<(MixId, RewardedSetNodeStatus)>,
|
||||
pub start_next_after: Option<MixId>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn percent_serde() {
|
||||
let valid_value = Percent::from_percentage_value(80).unwrap();
|
||||
let serialized = serde_json::to_string(&valid_value).unwrap();
|
||||
|
||||
println!("{}", serialized);
|
||||
let deserialized: Percent = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(valid_value, deserialized);
|
||||
|
||||
let invalid_values = vec!["\"42\"", "\"1.1\"", "\"1.00000001\"", "\"foomp\"", "\"1a\""];
|
||||
for invalid_value in invalid_values {
|
||||
assert!(serde_json::from_str::<'_, Percent>(invalid_value).is_err())
|
||||
}
|
||||
assert_eq!(
|
||||
serde_json::from_str::<'_, Percent>("\"0.95\"").unwrap(),
|
||||
Percent::from_percentage_value(95).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn percent_to_absolute_integer() {
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0001\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 0);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0099\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 0);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0199\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 1);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.45123\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 45);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.999999999\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 99);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"1.00\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
mixnet-contract-common = { path = "../mixnet-contract" }
|
||||
contracts-common = { path = "../contracts-common" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
ts-rs = {version = "6.1.2", optional = true}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
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 mixnet_contract_common::MixId;
|
||||
use schemars::JsonSchema;
|
||||
@@ -46,6 +45,34 @@ impl PledgeData {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub enum PledgeCap {
|
||||
Percent(Percent),
|
||||
Absolute(Uint128), // This has to be in unym
|
||||
}
|
||||
|
||||
impl FromStr for PledgeCap {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(cap: &str) -> Result<Self, Self::Err> {
|
||||
let cap = cap.replace('_', "").replace(',', ".");
|
||||
match Percent::from_str(&cap) {
|
||||
Ok(p) => Ok(PledgeCap::Percent(p)),
|
||||
Err(_) => match cap.parse::<u128>() {
|
||||
Ok(i) => Ok(PledgeCap::Absolute(Uint128::from(i))),
|
||||
Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PledgeCap {
|
||||
fn default() -> Self {
|
||||
PledgeCap::Percent(Percent::from_percentage_value(10).expect("This can never fail!"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct OriginalVestingResponse {
|
||||
pub amount: Coin,
|
||||
@@ -102,3 +129,32 @@ pub struct AllDelegationsResponse {
|
||||
pub delegations: Vec<VestingDelegation>,
|
||||
pub start_next_after: Option<(u32, MixId, u64)>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_std::Uint128;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::PledgeCap;
|
||||
|
||||
#[test]
|
||||
fn test_pledge_cap_from_str() {
|
||||
assert_eq!(
|
||||
PledgeCap::from_str("0.1").unwrap(),
|
||||
PledgeCap::Percent(Percent::from_percentage_value(10).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
PledgeCap::from_str("0,1").unwrap(),
|
||||
PledgeCap::Percent(Percent::from_percentage_value(10).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
PledgeCap::from_str("100_000_000_000").unwrap(),
|
||||
PledgeCap::Absolute(Uint128::new(100_000_000_000))
|
||||
);
|
||||
assert_eq!(
|
||||
PledgeCap::from_str("100000000000").unwrap(),
|
||||
PledgeCap::Absolute(Uint128::new(100_000_000_000))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use cosmwasm_std::{Coin, Timestamp, Uint128};
|
||||
use cosmwasm_std::{Coin, Timestamp};
|
||||
use mixnet_contract_common::{
|
||||
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
|
||||
Gateway, MixId, MixNode,
|
||||
@@ -6,6 +6,8 @@ use mixnet_contract_common::{
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::PledgeCap;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct InitMsg {
|
||||
@@ -83,6 +85,7 @@ pub enum ExecuteMsg {
|
||||
owner_address: String,
|
||||
staking_address: Option<String>,
|
||||
vesting_spec: Option<VestingSpecification>,
|
||||
cap: Option<PledgeCap>,
|
||||
},
|
||||
WithdrawVestedCoins {
|
||||
amount: Coin,
|
||||
@@ -120,7 +123,8 @@ pub enum ExecuteMsg {
|
||||
to_address: Option<String>,
|
||||
},
|
||||
UpdateLockedPledgeCap {
|
||||
amount: Uint128,
|
||||
address: String,
|
||||
cap: PledgeCap,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -156,6 +160,7 @@ impl ExecuteMsg {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {
|
||||
GetContractVersion {},
|
||||
LockedCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -201,7 +206,6 @@ pub enum QueryMsg {
|
||||
GetCurrentVestingPeriod {
|
||||
address: String,
|
||||
},
|
||||
GetLockedPledgeCap {},
|
||||
GetDelegationTimes {
|
||||
address: String,
|
||||
mix_id: MixId,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "logging"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.0"
|
||||
pretty_env_logger = "0.4.0"
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// I'd argue we should start transitioning from `log` to `tracing`
|
||||
pub fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("handlebars", log::LevelFilter::Warn)
|
||||
.filter_module("sled", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
@@ -116,7 +116,10 @@ impl SphinxPacketProcessor {
|
||||
trace!("received an ack packet!");
|
||||
Ok((None, data))
|
||||
}
|
||||
PacketSize::RegularPacket | PacketSize::ExtendedPacket => {
|
||||
PacketSize::RegularPacket
|
||||
| PacketSize::ExtendedPacket8
|
||||
| PacketSize::ExtendedPacket16
|
||||
| PacketSize::ExtendedPacket32 => {
|
||||
trace!("received a normal packet!");
|
||||
let (ack_data, message) = self.split_hop_data_into_ack_and_message(data)?;
|
||||
let (ack_first_hop, ack_packet) = SurbAck::try_recover_first_hop_packet(&ack_data)?;
|
||||
|
||||
@@ -10,7 +10,7 @@ pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
|
||||
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
|
||||
|
||||
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
|
||||
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
|
||||
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
|
||||
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
|
||||
|
||||
@@ -25,8 +25,10 @@ impl Display for MixPacketFormattingError {
|
||||
InvalidPacketSize(actual) =>
|
||||
write!(
|
||||
f,
|
||||
"received request had invalid size. (actual: {}, but expected one of: {} (ACK), {} (REGULAR), {} (EXTENDED))",
|
||||
actual, PacketSize::AckPacket.size(), PacketSize::RegularPacket.size(), PacketSize::ExtendedPacket.size()
|
||||
"received request had invalid size. (actual: {}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))",
|
||||
actual, PacketSize::AckPacket.size(), PacketSize::RegularPacket.size(),
|
||||
PacketSize::ExtendedPacket8.size(), PacketSize::ExtendedPacket16.size(),
|
||||
PacketSize::ExtendedPacket32.size()
|
||||
),
|
||||
MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"),
|
||||
InvalidPacketMode => write!(f, "provided packet mode is invalid")
|
||||
|
||||
@@ -6,7 +6,6 @@ use bytes::{Buf, BufMut, BytesMut};
|
||||
use nymsphinx_params::packet_modes::InvalidPacketMode;
|
||||
use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize};
|
||||
use nymsphinx_types::SphinxPacket;
|
||||
use std::convert::TryFrom;
|
||||
use std::io;
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
@@ -75,7 +74,7 @@ impl Decoder for SphinxCodec {
|
||||
if src.is_empty() {
|
||||
// can't do anything if we have no bytes, but let's reserve enough for the most
|
||||
// conservative case, i.e. receiving an ack packet
|
||||
src.reserve(Header::SIZE + PacketSize::AckPacket.size());
|
||||
src.reserve(Header::LEGACY_SIZE + PacketSize::AckPacket.size());
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -87,7 +86,7 @@ impl Decoder for SphinxCodec {
|
||||
};
|
||||
|
||||
let sphinx_packet_size = header.packet_size.size();
|
||||
let frame_len = Header::SIZE + sphinx_packet_size;
|
||||
let frame_len = header.size() + sphinx_packet_size;
|
||||
|
||||
if src.len() < frame_len {
|
||||
// we don't have enough bytes to read the rest of frame
|
||||
@@ -96,7 +95,7 @@ impl Decoder for SphinxCodec {
|
||||
}
|
||||
|
||||
// advance buffer past the header - at this point we have enough bytes
|
||||
src.advance(Header::SIZE);
|
||||
src.advance(header.size());
|
||||
let sphinx_packet_bytes = src.split_to(sphinx_packet_size);
|
||||
let sphinx_packet = match SphinxPacket::from_bytes(&sphinx_packet_bytes) {
|
||||
Ok(sphinx_packet) => sphinx_packet,
|
||||
@@ -115,21 +114,27 @@ impl Decoder for SphinxCodec {
|
||||
// has appropriate capacity in anticipation of future calls to decode.
|
||||
// Failing to do so leads to inefficiency.
|
||||
|
||||
// if we have at least one more byte available, we can reserve enough bytes for
|
||||
// if we have enough bytes to decode the header of the next packet, we can reserve enough bytes for
|
||||
// the entire next frame, if not, we assume the next frame is an ack packet and
|
||||
// reserve for that.
|
||||
// we also assume the next packet coming from the same client will use exactly the same versioning
|
||||
// as the current packet
|
||||
let mut allocate_for_next_packet = header.size() + PacketSize::AckPacket.size();
|
||||
if !src.is_empty() {
|
||||
let next_packet_len = match PacketSize::try_from(src[0]) {
|
||||
Ok(next_packet_len) => next_packet_len,
|
||||
match Header::decode(src) {
|
||||
Ok(Some(next_header)) => {
|
||||
allocate_for_next_packet = next_header.size() + next_header.packet_size.size();
|
||||
}
|
||||
Ok(None) => {
|
||||
// we don't have enough information to know how much to reserve, fallback to the ack case
|
||||
}
|
||||
|
||||
// the next frame will be malformed but let's leave handling the error to the next
|
||||
// call to 'decode', as presumably, the current sphinx packet is still valid
|
||||
Err(_) => return Ok(Some(nymsphinx_packet)),
|
||||
};
|
||||
let next_frame_len = next_packet_len.size() + Header::SIZE;
|
||||
src.reserve(next_frame_len - 1);
|
||||
} else {
|
||||
src.reserve(Header::SIZE + PacketSize::AckPacket.size());
|
||||
}
|
||||
src.reserve(allocate_for_next_packet);
|
||||
|
||||
Ok(Some(nymsphinx_packet))
|
||||
}
|
||||
@@ -199,6 +204,8 @@ mod packet_encoding {
|
||||
#[cfg(test)]
|
||||
mod decode_will_allocate_enough_bytes_for_next_call {
|
||||
use super::*;
|
||||
use nymsphinx_params::packet_version::PacketVersion;
|
||||
use nymsphinx_params::PacketMode;
|
||||
|
||||
#[test]
|
||||
fn for_empty_bytes() {
|
||||
@@ -207,20 +214,23 @@ mod packet_encoding {
|
||||
assert!(SphinxCodec.decode(&mut empty_bytes).unwrap().is_none());
|
||||
assert_eq!(
|
||||
empty_bytes.capacity(),
|
||||
Header::SIZE + PacketSize::AckPacket.size()
|
||||
Header::LEGACY_SIZE + PacketSize::AckPacket.size()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_bytes_with_header() {
|
||||
fn for_bytes_with_legacy_header() {
|
||||
// if header gets decoded there should be enough bytes for the entire frame
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
for packet_size in packet_sizes {
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::Legacy,
|
||||
packet_size,
|
||||
packet_mode: Default::default(),
|
||||
};
|
||||
@@ -228,12 +238,60 @@ mod packet_encoding {
|
||||
header.encode(&mut bytes);
|
||||
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
|
||||
|
||||
assert_eq!(bytes.capacity(), Header::SIZE + packet_size.size())
|
||||
assert_eq!(bytes.capacity(), Header::LEGACY_SIZE + packet_size.size())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame() {
|
||||
fn for_bytes_with_versioned_header() {
|
||||
// if header gets decoded there should be enough bytes for the entire frame
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
for packet_size in packet_sizes {
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::Versioned(123),
|
||||
packet_size,
|
||||
packet_mode: Default::default(),
|
||||
};
|
||||
let mut bytes = BytesMut::new();
|
||||
header.encode(&mut bytes);
|
||||
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
|
||||
|
||||
assert_eq!(
|
||||
bytes.capacity(),
|
||||
Header::VERSIONED_SIZE + packet_size.size()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame_with_legacy_header() {
|
||||
// if full frame is used exactly, there should be enough space for header + ack packet
|
||||
let packet = FramedSphinxPacket {
|
||||
header: Header {
|
||||
packet_version: PacketVersion::Legacy,
|
||||
packet_size: Default::default(),
|
||||
packet_mode: Default::default(),
|
||||
},
|
||||
packet: make_valid_sphinx_packet(Default::default()),
|
||||
};
|
||||
|
||||
let mut bytes = BytesMut::new();
|
||||
SphinxCodec.encode(packet, &mut bytes).unwrap();
|
||||
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
||||
assert_eq!(
|
||||
bytes.capacity(),
|
||||
Header::LEGACY_SIZE + PacketSize::AckPacket.size()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame_with_versioned_header() {
|
||||
// if full frame is used exactly, there should be enough space for header + ack packet
|
||||
let packet = FramedSphinxPacket {
|
||||
header: Header::default(),
|
||||
@@ -245,17 +303,50 @@ mod packet_encoding {
|
||||
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
||||
assert_eq!(
|
||||
bytes.capacity(),
|
||||
Header::SIZE + PacketSize::AckPacket.size()
|
||||
Header::VERSIONED_SIZE + PacketSize::AckPacket.size()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame_with_extra_byte() {
|
||||
// if there was at least 1 byte left, there should be enough space for entire next frame
|
||||
fn for_full_frame_with_extra_bytes_with_legacy_header() {
|
||||
// if there was at least 2 byte left, there should be enough space for entire next frame
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
|
||||
for packet_size in packet_sizes {
|
||||
let first_packet = FramedSphinxPacket {
|
||||
header: Header {
|
||||
packet_version: PacketVersion::Legacy,
|
||||
packet_size: Default::default(),
|
||||
packet_mode: Default::default(),
|
||||
},
|
||||
packet: make_valid_sphinx_packet(Default::default()),
|
||||
};
|
||||
|
||||
let mut bytes = BytesMut::new();
|
||||
SphinxCodec.encode(first_packet, &mut bytes).unwrap();
|
||||
bytes.put_u8(packet_size as u8);
|
||||
bytes.put_u8(PacketMode::default() as u8);
|
||||
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
||||
|
||||
assert!(bytes.capacity() >= Header::LEGACY_SIZE + packet_size.size())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_full_frame_with_extra_bytes_with_versioned_header() {
|
||||
// if there was at least 3 byte left, there should be enough space for entire next frame
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
|
||||
for packet_size in packet_sizes {
|
||||
@@ -266,10 +357,12 @@ mod packet_encoding {
|
||||
|
||||
let mut bytes = BytesMut::new();
|
||||
SphinxCodec.encode(first_packet, &mut bytes).unwrap();
|
||||
bytes.put_u8(PacketVersion::new_versioned(123).as_u8().unwrap());
|
||||
bytes.put_u8(packet_size as u8);
|
||||
bytes.put_u8(PacketMode::default() as u8);
|
||||
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
||||
|
||||
assert!(bytes.capacity() >= Header::SIZE + packet_size.size())
|
||||
assert!(bytes.capacity() >= Header::VERSIONED_SIZE + packet_size.size())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::codec::SphinxCodecError;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use nymsphinx_params::packet_sizes::PacketSize;
|
||||
use nymsphinx_params::packet_version::PacketVersion;
|
||||
use nymsphinx_params::PacketMode;
|
||||
use nymsphinx_types::SphinxPacket;
|
||||
use std::convert::TryFrom;
|
||||
@@ -17,12 +18,14 @@ pub struct FramedSphinxPacket {
|
||||
}
|
||||
|
||||
impl FramedSphinxPacket {
|
||||
pub fn new(packet: SphinxPacket, packet_mode: PacketMode) -> Self {
|
||||
pub fn new(packet: SphinxPacket, packet_mode: PacketMode, use_legacy_version: bool) -> Self {
|
||||
// If this fails somebody is using the library in a super incorrect way, because they
|
||||
// already managed to somehow create a sphinx packet
|
||||
let packet_size = PacketSize::get_type(packet.len()).unwrap();
|
||||
|
||||
FramedSphinxPacket {
|
||||
header: Header {
|
||||
packet_version: PacketVersion::new(use_legacy_version),
|
||||
packet_size,
|
||||
packet_mode,
|
||||
},
|
||||
@@ -48,6 +51,9 @@ impl FramedSphinxPacket {
|
||||
// but would that really be worth it?
|
||||
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct Header {
|
||||
/// Represents the wire format version used to construct this packet.
|
||||
pub(crate) packet_version: PacketVersion,
|
||||
|
||||
/// Represents type and consequently size of the included SphinxPacket.
|
||||
pub(crate) packet_size: PacketSize,
|
||||
|
||||
@@ -64,11 +70,25 @@ pub struct Header {
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub(crate) const SIZE: usize = 2;
|
||||
pub(crate) const LEGACY_SIZE: usize = 2;
|
||||
pub(crate) const VERSIONED_SIZE: usize = 3;
|
||||
|
||||
pub(crate) fn size(&self) -> usize {
|
||||
if self.packet_version.is_legacy() {
|
||||
Self::LEGACY_SIZE
|
||||
} else {
|
||||
Self::VERSIONED_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encode(&self, dst: &mut BytesMut) {
|
||||
// we reserve one byte for `packet_size` and the other for `mode`
|
||||
dst.reserve(Self::SIZE);
|
||||
dst.reserve(Self::LEGACY_SIZE);
|
||||
if let Some(version) = self.packet_version.as_u8() {
|
||||
dst.reserve(Self::VERSIONED_SIZE);
|
||||
dst.put_u8(version)
|
||||
}
|
||||
|
||||
dst.put_u8(self.packet_size as u8);
|
||||
dst.put_u8(self.packet_mode as u8);
|
||||
// reserve bytes for the actual packet
|
||||
@@ -76,16 +96,30 @@ impl Header {
|
||||
}
|
||||
|
||||
pub(crate) fn decode(src: &mut BytesMut) -> Result<Option<Self>, SphinxCodecError> {
|
||||
if src.len() < Self::SIZE {
|
||||
if src.len() < Self::LEGACY_SIZE {
|
||||
// can't do anything if we don't have enough bytes - but reserve enough for the next call
|
||||
src.reserve(Self::SIZE);
|
||||
src.reserve(Self::LEGACY_SIZE);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(Header {
|
||||
packet_size: PacketSize::try_from(src[0])?,
|
||||
packet_mode: PacketMode::try_from(src[1])?,
|
||||
}))
|
||||
let packet_version = PacketVersion::from(src[0]);
|
||||
if packet_version.is_legacy() {
|
||||
Ok(Some(Header {
|
||||
packet_version,
|
||||
packet_size: PacketSize::try_from(src[0])?,
|
||||
packet_mode: PacketMode::try_from(src[1])?,
|
||||
}))
|
||||
} else if src.len() < Self::VERSIONED_SIZE {
|
||||
// we're missing that 1 byte to read the full header...
|
||||
src.reserve(Self::VERSIONED_SIZE);
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(Header {
|
||||
packet_version,
|
||||
packet_size: PacketSize::try_from(src[1])?,
|
||||
packet_mode: PacketMode::try_from(src[2])?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +142,16 @@ mod header_encoding {
|
||||
// make sure this is still 'unknown' for if we make changes in the future
|
||||
assert!(PacketSize::try_from(unknown_packet_size).is_err());
|
||||
|
||||
let mut bytes = BytesMut::from([unknown_packet_size, PacketMode::default() as u8].as_ref());
|
||||
// unfortunately this will only work for the 'versioned' variant
|
||||
// due to the hack used to get legacy mode compatibility
|
||||
let mut bytes = BytesMut::from(
|
||||
[
|
||||
PacketVersion::new_versioned(123).as_u8().unwrap(),
|
||||
unknown_packet_size,
|
||||
PacketMode::default() as u8,
|
||||
]
|
||||
.as_ref(),
|
||||
);
|
||||
assert!(Header::decode(&mut bytes).is_err())
|
||||
}
|
||||
|
||||
@@ -127,23 +170,47 @@ mod header_encoding {
|
||||
let mut empty_bytes = BytesMut::new();
|
||||
let decode_attempt_1 = Header::decode(&mut empty_bytes).unwrap();
|
||||
assert!(decode_attempt_1.is_none());
|
||||
assert!(empty_bytes.capacity() > Header::SIZE);
|
||||
assert!(empty_bytes.capacity() > Header::LEGACY_SIZE);
|
||||
|
||||
let mut empty_bytes = BytesMut::with_capacity(1);
|
||||
let decode_attempt_2 = Header::decode(&mut empty_bytes).unwrap();
|
||||
assert!(decode_attempt_2.is_none());
|
||||
assert!(empty_bytes.capacity() > Header::SIZE);
|
||||
assert!(empty_bytes.capacity() > Header::LEGACY_SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet() {
|
||||
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_in_legacy_mode() {
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
for packet_size in packet_sizes {
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::Legacy,
|
||||
packet_size,
|
||||
packet_mode: Default::default(),
|
||||
};
|
||||
let mut bytes = BytesMut::new();
|
||||
header.encode(&mut bytes);
|
||||
assert_eq!(bytes.capacity(), bytes.len() + packet_size.size())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_in_versioned_mode() {
|
||||
let packet_sizes = vec![
|
||||
PacketSize::AckPacket,
|
||||
PacketSize::RegularPacket,
|
||||
PacketSize::ExtendedPacket8,
|
||||
PacketSize::ExtendedPacket16,
|
||||
PacketSize::ExtendedPacket32,
|
||||
];
|
||||
for packet_size in packet_sizes {
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::Versioned(123),
|
||||
packet_size,
|
||||
packet_mode: Default::default(),
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ pub use packet_sizes::PacketSize;
|
||||
|
||||
pub mod packet_modes;
|
||||
pub mod packet_sizes;
|
||||
pub mod packet_version;
|
||||
|
||||
// If somebody can provide an argument why it might be reasonable to have more than 255 mix hops,
|
||||
// I will change this to [`usize`]
|
||||
@@ -24,6 +25,17 @@ pub const DEFAULT_NUM_MIX_HOPS: u8 = 3;
|
||||
pub const FRAG_ID_LEN: usize = 5;
|
||||
pub type SerializedFragmentIdentifier = [u8; FRAG_ID_LEN];
|
||||
|
||||
// wait, wait, but why are we starting with version 7?
|
||||
// when packet header gets serialized, the following bytes (in that order) are put onto the wire:
|
||||
// - packet_version (starting with v1.1.0)
|
||||
// - packet_size indicator
|
||||
// - packet_mode
|
||||
// it also just so happens that the only valid values for packet_size indicator include values 1-6
|
||||
// therefore if we receive byte `7` (or larger than that) we'll know we received a versioned packet,
|
||||
// otherwise we should treat it as legacy
|
||||
/// Increment it whenever we perform any breaking change in the wire format!
|
||||
const CURRENT_PACKET_VERSION_NUMBER: u8 = 7;
|
||||
|
||||
// TODO: ask @AP about the choice of below algorithms
|
||||
|
||||
/// Hashing algorithm used during hkdf for ephemeral shared key generation per sphinx packet payload.
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::FRAG_ID_LEN;
|
||||
use nymsphinx_types::header::HEADER_SIZE;
|
||||
use nymsphinx_types::PAYLOAD_OVERHEAD_SIZE;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
// it's up to the smart people to figure those values out : )
|
||||
const REGULAR_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 2 * 1024;
|
||||
@@ -15,11 +16,16 @@ const REGULAR_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 2 * 102
|
||||
const ACK_IV_SIZE: usize = 16;
|
||||
|
||||
const ACK_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + ACK_IV_SIZE + FRAG_ID_LEN;
|
||||
const EXTENDED_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 32 * 1024;
|
||||
const EXTENDED_PACKET_SIZE_8: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 8 * 1024;
|
||||
const EXTENDED_PACKET_SIZE_16: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 16 * 1024;
|
||||
const EXTENDED_PACKET_SIZE_32: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 32 * 1024;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidPacketSize;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidExtendedPacketSize;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PacketSize {
|
||||
@@ -30,7 +36,28 @@ pub enum PacketSize {
|
||||
AckPacket = 2,
|
||||
|
||||
// for example for streaming fast and furious in uncompressed 10bit 4K HDR quality
|
||||
ExtendedPacket = 3,
|
||||
ExtendedPacket32 = 3,
|
||||
|
||||
// for example for streaming fast and furious in heavily compressed lossy RealPlayer quality
|
||||
ExtendedPacket8 = 4,
|
||||
|
||||
// for example for streaming fast and furious in compressed XviD quality
|
||||
ExtendedPacket16 = 5,
|
||||
}
|
||||
|
||||
impl FromStr for PacketSize {
|
||||
type Err = InvalidPacketSize;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"regular" => Ok(Self::RegularPacket),
|
||||
"ack" => Ok(Self::AckPacket),
|
||||
"extended8" => Ok(Self::ExtendedPacket8),
|
||||
"extended16" => Ok(Self::ExtendedPacket16),
|
||||
"extended32" => Ok(Self::ExtendedPacket32),
|
||||
_ => Err(InvalidPacketSize),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PacketSize {
|
||||
@@ -40,7 +67,9 @@ impl TryFrom<u8> for PacketSize {
|
||||
match value {
|
||||
_ if value == (PacketSize::RegularPacket as u8) => Ok(Self::RegularPacket),
|
||||
_ if value == (PacketSize::AckPacket as u8) => Ok(Self::AckPacket),
|
||||
_ if value == (PacketSize::ExtendedPacket as u8) => Ok(Self::ExtendedPacket),
|
||||
_ if value == (PacketSize::ExtendedPacket8 as u8) => Ok(Self::ExtendedPacket8),
|
||||
_ if value == (PacketSize::ExtendedPacket16 as u8) => Ok(Self::ExtendedPacket16),
|
||||
_ if value == (PacketSize::ExtendedPacket32 as u8) => Ok(Self::ExtendedPacket32),
|
||||
_ => Err(InvalidPacketSize),
|
||||
}
|
||||
}
|
||||
@@ -51,7 +80,9 @@ impl PacketSize {
|
||||
match self {
|
||||
PacketSize::RegularPacket => REGULAR_PACKET_SIZE,
|
||||
PacketSize::AckPacket => ACK_PACKET_SIZE,
|
||||
PacketSize::ExtendedPacket => EXTENDED_PACKET_SIZE,
|
||||
PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8,
|
||||
PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16,
|
||||
PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,12 +99,33 @@ impl PacketSize {
|
||||
Ok(PacketSize::RegularPacket)
|
||||
} else if PacketSize::AckPacket.size() == size {
|
||||
Ok(PacketSize::AckPacket)
|
||||
} else if PacketSize::ExtendedPacket.size() == size {
|
||||
Ok(PacketSize::ExtendedPacket)
|
||||
} else if PacketSize::ExtendedPacket8.size() == size {
|
||||
Ok(PacketSize::ExtendedPacket8)
|
||||
} else if PacketSize::ExtendedPacket16.size() == size {
|
||||
Ok(PacketSize::ExtendedPacket16)
|
||||
} else if PacketSize::ExtendedPacket32.size() == size {
|
||||
Ok(PacketSize::ExtendedPacket32)
|
||||
} else {
|
||||
Err(InvalidPacketSize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_extended_size(&self) -> bool {
|
||||
match self {
|
||||
PacketSize::RegularPacket | PacketSize::AckPacket => false,
|
||||
PacketSize::ExtendedPacket8
|
||||
| PacketSize::ExtendedPacket16
|
||||
| PacketSize::ExtendedPacket32 => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_extended_size(self) -> Option<Self> {
|
||||
if self.is_extended_size() {
|
||||
Some(self)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PacketSize {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{PacketSize, CURRENT_PACKET_VERSION_NUMBER};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PacketVersion {
|
||||
// this will allow updated mixnodes to still understand packets from before the update
|
||||
Legacy,
|
||||
Versioned(u8),
|
||||
}
|
||||
|
||||
impl PacketVersion {
|
||||
pub fn new(use_legacy: bool) -> Self {
|
||||
if use_legacy {
|
||||
Self::new_legacy()
|
||||
} else {
|
||||
Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_legacy() -> Self {
|
||||
PacketVersion::Legacy
|
||||
}
|
||||
|
||||
pub fn new_versioned(version: u8) -> Self {
|
||||
PacketVersion::Versioned(version)
|
||||
}
|
||||
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
matches!(self, PacketVersion::Legacy)
|
||||
}
|
||||
|
||||
pub fn as_u8(&self) -> Option<u8> {
|
||||
match self {
|
||||
PacketVersion::Legacy => None,
|
||||
PacketVersion::Versioned(version) => Some(*version),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for PacketVersion {
|
||||
fn from(v: u8) -> Self {
|
||||
match v {
|
||||
n if n == PacketSize::RegularPacket as u8 => PacketVersion::Legacy,
|
||||
n if n == PacketSize::AckPacket as u8 => PacketVersion::Legacy,
|
||||
n if n == PacketSize::ExtendedPacket8 as u8 => PacketVersion::Legacy,
|
||||
n if n == PacketSize::ExtendedPacket16 as u8 => PacketVersion::Legacy,
|
||||
n if n == PacketSize::ExtendedPacket32 as u8 => PacketVersion::Legacy,
|
||||
n => PacketVersion::Versioned(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PacketVersion {
|
||||
fn default() -> Self {
|
||||
PacketVersion::Versioned(CURRENT_PACKET_VERSION_NUMBER)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ use task::ShutdownListener;
|
||||
use tokio::select;
|
||||
use tokio::{net::tcp::OwnedReadHalf, sync::Notify, time::sleep};
|
||||
|
||||
async fn send_empty_close<F, S>(
|
||||
fn send_empty_close<F, S>(
|
||||
connection_id: ConnectionId,
|
||||
message_sender: &mut OrderedMessageSender,
|
||||
mix_sender: &MixProxySender<S>,
|
||||
@@ -24,20 +24,12 @@ async fn send_empty_close<F, S>(
|
||||
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
|
||||
{
|
||||
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
|
||||
//mix_sender
|
||||
// .unbounded_send(adapter_fn(connection_id, ordered_msg, true))
|
||||
// .unwrap();
|
||||
log::info!("mix_sender capacity: {}", mix_sender.capacity());
|
||||
if let Err(err) = mix_sender
|
||||
.send(adapter_fn(connection_id, ordered_msg, true))
|
||||
.await
|
||||
{
|
||||
log::error!("failed to send");
|
||||
panic!();
|
||||
}
|
||||
mix_sender
|
||||
.unbounded_send(adapter_fn(connection_id, ordered_msg, true))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn deal_with_data<F, S>(
|
||||
fn deal_with_data<F, S>(
|
||||
read_data: Option<io::Result<Bytes>>,
|
||||
local_destination_address: &str,
|
||||
remote_source_address: &str,
|
||||
@@ -71,17 +63,9 @@ where
|
||||
|
||||
// if we're sending through the mixnet increase the sequence number...
|
||||
let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes();
|
||||
//mix_sender
|
||||
// .unbounded_send(adapter_fn(connection_id, ordered_msg, is_finished))
|
||||
// .unwrap();
|
||||
log::info!("mix_sender capacity: {}", mix_sender.capacity());
|
||||
if let Err(err) = mix_sender
|
||||
.send(adapter_fn(connection_id, ordered_msg, is_finished))
|
||||
.await
|
||||
{
|
||||
log::error!("failed to send");
|
||||
panic!();
|
||||
}
|
||||
mix_sender
|
||||
.unbounded_send(adapter_fn(connection_id, ordered_msg, is_finished))
|
||||
.unwrap();
|
||||
|
||||
if is_finished {
|
||||
// technically we already informed it when we sent the message to mixnet above
|
||||
@@ -114,7 +98,7 @@ where
|
||||
loop {
|
||||
select! {
|
||||
read_data = &mut available_reader.next() => {
|
||||
if deal_with_data(read_data, &local_destination_address, &remote_source_address, connection_id, &mut message_sender, &mix_sender, &adapter_fn).await {
|
||||
if deal_with_data(read_data, &local_destination_address, &remote_source_address, connection_id, &mut message_sender, &mix_sender, &adapter_fn) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,7 @@ impl From<(Vec<u8>, bool)> for ProxyMessage {
|
||||
}
|
||||
}
|
||||
|
||||
//pub type MixProxySender<S> = mpsc::UnboundedSender<S>;
|
||||
pub type MixProxySender<S> = tokio::sync::mpsc::Sender<S>;
|
||||
pub type MixProxySender<S> = mpsc::UnboundedSender<S>;
|
||||
|
||||
// TODO: when we finally get to implementing graceful shutdown,
|
||||
// on Drop this guy should tell the remote that it's closed now
|
||||
@@ -79,7 +78,7 @@ where
|
||||
// request/response as required by entity running particular side of the proxy.
|
||||
pub async fn run<F>(mut self, adapter_fn: F) -> Self
|
||||
where
|
||||
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static + std::marker::Sync,
|
||||
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static,
|
||||
{
|
||||
let (read_half, write_half) = self.socket.take().unwrap().into_split();
|
||||
let shutdown_notify = Arc::new(Notify::new());
|
||||
|
||||
@@ -62,6 +62,7 @@ pub struct DelegationWithEverything {
|
||||
|
||||
// DEPRECATED, IF POSSIBLE TRY TO DISCONTINUE USE OF IT!
|
||||
pub pending_events: Vec<DelegationEvent>,
|
||||
pub mixnode_is_unbonding: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
|
||||
+35
-4
@@ -1,14 +1,46 @@
|
||||
## [nym-contracts-v1.1.0](https://github.com/nymtech/nym/tree/nym-contracts-v1.1.0) (2022-11-09)
|
||||
|
||||
### Changed
|
||||
- mixnet-contract: rework of rewarding ([#1472]), which includes, but is not limited to:
|
||||
- internal reward accounting was modified to be similar to the ideas presented in Cosmos' F1 paper, which results in throughput gains and no storage or gas cost bloat over time,
|
||||
- introduced internal queues for pending epoch and interval events that only get resolved once relevant epoch/interval rolls over
|
||||
- the contract no longer stores any historical information regarding past epochs/parameters/stake state for the purposes of rewarding
|
||||
- a lot of queries got renamed to keep naming more consistent,
|
||||
- introduced new utility-based queries such as a query for reward estimation for the current epoch,
|
||||
- mixnodes are now identified by a monotonously increasing `mix_id`
|
||||
- bonding now results in getting fresh `mix_id` and thus if given node decides to unbond and rebond, it will lose all its delegations,
|
||||
- mixnode operators are now allowed to set their operating costs as opposed to having fixed value of 40nym/interval
|
||||
- rewarding parameters are now correctly updated at an **interval** end
|
||||
- rewarding parameters now include a staking supply scale factor attribute (beta in the tokenomics paper)
|
||||
- node performance can now be more granular with internal `Decimal` representation as opposed to an `u8`
|
||||
- node profit margin can now be more granular with internal `Decimal` representation as opposed to an `u8`
|
||||
- mixnode operators are now allowed to change their configuration options, such as port information, without having to unbond
|
||||
- mixnode unbonding is no longer instantaneous, instead it happens once an epoch rolls over
|
||||
- it is now possible to query for operator and node history to see how often (and with what parameters) they rebonded
|
||||
- other minor bugfixes and changes
|
||||
- ...
|
||||
- new exciting bugs to find and squash
|
||||
|
||||
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 10%
|
||||
- vesting-contract: updated internal delegation storage due to mixnet contract revamp ([#1472])
|
||||
|
||||
### Added
|
||||
- vesting-contract: added query for obtaining contract build information ([#1726])
|
||||
|
||||
[#1472]: https://github.com/nymtech/nym/pull/1472
|
||||
[#1687]: https://github.com/nymtech/nym/pull/1687
|
||||
[#1726]: https://github.com/nymtech/nym/pull/1726
|
||||
|
||||
|
||||
## [nym-contracts-v1.0.2](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.2) (2022-09-13)
|
||||
|
||||
### Added
|
||||
|
||||
- vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569])
|
||||
- all binaries: added shell completion and [Fig](fig.io) spec generation ([#1638])
|
||||
|
||||
### Changed
|
||||
|
||||
- mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571])
|
||||
- network-requester: updated CLI to use `clap` macros ([#1638])
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -18,9 +50,8 @@
|
||||
|
||||
[#1544]: https://github.com/nymtech/nym/pull/1544
|
||||
[#1569]: https://github.com/nymtech/nym/pull/1569
|
||||
[#1569]: https://github.com/nymtech/nym/pull/1571
|
||||
[#1571]: https://github.com/nymtech/nym/pull/1571
|
||||
[#1613]: https://github.com/nymtech/nym/pull/1613
|
||||
[#1638]: https://github.com/nymtech/nym/pull/1638
|
||||
|
||||
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
|
||||
|
||||
|
||||
Generated
+6
-1
@@ -221,7 +221,9 @@ name = "contracts-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -928,7 +930,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-contract"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-schema",
|
||||
@@ -1606,12 +1608,14 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
name = "vesting-contract"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"vergen",
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
@@ -1619,6 +1623,7 @@ dependencies = [
|
||||
name = "vesting-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mixnet-contract"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
@@ -497,6 +497,7 @@ mod tests {
|
||||
initial_rewarding_params: InitialRewardingParams {
|
||||
initial_reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(),
|
||||
initial_staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(),
|
||||
staking_supply_scale_factor: Percent::hundred(),
|
||||
sybil_resistance: Percent::from_percentage_value(23).unwrap(),
|
||||
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
|
||||
interval_pool_emission: Percent::from_percentage_value(1).unwrap(),
|
||||
@@ -535,6 +536,7 @@ mod tests {
|
||||
interval: IntervalRewardParams {
|
||||
reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(),
|
||||
staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(),
|
||||
staking_supply_scale_factor: Percent::hundred(),
|
||||
epoch_reward_budget: expected_epoch_reward_budget,
|
||||
stake_saturation_point: expected_stake_saturation_point,
|
||||
sybil_resistance: Percent::from_percentage_value(23).unwrap(),
|
||||
|
||||
@@ -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,
|
||||
@@ -1220,6 +1220,7 @@ mod tests {
|
||||
let update = IntervalRewardingParamsUpdate {
|
||||
reward_pool: Some(before.interval.reward_pool / two),
|
||||
staking_supply: Some(before.interval.staking_supply * four),
|
||||
staking_supply_scale_factor: None,
|
||||
sybil_resistance_percent: Some(Percent::from_percentage_value(42).unwrap()),
|
||||
active_set_work_factor: None,
|
||||
interval_pool_emission: None,
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
mod constants;
|
||||
pub mod contract;
|
||||
mod delegations;
|
||||
|
||||
@@ -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,9 +4,8 @@
|
||||
use super::storage;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use cosmwasm_std::{Coin, Storage};
|
||||
use cosmwasm_std::{Coin, Decimal, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::helpers::IntoBaseDecimal;
|
||||
use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding};
|
||||
use mixnet_contract_common::Delegation;
|
||||
|
||||
@@ -21,11 +20,13 @@ pub(crate) fn apply_reward_pool_changes(
|
||||
|
||||
let reward_pool = rewarding_params.interval.reward_pool - pending_pool_change.removed
|
||||
+ pending_pool_change.added;
|
||||
let staking_supply = rewarding_params.interval.staking_supply + pending_pool_change.removed;
|
||||
let epoch_reward_budget = reward_pool / interval.epochs_in_interval().into_base_decimal()?
|
||||
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()
|
||||
* rewarding_params.interval.interval_pool_emission;
|
||||
let stake_saturation_point =
|
||||
staking_supply / rewarding_params.rewarded_set_size.into_base_decimal()?;
|
||||
staking_supply / Decimal::from_atomics(rewarding_params.rewarded_set_size, 0).unwrap();
|
||||
|
||||
rewarding_params.interval.reward_pool = reward_pool;
|
||||
rewarding_params.interval.staking_supply = staking_supply;
|
||||
@@ -45,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)?;
|
||||
@@ -86,7 +87,7 @@ mod tests {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let epochs_in_interval = test.current_interval().epochs_in_interval();
|
||||
let epochs_in_interval_dec = epochs_in_interval.into_base_decimal().unwrap();
|
||||
let epochs_in_interval_dec = Decimal::from_atomics(epochs_in_interval, 0).unwrap();
|
||||
let start_rewarding_params = test.rewarding_params();
|
||||
|
||||
// nothing changes if pending changes are empty
|
||||
@@ -94,7 +95,7 @@ mod tests {
|
||||
assert_eq!(start_rewarding_params, test.rewarding_params());
|
||||
|
||||
// normal case of having distributed some rewards
|
||||
let distributed_rewards = 100_000_000u32.into_base_decimal().unwrap();
|
||||
let distributed_rewards = Decimal::from_atomics(100_000_000u32, 0).unwrap();
|
||||
storage::PENDING_REWARD_POOL_CHANGE
|
||||
.save(
|
||||
test.deps_mut().storage,
|
||||
@@ -131,10 +132,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
updated_rewarding_params.interval.stake_saturation_point,
|
||||
updated_rewarding_params.interval.staking_supply
|
||||
/ updated_rewarding_params
|
||||
.rewarded_set_size
|
||||
.into_base_decimal()
|
||||
.unwrap()
|
||||
/ Decimal::from_atomics(updated_rewarding_params.rewarded_set_size, 0).unwrap()
|
||||
);
|
||||
|
||||
// resets changes back to 0
|
||||
@@ -146,7 +144,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// future case of having to also increase the reward pool
|
||||
let added_credentials = 50_000_000u32.into_base_decimal().unwrap();
|
||||
let added_credentials = Decimal::from_atomics(50_000_000u32, 0).unwrap();
|
||||
storage::PENDING_REWARD_POOL_CHANGE
|
||||
.save(
|
||||
test.deps_mut().storage,
|
||||
@@ -183,10 +181,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
updated_rewarding_params2.interval.stake_saturation_point,
|
||||
updated_rewarding_params2.interval.staking_supply
|
||||
/ updated_rewarding_params2
|
||||
.rewarded_set_size
|
||||
.into_base_decimal()
|
||||
.unwrap()
|
||||
/ Decimal::from_atomics(updated_rewarding_params2.rewarded_set_size, 0).unwrap()
|
||||
);
|
||||
|
||||
// resets changes back to 0
|
||||
@@ -203,7 +198,7 @@ mod tests {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
let pledge_dec = 250_000_000u32.into_base_decimal().unwrap();
|
||||
let pledge_dec = Decimal::from_atomics(250_000_000u32, 0).unwrap();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", Some(pledge));
|
||||
|
||||
// no rewards
|
||||
@@ -240,7 +235,7 @@ mod tests {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let delegation_amount = Uint128::new(2500_000_000);
|
||||
let delegation_dec = 2500_000_000u32.into_base_decimal().unwrap();
|
||||
let delegation_dec = Decimal::from_atomics(2500_000_000u32, 0).unwrap();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
let delegator = "delegator";
|
||||
test.add_immediate_delegation(delegator, delegation_amount, mix_id);
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::interval::storage as interval_storage;
|
||||
use crate::mixnodes;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use cosmwasm_std::{coin, Coin, Decimal, Deps, StdResult};
|
||||
use mixnet_contract_common::helpers::into_base_decimal;
|
||||
use mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams};
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward;
|
||||
@@ -20,18 +19,16 @@ pub(crate) fn query_rewarding_params(deps: Deps<'_>) -> StdResult<RewardingParam
|
||||
storage::REWARDING_PARAMS.load(deps.storage)
|
||||
}
|
||||
|
||||
fn pending_operator_reward(
|
||||
mix_details: Option<MixNodeDetails>,
|
||||
) -> StdResult<PendingRewardResponse> {
|
||||
Ok(match mix_details {
|
||||
fn pending_operator_reward(mix_details: Option<MixNodeDetails>) -> PendingRewardResponse {
|
||||
match mix_details {
|
||||
Some(mix_details) => PendingRewardResponse {
|
||||
amount_staked: Some(mix_details.original_pledge().clone()),
|
||||
amount_earned: Some(mix_details.pending_operator_reward()),
|
||||
amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()?),
|
||||
amount_earned_detailed: Some(mix_details.pending_detailed_operator_reward()),
|
||||
mixnode_still_fully_bonded: !mix_details.is_unbonding(),
|
||||
},
|
||||
None => PendingRewardResponse::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_pending_operator_reward(
|
||||
@@ -42,7 +39,7 @@ pub fn query_pending_operator_reward(
|
||||
// in order to determine operator's reward we need to know its original pledge and thus
|
||||
// we have to load the entire thing
|
||||
let mix_details = mixnodes::helpers::get_mixnode_details_by_owner(deps.storage, owner_address)?;
|
||||
pending_operator_reward(mix_details)
|
||||
Ok(pending_operator_reward(mix_details))
|
||||
}
|
||||
|
||||
pub fn query_pending_mixnode_operator_reward(
|
||||
@@ -52,7 +49,7 @@ pub fn query_pending_mixnode_operator_reward(
|
||||
// in order to determine operator's reward we need to know its original pledge and thus
|
||||
// we have to load the entire thing
|
||||
let mix_details = mixnodes::helpers::get_mixnode_details_by_id(deps.storage, mix_id)?;
|
||||
pending_operator_reward(mix_details)
|
||||
Ok(pending_operator_reward(mix_details))
|
||||
}
|
||||
|
||||
pub fn query_pending_delegator_reward(
|
||||
@@ -77,8 +74,8 @@ pub fn query_pending_delegator_reward(
|
||||
None => return Ok(PendingRewardResponse::default()),
|
||||
};
|
||||
|
||||
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation)?;
|
||||
let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation)?;
|
||||
let detailed_reward = mix_rewarding.determine_delegation_reward(&delegation);
|
||||
let delegator_reward = mix_rewarding.pending_delegator_reward(&delegation);
|
||||
|
||||
// check if the mixnode isnt in the process of unbonding (or has already unbonded)
|
||||
let is_bonded = matches!(mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)?, Some(mix_bond) if !mix_bond.is_unbonding);
|
||||
@@ -180,8 +177,8 @@ pub(crate) fn query_estimated_current_epoch_delegator_reward(
|
||||
None => return Ok(EstimatedCurrentEpochRewardResponse::empty_response()),
|
||||
};
|
||||
|
||||
let staked_dec = into_base_decimal(delegation.amount.amount)?;
|
||||
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation)?;
|
||||
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
|
||||
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
|
||||
let amount_staked = delegation.amount;
|
||||
|
||||
// check if the mixnode isnt in the process of unbonding (or has already unbonded)
|
||||
@@ -795,10 +792,7 @@ mod tests {
|
||||
let delegation = test.delegation(mix_id, owner, &None);
|
||||
|
||||
let staked_dec = Decimal::from_atomics(delegation.amount.amount, 0).unwrap();
|
||||
let current_value = staked_dec
|
||||
+ mix_rewarding
|
||||
.determine_delegation_reward(&delegation)
|
||||
.unwrap();
|
||||
let current_value = staked_dec + mix_rewarding.determine_delegation_reward(&delegation);
|
||||
let amount_staked = delegation.amount;
|
||||
|
||||
EstimatedCurrentEpochRewardResponse {
|
||||
|
||||
@@ -744,7 +744,7 @@ pub mod tests {
|
||||
performance,
|
||||
in_active_set: true,
|
||||
};
|
||||
let sim_res = sim.simulate_epoch_single_node(node_params).unwrap();
|
||||
let sim_res = sim.simulate_epoch_single_node(node_params);
|
||||
assert_eq!(sim_res, dist);
|
||||
}
|
||||
test.skip_to_next_epoch_end();
|
||||
@@ -768,7 +768,7 @@ pub mod tests {
|
||||
performance,
|
||||
in_active_set: true,
|
||||
};
|
||||
let sim_res = sim.simulate_epoch_single_node(node_params).unwrap();
|
||||
let sim_res = sim.simulate_epoch_single_node(node_params);
|
||||
assert_eq!(sim_res, dist);
|
||||
}
|
||||
test.skip_to_next_epoch_end();
|
||||
@@ -809,8 +809,8 @@ pub mod tests {
|
||||
in_active_set: true,
|
||||
};
|
||||
|
||||
let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap();
|
||||
let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap();
|
||||
let dist1 = sim1.simulate_epoch_single_node(node_params);
|
||||
let dist2 = sim2.simulate_epoch_single_node(node_params);
|
||||
|
||||
let env = test.env();
|
||||
|
||||
@@ -858,17 +858,15 @@ pub mod tests {
|
||||
let unit_delegation_base = actual_prior1.unit_delegation;
|
||||
|
||||
// recompute the state of fully compounded delegation from before this rewarding was distributed
|
||||
let pre_rewarding_del11 = del11.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del11.cumulative_reward_ratio)
|
||||
* del11.dec_amount().unwrap()
|
||||
let pre_rewarding_del11 = del11.dec_amount()
|
||||
+ (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
|
||||
/ (del11.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del11_reward =
|
||||
pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
|
||||
|
||||
let pre_rewarding_del12 = del12.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del12.cumulative_reward_ratio)
|
||||
* del12.dec_amount().unwrap()
|
||||
let pre_rewarding_del12 = del12.dec_amount()
|
||||
+ (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
|
||||
/ (del12.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del12_reward =
|
||||
@@ -922,9 +920,8 @@ pub mod tests {
|
||||
let unit_delegation_base = actual_prior2.unit_delegation;
|
||||
|
||||
// recompute the state of fully compounded delegation from before this rewarding was distributed
|
||||
let pre_rewarding_del21 = del21.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del21.cumulative_reward_ratio)
|
||||
* del21.dec_amount().unwrap()
|
||||
let pre_rewarding_del21 = del21.dec_amount()
|
||||
+ (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
|
||||
/ (del21.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del21_reward =
|
||||
@@ -952,8 +949,8 @@ pub mod tests {
|
||||
in_active_set: true,
|
||||
};
|
||||
|
||||
let dist1 = sim1.simulate_epoch_single_node(node_params).unwrap();
|
||||
let dist2 = sim2.simulate_epoch_single_node(node_params).unwrap();
|
||||
let dist1 = sim1.simulate_epoch_single_node(node_params);
|
||||
let dist2 = sim2.simulate_epoch_single_node(node_params);
|
||||
|
||||
let env = test.env();
|
||||
|
||||
@@ -1001,25 +998,22 @@ pub mod tests {
|
||||
let unit_delegation_base = actual_prior1.unit_delegation;
|
||||
|
||||
// recompute the state of fully compounded delegation from before this rewarding was distributed
|
||||
let pre_rewarding_del11 = del11.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del11.cumulative_reward_ratio)
|
||||
* del11.dec_amount().unwrap()
|
||||
let pre_rewarding_del11 = del11.dec_amount()
|
||||
+ (prior_unit_reward - del11.cumulative_reward_ratio) * del11.dec_amount()
|
||||
/ (del11.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del11_reward =
|
||||
pre_rewarding_del11 / prior_delegates1 * delegates_reward1;
|
||||
|
||||
let pre_rewarding_del12 = del12.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del12.cumulative_reward_ratio)
|
||||
* del12.dec_amount().unwrap()
|
||||
let pre_rewarding_del12 = del12.dec_amount()
|
||||
+ (prior_unit_reward - del12.cumulative_reward_ratio) * del12.dec_amount()
|
||||
/ (del12.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del12_reward =
|
||||
pre_rewarding_del12 / prior_delegates1 * delegates_reward1;
|
||||
|
||||
let pre_rewarding_del13 = del13.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del13.cumulative_reward_ratio)
|
||||
* del13.dec_amount().unwrap()
|
||||
let pre_rewarding_del13 = del13.dec_amount()
|
||||
+ (prior_unit_reward - del13.cumulative_reward_ratio) * del13.dec_amount()
|
||||
/ (del13.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del13_reward =
|
||||
@@ -1073,17 +1067,15 @@ pub mod tests {
|
||||
let unit_delegation_base = actual_prior2.unit_delegation;
|
||||
|
||||
// recompute the state of fully compounded delegation from before this rewarding was distributed
|
||||
let pre_rewarding_del21 = del21.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del21.cumulative_reward_ratio)
|
||||
* del21.dec_amount().unwrap()
|
||||
let pre_rewarding_del21 = del21.dec_amount()
|
||||
+ (prior_unit_reward - del21.cumulative_reward_ratio) * del21.dec_amount()
|
||||
/ (del21.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del21_reward =
|
||||
pre_rewarding_del21 / prior_delegates2 * delegates_reward2;
|
||||
|
||||
let pre_rewarding_del23 = del23.dec_amount().unwrap()
|
||||
+ (prior_unit_reward - del23.cumulative_reward_ratio)
|
||||
* del23.dec_amount().unwrap()
|
||||
let pre_rewarding_del23 = del23.dec_amount()
|
||||
+ (prior_unit_reward - del23.cumulative_reward_ratio) * del23.dec_amount()
|
||||
/ (del23.cumulative_reward_ratio + unit_delegation_base);
|
||||
|
||||
let computed_del23_reward =
|
||||
@@ -1718,6 +1710,7 @@ pub mod tests {
|
||||
let update = IntervalRewardingParamsUpdate {
|
||||
reward_pool: None,
|
||||
staking_supply: None,
|
||||
staking_supply_scale_factor: None,
|
||||
sybil_resistance_percent: None,
|
||||
active_set_work_factor: None,
|
||||
interval_pool_emission: None,
|
||||
@@ -1750,6 +1743,7 @@ pub mod tests {
|
||||
let empty_update = IntervalRewardingParamsUpdate {
|
||||
reward_pool: None,
|
||||
staking_supply: None,
|
||||
staking_supply_scale_factor: None,
|
||||
sybil_resistance_percent: None,
|
||||
active_set_work_factor: None,
|
||||
interval_pool_emission: None,
|
||||
@@ -1769,6 +1763,7 @@ pub mod tests {
|
||||
let update = IntervalRewardingParamsUpdate {
|
||||
reward_pool: None,
|
||||
staking_supply: None,
|
||||
staking_supply_scale_factor: None,
|
||||
sybil_resistance_percent: None,
|
||||
active_set_work_factor: None,
|
||||
interval_pool_emission: None,
|
||||
@@ -1803,6 +1798,7 @@ pub mod tests {
|
||||
let update = IntervalRewardingParamsUpdate {
|
||||
reward_pool: None,
|
||||
staking_supply: None,
|
||||
staking_supply_scale_factor: None,
|
||||
sybil_resistance_percent: None,
|
||||
active_set_work_factor: None,
|
||||
interval_pool_emission: None,
|
||||
@@ -1827,6 +1823,7 @@ pub mod tests {
|
||||
let update = IntervalRewardingParamsUpdate {
|
||||
reward_pool: None,
|
||||
staking_supply: None,
|
||||
staking_supply_scale_factor: None,
|
||||
sybil_resistance_percent: None,
|
||||
active_set_work_factor: None,
|
||||
interval_pool_emission: None,
|
||||
@@ -1867,6 +1864,7 @@ pub mod tests {
|
||||
let update = IntervalRewardingParamsUpdate {
|
||||
reward_pool: Some(old.interval.reward_pool / two),
|
||||
staking_supply: Some(old.interval.staking_supply * four),
|
||||
staking_supply_scale_factor: None,
|
||||
sybil_resistance_percent: None,
|
||||
active_set_work_factor: None,
|
||||
interval_pool_emission: None,
|
||||
|
||||
@@ -69,10 +69,6 @@ pub(crate) fn validate_pledge(
|
||||
});
|
||||
}
|
||||
|
||||
// throughout this function we've been using the value at `pledge[0]` without problems
|
||||
// (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine,
|
||||
// since it cannot possibly fail without UB
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(pledge.pop().unwrap())
|
||||
}
|
||||
|
||||
@@ -110,10 +106,6 @@ pub(crate) fn validate_delegation_stake(
|
||||
return Err(MixnetContractError::EmptyDelegation);
|
||||
}
|
||||
|
||||
// throughout this function we've been using the value at `delegation[0]` without problems
|
||||
// (plus we have even validated that the vec is not empty), so the unwrap here is absolutely fine,
|
||||
// since it cannot possibly fail without UB
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(delegation.pop().unwrap())
|
||||
}
|
||||
|
||||
|
||||
@@ -768,6 +768,7 @@ pub mod test_helpers {
|
||||
InitialRewardingParams {
|
||||
initial_reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(), // 250M * 1M (we're expressing it all in base tokens)
|
||||
initial_staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(), // 100M * 1M
|
||||
staking_supply_scale_factor: Percent::hundred(),
|
||||
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
||||
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
|
||||
interval_pool_emission: Percent::from_percentage_value(2).unwrap(),
|
||||
|
||||
@@ -15,6 +15,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||
|
||||
cosmwasm-std = { version = "1.0.0 "}
|
||||
@@ -22,4 +23,7 @@ cw-storage-plus = { version = "0.13.4", features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0" }
|
||||
thiserror = { version = "1.0" }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] }
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use vergen::{vergen, Config};
|
||||
|
||||
fn main() {
|
||||
vergen(Config::default()).expect("failed to extract build metadata")
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::queued_migrations::migrate_to_v2_mixnet_contract;
|
||||
use crate::storage::{
|
||||
account_from_address, locked_pledge_cap, update_locked_pledge_cap, BlockTimestampSecs, ADMIN,
|
||||
DELEGATIONS, MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||
account_from_address, save_account, BlockTimestampSecs, ADMIN, DELEGATIONS,
|
||||
MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||
};
|
||||
use crate::traits::{
|
||||
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
|
||||
};
|
||||
use crate::vesting::{populate_vesting_periods, Account};
|
||||
use contracts_common::ContractBuildInformation;
|
||||
use cosmwasm_std::{
|
||||
coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order,
|
||||
QueryResponse, Response, StdResult, Timestamp, Uint128,
|
||||
@@ -25,8 +26,8 @@ use vesting_contract_common::messages::{
|
||||
ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification,
|
||||
};
|
||||
use vesting_contract_common::{
|
||||
AllDelegationsResponse, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeData,
|
||||
VestingDelegation,
|
||||
AllDelegationsResponse, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeCap,
|
||||
PledgeData, VestingDelegation,
|
||||
};
|
||||
|
||||
pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000);
|
||||
@@ -59,8 +60,8 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::UpdateLockedPledgeCap { amount } => {
|
||||
try_update_locked_pledge_cap(amount, info, deps)
|
||||
ExecuteMsg::UpdateLockedPledgeCap { address, cap } => {
|
||||
try_update_locked_pledge_cap(address, cap, info, deps)
|
||||
}
|
||||
ExecuteMsg::TrackReward { amount, address } => {
|
||||
try_track_reward(deps, info, amount, &address)
|
||||
@@ -88,10 +89,12 @@ pub fn execute(
|
||||
owner_address,
|
||||
staking_address,
|
||||
vesting_spec,
|
||||
cap,
|
||||
} => try_create_periodic_vesting_account(
|
||||
&owner_address,
|
||||
staking_address,
|
||||
vesting_spec,
|
||||
cap,
|
||||
info,
|
||||
env,
|
||||
deps,
|
||||
@@ -144,14 +147,18 @@ pub fn execute(
|
||||
///
|
||||
/// Callable by ADMIN only, see [instantiate].
|
||||
pub fn try_update_locked_pledge_cap(
|
||||
amount: Uint128,
|
||||
address: String,
|
||||
cap: PledgeCap,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
if info.sender != ADMIN.load(deps.storage)? {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
update_locked_pledge_cap(amount, deps.storage)?;
|
||||
let mut account = account_from_address(&address, deps.storage, deps.api)?;
|
||||
|
||||
account.pledge_cap = Some(cap);
|
||||
save_account(&account, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
@@ -430,6 +437,7 @@ fn try_create_periodic_vesting_account(
|
||||
owner_address: &str,
|
||||
staking_address: Option<String>,
|
||||
vesting_spec: Option<VestingSpecification>,
|
||||
cap: Option<PledgeCap>,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
@@ -437,6 +445,7 @@ fn try_create_periodic_vesting_account(
|
||||
if info.sender != ADMIN.load(deps.storage)? {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
|
||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||
|
||||
let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok();
|
||||
@@ -452,6 +461,11 @@ fn try_create_periodic_vesting_account(
|
||||
|
||||
let owner_address = deps.api.addr_validate(owner_address)?;
|
||||
let staking_address = if let Some(staking_address) = staking_address {
|
||||
let staking_account_exists =
|
||||
account_from_address(&staking_address, deps.storage, deps.api).is_ok();
|
||||
if staking_account_exists {
|
||||
return Err(ContractError::StakingAccountAlreadyExists(staking_address));
|
||||
}
|
||||
Some(deps.api.addr_validate(&staking_address)?)
|
||||
} else {
|
||||
None
|
||||
@@ -472,6 +486,7 @@ fn try_create_periodic_vesting_account(
|
||||
coin.clone(),
|
||||
start_time,
|
||||
periods,
|
||||
cap,
|
||||
deps.storage,
|
||||
)?;
|
||||
|
||||
@@ -486,7 +501,7 @@ fn try_create_periodic_vesting_account(
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let query_res = match msg {
|
||||
QueryMsg::GetLockedPledgeCap {} => to_binary(&get_locked_pledge_cap(deps)),
|
||||
QueryMsg::GetContractVersion {} => to_binary(&get_contract_version()),
|
||||
QueryMsg::LockedCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
@@ -567,11 +582,6 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
/// Get locked_pledge_cap, the hard cap for staking/bonding with unvested tokens.
|
||||
pub fn get_locked_pledge_cap(deps: Deps<'_>) -> Uint128 {
|
||||
locked_pledge_cap(deps.storage)
|
||||
}
|
||||
|
||||
/// Get current vesting period for a given [crate::vesting::Account].
|
||||
pub fn try_get_current_vesting_period(
|
||||
address: &str,
|
||||
@@ -579,7 +589,7 @@ pub fn try_get_current_vesting_period(
|
||||
env: Env,
|
||||
) -> Result<Period, ContractError> {
|
||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||
account.get_current_vesting_period(env.block.time)
|
||||
Ok(account.get_current_vesting_period(env.block.time))
|
||||
}
|
||||
|
||||
/// Loads mixnode bond from vesting contract storage.
|
||||
@@ -598,6 +608,21 @@ pub fn try_get_account(address: &str, deps: Deps<'_>) -> Result<Account, Contrac
|
||||
account_from_address(address, deps.storage, deps.api)
|
||||
}
|
||||
|
||||
/// Gets build information of this contract.
|
||||
pub fn get_contract_version() -> ContractBuildInformation {
|
||||
// as per docs
|
||||
// env! macro will expand to the value of the named environment variable at
|
||||
// compile time, yielding an expression of type `&'static str`
|
||||
ContractBuildInformation {
|
||||
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(),
|
||||
build_version: env!("VERGEN_BUILD_SEMVER").to_string(),
|
||||
commit_sha: env!("VERGEN_GIT_SHA").to_string(),
|
||||
commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP").to_string(),
|
||||
commit_branch: env!("VERGEN_GIT_BRANCH").to_string(),
|
||||
rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets currently locked coins, see [crate::traits::VestingAccount::locked_coins]
|
||||
pub fn try_get_locked_coins(
|
||||
vesting_account_address: &str,
|
||||
@@ -666,7 +691,7 @@ pub fn try_get_original_vesting(
|
||||
deps: Deps<'_>,
|
||||
) -> Result<OriginalVestingResponse, ContractError> {
|
||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||
account.get_original_vesting()
|
||||
Ok(account.get_original_vesting())
|
||||
}
|
||||
|
||||
/// See [crate::traits::VestingAccount::get_delegated_free]
|
||||
|
||||
@@ -44,10 +44,10 @@ pub enum ContractError {
|
||||
InvalidAddress(String),
|
||||
#[error("VESTING ({}): Account already exists: {0}", line!())]
|
||||
AccountAlreadyExists(String),
|
||||
#[error("VESTING ({}): Staking account already exists: {0}", line!())]
|
||||
StakingAccountAlreadyExists(String),
|
||||
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
|
||||
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,9 +1,6 @@
|
||||
#![allow(rustdoc::private_intra_doc_links)]
|
||||
//! Nym vesting contract, providing vesting accounts with ability to stake unvested tokens
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod contract;
|
||||
mod errors;
|
||||
mod queued_migrations;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::vesting::Account;
|
||||
use crate::{contract::INITIAL_LOCKED_PLEDGE_CAP, errors::ContractError};
|
||||
use cosmwasm_std::{Addr, Api, Storage, Uint128};
|
||||
use cw_storage_plus::{Item, Map};
|
||||
use mixnet_contract_common::{IdentityKey, MixId};
|
||||
@@ -20,21 +20,6 @@ pub const DELEGATIONS: Map<'_, (u32, MixId, BlockTimestampSecs), Uint128> = Map:
|
||||
pub const ADMIN: Item<'_, String> = Item::new("adm");
|
||||
pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix");
|
||||
pub const MIX_DENOM: Item<'_, String> = Item::new("den");
|
||||
pub const LOCKED_PLEDGE_CAP: Item<'_, Uint128> = Item::new("lck");
|
||||
|
||||
pub fn locked_pledge_cap(storage: &dyn Storage) -> Uint128 {
|
||||
LOCKED_PLEDGE_CAP
|
||||
.load(storage)
|
||||
.unwrap_or(INITIAL_LOCKED_PLEDGE_CAP)
|
||||
}
|
||||
|
||||
pub fn update_locked_pledge_cap(
|
||||
amount: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
LOCKED_PLEDGE_CAP.save(storage, &amount)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_delegation(
|
||||
key: (u32, MixId, BlockTimestampSecs),
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
pub mod helpers {
|
||||
use crate::contract::instantiate;
|
||||
use crate::vesting::{populate_vesting_periods, Account};
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
|
||||
use cosmwasm_std::{Addr, Coin, Empty, Env, MemoryStorage, OwnedDeps, Storage, Uint128};
|
||||
use std::str::FromStr;
|
||||
use vesting_contract_common::messages::{InitMsg, VestingSpecification};
|
||||
use vesting_contract_common::PledgeCap;
|
||||
|
||||
pub const TEST_COIN_DENOM: &str = "unym";
|
||||
|
||||
@@ -37,6 +40,7 @@ pub mod helpers {
|
||||
},
|
||||
start_time_ts,
|
||||
periods,
|
||||
None,
|
||||
storage,
|
||||
)
|
||||
.unwrap()
|
||||
@@ -56,6 +60,29 @@ pub mod helpers {
|
||||
},
|
||||
start_time,
|
||||
periods,
|
||||
Some(PledgeCap::from_str("0.1").unwrap()),
|
||||
storage,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn vesting_account_percent_fixture(storage: &mut dyn Storage, env: &Env) -> Account {
|
||||
let start_time = env.block.time;
|
||||
let periods =
|
||||
populate_vesting_periods(start_time.seconds(), VestingSpecification::default());
|
||||
|
||||
Account::new(
|
||||
Addr::unchecked("owner"),
|
||||
Some(Addr::unchecked("staking")),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
start_time,
|
||||
periods,
|
||||
Some(PledgeCap::Percent(
|
||||
Percent::from_percentage_value(10).unwrap(),
|
||||
)),
|
||||
storage,
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
@@ -55,7 +55,7 @@ pub trait VestingAccount {
|
||||
|
||||
/// Returns amount of coins set at account creation
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl
|
||||
fn get_original_vesting(&self) -> Result<OriginalVestingResponse, ContractError>;
|
||||
fn get_original_vesting(&self) -> OriginalVestingResponse;
|
||||
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_delegated_free] for impl
|
||||
fn get_delegated_free(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::locked_pledge_cap;
|
||||
use crate::storage::save_delegation;
|
||||
use crate::storage::MIXNET_CONTRACT_ADDRESS;
|
||||
use crate::traits::DelegatingAccount;
|
||||
@@ -38,8 +37,9 @@ impl DelegatingAccount for Account {
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
let total_pledged_after = self.total_pledged_locked(storage, env)? + coin.amount;
|
||||
let locked_pledge_cap = locked_pledge_cap(storage);
|
||||
let total_pledged_locked = self.total_pledged_locked(storage, env)?;
|
||||
let total_pledged_after = total_pledged_locked + coin.amount;
|
||||
let locked_pledge_cap = self.absolute_pledge_cap()?;
|
||||
|
||||
if locked_pledge_cap < total_pledged_after {
|
||||
return Err(ContractError::LockedPledgeCapReached {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::PledgeData;
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::locked_pledge_cap;
|
||||
use crate::storage::MIXNET_CONTRACT_ADDRESS;
|
||||
use crate::traits::GatewayBondingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
@@ -22,8 +21,9 @@ impl GatewayBondingAccount for Account {
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
let total_pledged_after = self.total_pledged_locked(storage, env)? + pledge.amount;
|
||||
let locked_pledge_cap = locked_pledge_cap(storage);
|
||||
let total_pledged_locked = self.total_pledged_locked(storage, env)?;
|
||||
let total_pledged_after = total_pledged_locked + pledge.amount;
|
||||
let locked_pledge_cap = self.absolute_pledge_cap()?;
|
||||
|
||||
if locked_pledge_cap < total_pledged_after {
|
||||
return Err(ContractError::LockedPledgeCapReached {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use super::Account;
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::locked_pledge_cap;
|
||||
use crate::storage::MIXNET_CONTRACT_ADDRESS;
|
||||
use crate::traits::MixnodeBondingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
@@ -39,8 +38,9 @@ impl MixnodeBondingAccount for Account {
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
let total_pledged_after = self.total_pledged_locked(storage, env)? + pledge.amount;
|
||||
let locked_pledge_cap = locked_pledge_cap(storage);
|
||||
let total_pledged_locked = self.total_pledged_locked(storage, env)?;
|
||||
let total_pledged_after = total_pledged_locked + pledge.amount;
|
||||
let locked_pledge_cap = self.absolute_pledge_cap()?;
|
||||
|
||||
if locked_pledge_cap < total_pledged_after {
|
||||
return Err(ContractError::LockedPledgeCapReached {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user