Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5d68a5e7f | |||
| 7ddd819ff3 | |||
| 83b416d12d | |||
| b9c775c3ae | |||
| 6f669866e9 | |||
| 4e61fefec8 | |||
| b4514ecd83 | |||
| 4f6902525e | |||
| 881139e36f | |||
| 32e2557456 | |||
| 8b44820e51 | |||
| 5e6417f837 | |||
| dfb2a2f380 | |||
| d1de751850 | |||
| ecee6ca863 | |||
| 31ea3f92e2 | |||
| f19c934fae | |||
| 10d6f20de7 | |||
| 96b33bfbe4 | |||
| 444c787d0a | |||
| 61fcd4ac69 | |||
| b76802e6eb | |||
| 7d351029a4 | |||
| 4ee445c119 | |||
| 61ddeea495 | |||
| 7b802033b3 | |||
| b484f47369 | |||
| 66979df10c | |||
| 82f161fb91 | |||
| 9d0fd681d4 | |||
| c2ab47a102 | |||
| 8704c21621 | |||
| 03ffb25bf9 | |||
| 70db1ad062 | |||
| 952ed9b642 | |||
| f57fe79686 | |||
| 9179f1c351 | |||
| c4f7a1e09d | |||
| 701012a968 | |||
| 9767f72b8f | |||
| 7b10d92ca4 | |||
| 2c6e5eb673 | |||
| 02fde4e530 | |||
| cc25fc1f32 | |||
| c971e486b5 | |||
| 96a9eb6f6a | |||
| 9eeb61ea0a | |||
| 08042c61ad | |||
| 36c74f30e5 | |||
| fd1d437211 | |||
| 4956d13bdc | |||
| 6478736654 | |||
| d9f6c0723e | |||
| f86050d916 | |||
| 52f5656190 | |||
| 21cd90f238 | |||
| 4e51188d35 | |||
| 22eb199936 | |||
| a2fc1bbc96 | |||
| 621599692f | |||
| 3ad3837c87 | |||
| 4d745e3b7e | |||
| 3a053b8dd6 | |||
| 1f144690da | |||
| eec1895acc | |||
| 72e243042e | |||
| 99864cb7a9 | |||
| 3155728119 | |||
| c253b22f69 | |||
| 66f3a3e9a8 | |||
| 65a1d6d91e | |||
| 44cf9b054b | |||
| 39e2473ef3 | |||
| 93a108863c | |||
| 0905593123 | |||
| ed9223d5a3 | |||
| c2ad4e5bb4 | |||
| 5f7f5ef92d | |||
| ebac4e8564 | |||
| da81664729 | |||
| ebfb9c4bc1 | |||
| 8e7918cc45 | |||
| 008afe7a85 |
@@ -104,8 +104,6 @@ jobs:
|
||||
name: nym-binaries-artifacts
|
||||
path: |
|
||||
target/release/nym-client
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
target/release/nym-network-requester
|
||||
@@ -123,8 +121,6 @@ jobs:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
cp target/release/nym-client $OUTPUT_DIR
|
||||
cp target/release/nym-gateway $OUTPUT_DIR
|
||||
cp target/release/nym-mixnode $OUTPUT_DIR
|
||||
cp target/release/nym-socks5-client $OUTPUT_DIR
|
||||
cp target/release/nym-api $OUTPUT_DIR
|
||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||
|
||||
@@ -51,6 +51,10 @@ jobs:
|
||||
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
|
||||
|
||||
- name: Set CARGO_FEATURES
|
||||
run: |
|
||||
echo 'CARGO_FEATURES=--features wireguard' >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -60,8 +64,8 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
args: --workspace --release ${{ env.CARGO_FEATURES }}
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
+56
-1
@@ -4,6 +4,62 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.8-wispa] (2024-07-10)
|
||||
|
||||
- add event parsing to support cosmos_sdk > 0.50 ([#4697])
|
||||
- Fix NR config compatibility ([#4690])
|
||||
- Remove UserAgent constructor since it's weakly typed ([#4689])
|
||||
- [bugfix]: Node_api_check CLI looked over roles on blacklisted nodes ([#4687])
|
||||
- Add mixnodes to self describing api cache ([#4684])
|
||||
- Move and whole bump of crates to workspace and upgrade some ([#4680])
|
||||
- Remove code that refers to removed nym-network-statistics ([#4679])
|
||||
- Remove nym-network-statistics ([#4678])
|
||||
- Create UserAgent that can be passed from the binary to the nym api client ([#4677])
|
||||
- Add authenticator ([#4667])
|
||||
|
||||
[#4697]: https://github.com/nymtech/nym/pull/4697
|
||||
[#4690]: https://github.com/nymtech/nym/pull/4690
|
||||
[#4689]: https://github.com/nymtech/nym/pull/4689
|
||||
[#4687]: https://github.com/nymtech/nym/pull/4687
|
||||
[#4684]: https://github.com/nymtech/nym/pull/4684
|
||||
[#4680]: https://github.com/nymtech/nym/pull/4680
|
||||
[#4679]: https://github.com/nymtech/nym/pull/4679
|
||||
[#4678]: https://github.com/nymtech/nym/pull/4678
|
||||
[#4677]: https://github.com/nymtech/nym/pull/4677
|
||||
[#4667]: https://github.com/nymtech/nym/pull/4667
|
||||
|
||||
## [2024.7-doubledecker] (2024-07-04)
|
||||
|
||||
- Add an early return in `parse_raw_str_logs` for empty raw log strings. ([#4686])
|
||||
- Bump braces from 3.0.2 to 3.0.3 in /wasm/mix-fetch/internal-dev ([#4672])
|
||||
- add expiry returned on import ([#4670])
|
||||
- [bugfix] missing rustls feature ([#4666])
|
||||
- Bump ws from 8.13.0 to 8.17.1 in /wasm/client/internal-dev-node ([#4665])
|
||||
- Bump braces from 3.0.2 to 3.0.3 in /clients/native/examples/js-examples/websocket ([#4663])
|
||||
- Bump ws from 8.14.2 to 8.17.1 in /sdk/typescript/packages/nodejs-client ([#4662])
|
||||
- Update setup.md ([#4661])
|
||||
- New clippy lints ([#4660])
|
||||
- Bump braces from 3.0.2 to 3.0.3 in /nym-api/tests ([#4659])
|
||||
- Bump braces from 3.0.2 to 3.0.3 in /docker/typescript_client/upload_contract ([#4658])
|
||||
- Update vps-setup.md ([#4656])
|
||||
- Update configuration.md ([#4655])
|
||||
- Remove old PR template ([#4639])
|
||||
|
||||
[#4686]: https://github.com/nymtech/nym/pull/4686
|
||||
[#4672]: https://github.com/nymtech/nym/pull/4672
|
||||
[#4670]: https://github.com/nymtech/nym/pull/4670
|
||||
[#4666]: https://github.com/nymtech/nym/pull/4666
|
||||
[#4665]: https://github.com/nymtech/nym/pull/4665
|
||||
[#4663]: https://github.com/nymtech/nym/pull/4663
|
||||
[#4662]: https://github.com/nymtech/nym/pull/4662
|
||||
[#4661]: https://github.com/nymtech/nym/pull/4661
|
||||
[#4660]: https://github.com/nymtech/nym/pull/4660
|
||||
[#4659]: https://github.com/nymtech/nym/pull/4659
|
||||
[#4658]: https://github.com/nymtech/nym/pull/4658
|
||||
[#4656]: https://github.com/nymtech/nym/pull/4656
|
||||
[#4655]: https://github.com/nymtech/nym/pull/4655
|
||||
[#4639]: https://github.com/nymtech/nym/pull/4639
|
||||
|
||||
## [2024.6-chomp] (2024-06-25)
|
||||
|
||||
- Remove additional code as part of Ephemera Purge and SP and contracts ([#4650])
|
||||
@@ -481,7 +537,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#3187]: https://github.com/nymtech/nym/issues/3187
|
||||
[#3203]: https://github.com/nymtech/nym/pull/3203
|
||||
[#3199]: https://github.com/nymtech/nym/pull/3199
|
||||
>>>>>>> master
|
||||
|
||||
## [v1.1.13] (2023-03-15)
|
||||
|
||||
|
||||
Generated
+33
-19
@@ -1378,7 +1378,7 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"parking_lot 0.12.2",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
@@ -1394,7 +1394,7 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"parking_lot 0.12.2",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
@@ -2093,7 +2093,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.35"
|
||||
version = "1.1.37"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.5.4",
|
||||
@@ -3596,6 +3596,18 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mix-fetch-wasm"
|
||||
version = "1.3.0-rc.0"
|
||||
@@ -3777,7 +3789,7 @@ dependencies = [
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
@@ -3849,7 +3861,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.39"
|
||||
version = "1.1.41"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4057,7 +4069,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.37"
|
||||
version = "1.1.39"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -4136,7 +4148,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.36"
|
||||
version = "1.1.38"
|
||||
dependencies = [
|
||||
"bs58 0.5.1",
|
||||
"clap 4.5.4",
|
||||
@@ -4950,7 +4962,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.37"
|
||||
version = "1.1.39"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -5001,7 +5013,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
@@ -5264,7 +5276,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.36"
|
||||
version = "1.1.38"
|
||||
dependencies = [
|
||||
"bs58 0.5.1",
|
||||
"clap 4.5.4",
|
||||
@@ -5742,6 +5754,7 @@ name = "nym-wireguard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"defguard_wireguard_rs",
|
||||
"ip_network",
|
||||
@@ -5750,6 +5763,7 @@ dependencies = [
|
||||
"nym-network-defaults",
|
||||
"nym-task",
|
||||
"nym-wireguard-types",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"x25519-dalek",
|
||||
@@ -5777,7 +5791,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7743,7 +7757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 0.8.11",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
@@ -8452,22 +8466,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.37.0"
|
||||
version = "1.39.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
|
||||
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"mio 1.0.1",
|
||||
"parking_lot 0.12.2",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8482,9 +8495,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.2.0"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9437,6 +9450,7 @@ dependencies = [
|
||||
name = "wasm-utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"futures",
|
||||
"getrandom 0.2.15",
|
||||
"gloo-net",
|
||||
|
||||
+4
-4
@@ -277,11 +277,11 @@ tar = "0.4.40"
|
||||
tempfile = "3.5.0"
|
||||
thiserror = "1.0.48"
|
||||
time = "0.3.30"
|
||||
tokio = "1.33.0"
|
||||
tokio-stream = "0.1.14"
|
||||
tokio-test = "0.4.2"
|
||||
tokio = "1.39"
|
||||
tokio-stream = "0.1.15"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.10"
|
||||
tokio-util = "0.7.11"
|
||||
toml = "0.8.14"
|
||||
tower = "0.4.13"
|
||||
tower-http = "0.5.2"
|
||||
|
||||
@@ -52,7 +52,7 @@ References for developers:
|
||||
|
||||
You can chat to us in two places:
|
||||
* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat)
|
||||
* The various developer channels on [Discord](https://discord.gg/FaTJb8q8)
|
||||
* The various developer channels on [Discord](https://nymtech.net/go/discord)
|
||||
|
||||
### Tokenomics & Rewards
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.36"
|
||||
version = "1.1.38"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.36"
|
||||
version = "1.1.38"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::{GatewayClient, InitMessage};
|
||||
use nym_wireguard_types::{GatewayClient, InitMessage, PeerPublicKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
@@ -57,6 +57,19 @@ impl AuthenticatorRequest {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
@@ -67,4 +80,5 @@ impl AuthenticatorRequest {
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(GatewayClient),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::registration::RegistrationData;
|
||||
use nym_wireguard_types::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
@@ -33,10 +33,31 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(reply_to: Recipient, request_id: u64) -> Self {
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registred_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
@@ -64,6 +85,7 @@ impl AuthenticatorResponse {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +94,7 @@ impl AuthenticatorResponse {
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -85,4 +108,12 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
}
|
||||
|
||||
@@ -683,6 +683,24 @@ pub trait MixnetSigningClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn migrate_vested_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(fee, MixnetExecuteMsg::MigrateVestedMixNode {}, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn migrate_vested_delegation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::MigrateVestedDelegation { mix_id },
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
async fn testing_resolve_all_pending_events(
|
||||
&self,
|
||||
@@ -928,6 +946,12 @@ mod tests {
|
||||
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => client
|
||||
.withdraw_delegator_reward_on_behalf(owner.parse().unwrap(), mix_id, None)
|
||||
.ignore(),
|
||||
MixnetExecuteMsg::MigrateVestedMixNode { .. } => {
|
||||
client.migrate_vested_mixnode(None).ignore()
|
||||
}
|
||||
MixnetExecuteMsg::MigrateVestedDelegation { mix_id } => {
|
||||
client.migrate_vested_delegation(mix_id, None).ignore()
|
||||
}
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
||||
|
||||
@@ -437,6 +437,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
|
||||
use nym_vesting_contract_common::ExecuteMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
@@ -560,6 +561,9 @@ mod tests {
|
||||
VestingExecuteMsg::UpdateLockedPledgeCap { address, cap } => client
|
||||
.update_locked_pledge_cap(address.parse().unwrap(), cap, None)
|
||||
.ignore(),
|
||||
// those will never be manually called by clients
|
||||
ExecuteMsg::TrackMigratedMixnode { .. } => "explicitly_ignored".ignore(),
|
||||
ExecuteMsg::TrackMigratedDelegation { .. } => "explicitly_ignored".ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use crate::nyxd::cosmwasm_client::client_traits::SigningCosmWasmClient;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Config, GasPrice, Hash, Height};
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use crate::nyxd::contract_traits::{NymContractsProvider, TypedNymContracts};
|
||||
use crate::nyxd::cosmwasm_client::types::{
|
||||
ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::tendermint::{self, abci, block::Height, evidence::Evidence, Genesis, Hash};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use log::{debug, info};
|
||||
use nym_mixnet_contract_common::{
|
||||
InitialRewardingParams, InstantiateMsg, OperatingCostRange, Percent, ProfitMarginRange,
|
||||
};
|
||||
use nym_network_defaults::mainnet::MIX_DENOM;
|
||||
use nym_network_defaults::TOTAL_SUPPLY;
|
||||
use nym_validator_client::nyxd::{AccountId, Coin};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn default_maximum_operating_cost() -> Coin {
|
||||
Coin::new(TOTAL_SUPPLY, MIX_DENOM.base)
|
||||
}
|
||||
|
||||
pub fn default_minimum_operating_cost() -> Coin {
|
||||
Coin::new(0, MIX_DENOM.base)
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
@@ -50,6 +61,18 @@ pub struct Args {
|
||||
|
||||
#[clap(long, default_value_t = 240)]
|
||||
pub active_set_size: u32,
|
||||
|
||||
#[clap(long, default_value_t = Percent::zero())]
|
||||
pub minimum_profit_margin_percent: Percent,
|
||||
|
||||
#[clap(long, default_value_t = Percent::hundred())]
|
||||
pub maximum_profit_margin_percent: Percent,
|
||||
|
||||
#[clap(long, default_value_t = default_minimum_operating_cost())]
|
||||
pub minimum_interval_operating_cost: Coin,
|
||||
|
||||
#[clap(long, default_value_t = default_maximum_operating_cost())]
|
||||
pub maximum_interval_operating_cost: Coin,
|
||||
}
|
||||
|
||||
pub async fn generate(args: Args) {
|
||||
@@ -97,6 +120,10 @@ pub async fn generate(args: Args) {
|
||||
.expect("Rewarding (mix) denom has to be set")
|
||||
});
|
||||
|
||||
if args.minimum_interval_operating_cost.denom != args.maximum_interval_operating_cost.denom {
|
||||
panic!("different denoms for operating cost bounds")
|
||||
}
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
rewarding_validator_address: rewarding_validator_address.to_string(),
|
||||
vesting_contract_address: vesting_contract_address.to_string(),
|
||||
@@ -104,6 +131,14 @@ pub async fn generate(args: Args) {
|
||||
epochs_in_interval: args.epochs_in_interval,
|
||||
epoch_duration: Duration::from_secs(args.epoch_duration),
|
||||
initial_rewarding_params,
|
||||
profit_margin: ProfitMarginRange {
|
||||
minimum: args.minimum_profit_margin_percent,
|
||||
maximum: args.maximum_profit_margin_percent,
|
||||
},
|
||||
interval_operating_cost: OperatingCostRange {
|
||||
minimum: args.minimum_interval_operating_cost.amount.into(),
|
||||
maximum: args.maximum_interval_operating_cost.amount.into(),
|
||||
},
|
||||
};
|
||||
|
||||
debug!("instantiate_msg: {:?}", instantiate_msg);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn migrate_vested_delegation(args: Args, client: SigningClient) {
|
||||
let mix_id = match args.mix_id {
|
||||
Some(mix_id) => mix_id,
|
||||
None => {
|
||||
let identity_key = args
|
||||
.identity_key
|
||||
.expect("either mix_id or mix_identity has to be specified");
|
||||
let node_details = client
|
||||
.get_mixnode_details_by_identity(identity_key)
|
||||
.await
|
||||
.expect("contract query failed")
|
||||
.mixnode_details
|
||||
.expect("mixnode with the specified identity doesnt exist");
|
||||
node_details.mix_id()
|
||||
}
|
||||
};
|
||||
|
||||
let res = client
|
||||
.migrate_vested_delegation(mix_id, None)
|
||||
.await
|
||||
.expect("failed to migrate delegation!");
|
||||
|
||||
info!("migration result: {:?}", res)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ pub mod rewards;
|
||||
|
||||
pub mod delegate_to_mixnode;
|
||||
pub mod delegate_to_multiple_mixnodes;
|
||||
pub mod migrate_vested_delegation;
|
||||
pub mod query_for_delegations;
|
||||
pub mod undelegate_from_mixnode;
|
||||
pub mod vesting_delegate_to_mixnode;
|
||||
@@ -35,4 +36,6 @@ pub enum MixnetDelegatorsCommands {
|
||||
DelegateVesting(vesting_delegate_to_mixnode::Args),
|
||||
/// Undelegate from a mixnode (when originally using locked tokens)
|
||||
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
|
||||
/// Migrate the delegation to use liquid tokens
|
||||
MigrateVestedDelegation(migrate_vested_delegation::Args),
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
|
||||
mix_id,
|
||||
amount,
|
||||
proxy,
|
||||
..
|
||||
} => {
|
||||
if owner.as_str() == client.nyxd.address().as_ref() {
|
||||
table.add_row(vec![
|
||||
@@ -111,6 +112,7 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
|
||||
owner,
|
||||
mix_id,
|
||||
proxy,
|
||||
..
|
||||
} => {
|
||||
if owner.as_str() == client.nyxd.address().as_ref() {
|
||||
table.add_row(vec![
|
||||
|
||||
+2
-13
@@ -8,7 +8,7 @@ use cosmwasm_std::Coin;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_mixnet_contract_common::construct_gateway_bonding_sign_payload;
|
||||
use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -39,10 +39,6 @@ pub struct Args {
|
||||
)]
|
||||
pub amount: u128,
|
||||
|
||||
/// Indicates whether the gateway is going to get bonded via a vesting account
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
@@ -74,15 +70,8 @@ pub async fn create_payload(args: Args, client: SigningClient) {
|
||||
};
|
||||
|
||||
let address = account_id_to_cw_addr(&client.address());
|
||||
let proxy = if args.with_vesting_account {
|
||||
Some(account_id_to_cw_addr(
|
||||
client.vesting_contract_address().unwrap(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let payload = construct_gateway_bonding_sign_payload(nonce, address, proxy, coin, gateway);
|
||||
let payload = construct_gateway_bonding_sign_payload(nonce, address, coin, gateway);
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -5,33 +5,21 @@ use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Label that is going to be used for creating the family
|
||||
#[arg(long)]
|
||||
pub family_label: String,
|
||||
|
||||
/// Indicates whether the family is going to get created via a vesting account
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
}
|
||||
|
||||
pub async fn create_family(args: Args, client: SigningClient) {
|
||||
info!("Create family");
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to create family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to create family")
|
||||
};
|
||||
let res = client
|
||||
.create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to create family");
|
||||
|
||||
info!("Family creation result: {:?}", res);
|
||||
}
|
||||
|
||||
+3
-16
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::{account_id_to_cw_addr, DataWrapper};
|
||||
use crate::utils::DataWrapper;
|
||||
use clap::Parser;
|
||||
use cosmrs::AccountId;
|
||||
use log::info;
|
||||
@@ -10,7 +10,7 @@ use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::construct_family_join_permit;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -18,10 +18,6 @@ pub struct Args {
|
||||
#[arg(long)]
|
||||
pub address: AccountId,
|
||||
|
||||
/// Indicates whether the member joining the family is going to use the vesting account for joining.
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
// might as well validate the value when parsing the arguments
|
||||
/// Identity of the member for whom we're issuing the permit
|
||||
#[arg(long)]
|
||||
@@ -68,18 +64,9 @@ pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryCli
|
||||
}
|
||||
};
|
||||
|
||||
// let address = account_id_to_cw_addr(&args.address);
|
||||
let proxy = if args.with_vesting_account {
|
||||
Some(account_id_to_cw_addr(
|
||||
client.vesting_contract_address().unwrap(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let head = FamilyHead::new(mixnode.bond_information.identity());
|
||||
|
||||
let payload = construct_family_join_permit(nonce, head, proxy, args.member.to_base58_string());
|
||||
let payload = construct_family_join_permit(nonce, head, args.member.to_base58_string());
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use nym_contracts_common::signing::MessageSignature;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -16,10 +15,6 @@ pub struct Args {
|
||||
#[arg(long)]
|
||||
pub family_head: identity::PublicKey,
|
||||
|
||||
/// Indicates whether the member joining the family is going to do so via the vesting contract
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
/// Permission, as provided by the family head, for joining the family
|
||||
#[arg(long)]
|
||||
pub join_permit: MessageSignature,
|
||||
@@ -30,17 +25,10 @@ pub async fn join_family(args: Args, client: SigningClient) {
|
||||
|
||||
let family_head = FamilyHead::new(args.family_head.to_base58_string());
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family")
|
||||
};
|
||||
let res = client
|
||||
.join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family");
|
||||
|
||||
info!("Family join result: {:?}", res);
|
||||
}
|
||||
|
||||
@@ -7,17 +7,12 @@ use log::info;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// The head of the family that we intend to leave
|
||||
#[arg(long)]
|
||||
pub family_head: identity::PublicKey,
|
||||
|
||||
/// Indicates whether we joined the family via the vesting contract
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
}
|
||||
|
||||
pub async fn leave_family(args: Args, client: SigningClient) {
|
||||
@@ -25,17 +20,10 @@ pub async fn leave_family(args: Args, client: SigningClient) {
|
||||
|
||||
let family_head = FamilyHead::new(args.family_head.to_base58_string());
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family")
|
||||
};
|
||||
let res = client
|
||||
.leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family");
|
||||
|
||||
info!("Family leave result: {:?}", res);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
pub async fn migrate_vested_mixnode(_args: Args, client: SigningClient) {
|
||||
let res = client
|
||||
.migrate_vested_mixnode(None)
|
||||
.await
|
||||
.expect("failed to migrate mixnode!");
|
||||
|
||||
info!("migration result: {:?}", res)
|
||||
}
|
||||
+2
-13
@@ -11,7 +11,7 @@ use nym_mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNode
|
||||
use nym_network_defaults::{
|
||||
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
|
||||
};
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
|
||||
use nym_validator_client::nyxd::CosmWasmCoin;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -52,10 +52,6 @@ pub struct Args {
|
||||
)]
|
||||
pub amount: u128,
|
||||
|
||||
/// Indicates whether the mixnode is going to get bonded via a vesting account
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
@@ -100,16 +96,9 @@ pub async fn create_payload(args: Args, client: SigningClient) {
|
||||
};
|
||||
|
||||
let address = account_id_to_cw_addr(&client.address());
|
||||
let proxy = if args.with_vesting_account {
|
||||
Some(account_id_to_cw_addr(
|
||||
client.vesting_contract_address().unwrap(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let payload =
|
||||
construct_mixnode_bonding_sign_payload(nonce, address, proxy, coin, mixnode, cost_params);
|
||||
construct_mixnode_bonding_sign_payload(nonce, address, coin, mixnode, cost_params);
|
||||
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
|
||||
println!("{}", args.output.format(&wrapper))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ pub mod bond_mixnode;
|
||||
pub mod decrease_pledge;
|
||||
pub mod families;
|
||||
pub mod keys;
|
||||
pub mod migrate_vested_mixnode;
|
||||
pub mod mixnode_bonding_sign_payload;
|
||||
pub mod pledge_more;
|
||||
pub mod rewards;
|
||||
@@ -52,4 +53,6 @@ pub enum MixnetOperatorsMixnodeCommands {
|
||||
DecreasePledge(decrease_pledge::Args),
|
||||
/// Decrease pledge with locked tokens
|
||||
DecreasePledgeVesting(vesting_decrease_pledge::Args),
|
||||
/// Migrate the mixnode to use liquid tokens
|
||||
MigrateVestedNode(migrate_vested_mixnode::Args),
|
||||
}
|
||||
|
||||
@@ -218,7 +218,6 @@ where
|
||||
#[derive(Serialize)]
|
||||
pub struct ContractMessageContent<T> {
|
||||
pub sender: Addr,
|
||||
pub proxy: Option<Addr>,
|
||||
pub funds: Vec<Coin>,
|
||||
pub data: T,
|
||||
}
|
||||
@@ -233,25 +232,17 @@ where
|
||||
}
|
||||
|
||||
impl<T> ContractMessageContent<T> {
|
||||
pub fn new(sender: Addr, proxy: Option<Addr>, funds: Vec<Coin>, data: T) -> Self {
|
||||
pub fn new(sender: Addr, funds: Vec<Coin>, data: T) -> Self {
|
||||
ContractMessageContent {
|
||||
sender,
|
||||
proxy,
|
||||
funds,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_info(info: MessageInfo, signer: Addr, data: T) -> Self {
|
||||
let proxy = if info.sender == signer {
|
||||
None
|
||||
} else {
|
||||
Some(info.sender)
|
||||
};
|
||||
|
||||
ContractMessageContent {
|
||||
sender: signer,
|
||||
proxy,
|
||||
funds: info.funds,
|
||||
data,
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ impl Delegation {
|
||||
cumulative_reward_ratio: Decimal,
|
||||
amount: Coin,
|
||||
height: u64,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
amount.amount <= TOKEN_SUPPLY,
|
||||
@@ -78,7 +77,7 @@ impl Delegation {
|
||||
cumulative_reward_ratio,
|
||||
amount,
|
||||
height,
|
||||
proxy,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{EpochEventId, EpochState, IdentityKey, MixId};
|
||||
use crate::{EpochEventId, EpochState, IdentityKey, MixId, OperatingCostRange, ProfitMarginRange};
|
||||
use contracts_common::signing::verifier::ApiVerifierError;
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -76,21 +77,11 @@ pub enum MixnetContractError {
|
||||
#[error("Received multiple coin types during staking")]
|
||||
MultipleDenoms,
|
||||
|
||||
#[error("Proxy address mismatch, expected {existing}, got {incoming}")]
|
||||
ProxyMismatch { existing: String, incoming: String },
|
||||
|
||||
#[error("Proxy address ({received}) is not set to the vesting contract ({vesting_contract})")]
|
||||
ProxyIsNotVestingContract {
|
||||
received: Addr,
|
||||
vesting_contract: Addr,
|
||||
},
|
||||
#[error(
|
||||
"Sender of this message ({received}) is not the vesting contract ({vesting_contract})"
|
||||
)]
|
||||
SenderIsNotVestingContract {
|
||||
received: Addr,
|
||||
vesting_contract: Addr,
|
||||
},
|
||||
|
||||
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
|
||||
MalformedEd25519IdentityKey(String),
|
||||
@@ -239,6 +230,30 @@ pub enum MixnetContractError {
|
||||
#[from]
|
||||
source: ApiVerifierError,
|
||||
},
|
||||
|
||||
#[error("this operation is no longer allowed to be performed with vesting tokens. please move them to your liquid balance and try again")]
|
||||
DisabledVestingOperation,
|
||||
|
||||
#[error(
|
||||
"this mixnode has not been bonded with the vesting tokens or has already been migrated"
|
||||
)]
|
||||
NotAVestingMixnode,
|
||||
|
||||
#[error("this delegation has not been performed with the vesting tokens or has already been migrated")]
|
||||
NotAVestingDelegation,
|
||||
|
||||
#[error("the provided profit margin ({provided}) is outside the allowed range: {range}")]
|
||||
ProfitMarginOutsideRange {
|
||||
provided: Percent,
|
||||
range: ProfitMarginRange,
|
||||
},
|
||||
|
||||
#[error("the provided interval operating cost ({provided}{denom}) is outside the allowed range: {range}")]
|
||||
OperatingCostOutsideRange {
|
||||
denom: String,
|
||||
provided: Uint128,
|
||||
range: OperatingCostRange,
|
||||
},
|
||||
}
|
||||
|
||||
impl MixnetContractError {
|
||||
|
||||
@@ -103,7 +103,6 @@ impl Display for MixnetEventType {
|
||||
// attributes that are used in multiple places
|
||||
pub const OWNER_KEY: &str = "owner";
|
||||
pub const AMOUNT_KEY: &str = "amount";
|
||||
pub const PROXY_KEY: &str = "proxy";
|
||||
|
||||
// event-specific attributes
|
||||
|
||||
@@ -163,7 +162,6 @@ pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval";
|
||||
pub fn new_delegation_event(
|
||||
created_at: BlockHeight,
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_id: MixId,
|
||||
unit_reward: Decimal,
|
||||
@@ -171,58 +169,34 @@ pub fn new_delegation_event(
|
||||
Event::new(MixnetEventType::Delegation)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
.add_attribute(UNIT_REWARD_KEY, unit_reward.to_string())
|
||||
}
|
||||
|
||||
pub fn new_delegation_on_unbonded_node_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_delegation_on_unbonded_node_event(delegator: &Addr, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::Delegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_pending_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_pending_delegation_event(delegator: &Addr, amount: &Coin, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::PendingDelegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_withdraw_operator_reward_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Coin,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_withdraw_operator_reward_event(owner: &Addr, amount: Coin, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::WithdrawOperatorReward)
|
||||
.add_attribute(OWNER_KEY, owner.as_str())
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_withdraw_delegator_reward_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Coin,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_withdraw_delegator_reward_event(delegator: &Addr, amount: Coin, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::WithdrawDelegatorReward)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
}
|
||||
@@ -278,59 +252,43 @@ pub fn new_pending_rewarding_params_update_event(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_undelegation_event(
|
||||
created_at: BlockHeight,
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_undelegation_event(created_at: BlockHeight, delegator: &Addr, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::Undelegation)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_pending_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
pub fn new_pending_undelegation_event(delegator: &Addr, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::PendingUndelegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_gateway_bonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::GatewayBonding)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_gateway_unbonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::GatewayUnbonding)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_mixnode_bonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
mix_id: MixId,
|
||||
@@ -341,7 +299,6 @@ pub fn new_mixnode_bonding_event(
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(ASSIGNED_LAYER_KEY, assigned_layer)
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
@@ -380,7 +337,6 @@ pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Ev
|
||||
|
||||
pub fn new_pending_mixnode_unbonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
@@ -388,43 +344,33 @@ pub fn new_pending_mixnode_unbonding_event(
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
}
|
||||
|
||||
pub fn new_mixnode_config_update_event(
|
||||
mix_id: MixId,
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
update: &MixNodeConfigUpdate,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeConfigUpdate)
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
|
||||
}
|
||||
|
||||
pub fn new_gateway_config_update_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
update: &GatewayConfigUpdate,
|
||||
) -> Event {
|
||||
pub fn new_gateway_config_update_event(owner: &Addr, update: &GatewayConfigUpdate) -> Event {
|
||||
Event::new(MixnetEventType::GatewayConfigUpdate)
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(UPDATED_GATEWAY_CONFIG_KEY, update.to_inline_json())
|
||||
}
|
||||
|
||||
pub fn new_mixnode_pending_cost_params_update_event(
|
||||
mix_id: MixId,
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
new_costs: &MixNodeCostParams,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(OWNER_KEY, owner)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::{IdentityKey, IdentityKeyRef};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -84,10 +83,10 @@ impl FamilyHead {
|
||||
}
|
||||
|
||||
impl Family {
|
||||
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: String) -> Self {
|
||||
pub fn new(head: FamilyHead, label: String) -> Self {
|
||||
Family {
|
||||
head,
|
||||
proxy: proxy.map(|p| p.to_string()),
|
||||
proxy: None,
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,19 +55,13 @@ pub struct GatewayBond {
|
||||
}
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn new(
|
||||
pledge_amount: Coin,
|
||||
owner: Addr,
|
||||
block_height: u64,
|
||||
gateway: Gateway,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
pub fn new(pledge_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
|
||||
GatewayBond {
|
||||
pledge_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
proxy,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ use crate::helpers::IntoBaseDecimal;
|
||||
use crate::reward_params::{NodeRewardParams, RewardingParams};
|
||||
use crate::rewarding::helpers::truncate_reward;
|
||||
use crate::rewarding::RewardDistribution;
|
||||
use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey};
|
||||
use crate::{
|
||||
Delegation, EpochEventId, EpochId, IdentityKey, MixId, OperatingCostRange, Percent,
|
||||
ProfitMarginRange, SphinxKey,
|
||||
};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
@@ -152,6 +155,16 @@ impl MixNodeRewarding {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn normalise_profit_margin(&mut self, allowed_range: ProfitMarginRange) {
|
||||
self.cost_params.profit_margin_percent =
|
||||
allowed_range.normalise(self.cost_params.profit_margin_percent)
|
||||
}
|
||||
|
||||
pub fn normalise_operating_cost(&mut self, allowed_range: OperatingCostRange) {
|
||||
self.cost_params.interval_operating_cost.amount =
|
||||
allowed_range.normalise(self.cost_params.interval_operating_cost.amount)
|
||||
}
|
||||
|
||||
/// Determines whether this node is still bonded. This is performed via a simple check,
|
||||
/// if there are no tokens left associated with the operator, it means they have unbonded
|
||||
/// and those params only exist for the purposes of calculating rewards for delegators that
|
||||
@@ -518,7 +531,6 @@ impl MixNodeBond {
|
||||
original_pledge: Coin,
|
||||
layer: Layer,
|
||||
mix_node: MixNode,
|
||||
proxy: Option<Addr>,
|
||||
bonding_height: u64,
|
||||
) -> Self {
|
||||
MixNodeBond {
|
||||
@@ -527,7 +539,7 @@ impl MixNodeBond {
|
||||
original_pledge,
|
||||
layer,
|
||||
mix_node,
|
||||
proxy,
|
||||
proxy: None,
|
||||
bonding_height,
|
||||
is_unbonding: false,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::delegation::{self, OwnerProxySubKey};
|
||||
@@ -12,6 +12,7 @@ use crate::reward_params::{
|
||||
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
|
||||
};
|
||||
use crate::types::{ContractStateParams, LayerAssignment, MixId};
|
||||
use crate::{OperatingCostRange, ProfitMarginRange};
|
||||
use contracts_common::{signing::MessageSignature, IdentityKey, Percent};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
@@ -57,6 +58,12 @@ pub struct InstantiateMsg {
|
||||
pub epochs_in_interval: u32,
|
||||
pub epoch_duration: Duration,
|
||||
pub initial_rewarding_params: InitialRewardingParams,
|
||||
|
||||
#[serde(default)]
|
||||
pub profit_margin: ProfitMarginRange,
|
||||
|
||||
#[serde(default)]
|
||||
pub interval_operating_cost: OperatingCostRange,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -269,6 +276,12 @@ pub enum ExecuteMsg {
|
||||
owner: String,
|
||||
},
|
||||
|
||||
// vesting migration:
|
||||
MigrateVestedMixNode {},
|
||||
MigrateVestedDelegation {
|
||||
mix_id: MixId,
|
||||
},
|
||||
|
||||
// testing-only
|
||||
#[cfg(feature = "contract-testing")]
|
||||
TestingResolveAllPendingEvents {
|
||||
@@ -381,6 +394,9 @@ impl ExecuteMsg {
|
||||
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => {
|
||||
format!("withdrawing delegator reward from mixnode {mix_id} on behalf")
|
||||
}
|
||||
ExecuteMsg::MigrateVestedMixNode { .. } => "migrate vested mixnode".into(),
|
||||
ExecuteMsg::MigrateVestedDelegation { .. } => "migrate vested delegation".to_string(),
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
ExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
||||
"resolving all pending events".into()
|
||||
|
||||
@@ -38,6 +38,7 @@ pub enum PendingEpochEventKind {
|
||||
/// Request to create a delegation towards particular mixnode.
|
||||
/// Note that if such delegation already exists, it will get updated with the provided token amount.
|
||||
#[serde(alias = "Delegate")]
|
||||
#[non_exhaustive]
|
||||
Delegate {
|
||||
/// The address of the owner of the delegation.
|
||||
owner: Addr,
|
||||
@@ -55,6 +56,7 @@ pub enum PendingEpochEventKind {
|
||||
|
||||
/// Request to remove delegation from particular mixnode.
|
||||
#[serde(alias = "Undelegate")]
|
||||
#[non_exhaustive]
|
||||
Undelegate {
|
||||
/// The address of the owner of the delegation.
|
||||
owner: Addr,
|
||||
@@ -109,6 +111,23 @@ impl PendingEpochEventKind {
|
||||
kind: self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_delegate(owner: Addr, mix_id: MixId, amount: Coin) -> Self {
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_undelegate(owner: Addr, mix_id: MixId) -> Self {
|
||||
PendingEpochEventKind::Undelegate {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
|
||||
|
||||
-1
@@ -47,7 +47,6 @@ impl SimulatedNode {
|
||||
self.rewarding_details.total_unit_reward,
|
||||
delegation,
|
||||
42,
|
||||
None,
|
||||
);
|
||||
|
||||
self.delegations.insert(delegator, delegation);
|
||||
|
||||
@@ -37,13 +37,12 @@ impl SigningPurpose for MixnodeBondingPayload {
|
||||
pub fn construct_mixnode_bonding_sign_payload(
|
||||
nonce: Nonce,
|
||||
sender: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
mix_node: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
) -> SignableMixNodeBondingMsg {
|
||||
let payload = MixnodeBondingPayload::new(mix_node, cost_params);
|
||||
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
|
||||
let content = ContractMessageContent::new(sender, vec![pledge], payload);
|
||||
|
||||
SignableMessage::new(nonce, content)
|
||||
}
|
||||
@@ -68,12 +67,11 @@ impl SigningPurpose for GatewayBondingPayload {
|
||||
pub fn construct_gateway_bonding_sign_payload(
|
||||
nonce: Nonce,
|
||||
sender: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
gateway: Gateway,
|
||||
) -> SignableGatewayBondingMsg {
|
||||
let payload = GatewayBondingPayload::new(gateway);
|
||||
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
|
||||
let content = ContractMessageContent::new(sender, vec![pledge], payload);
|
||||
|
||||
SignableMessage::new(nonce, content)
|
||||
}
|
||||
@@ -82,17 +80,14 @@ pub fn construct_gateway_bonding_sign_payload(
|
||||
pub struct FamilyJoinPermit {
|
||||
// the granter of this permit
|
||||
family_head: FamilyHead,
|
||||
// whether the **member** will want to join via the proxy (i.e. vesting contract)
|
||||
proxy: Option<Addr>,
|
||||
// the actual member we want to permit to join
|
||||
member_node: IdentityKey,
|
||||
}
|
||||
|
||||
impl FamilyJoinPermit {
|
||||
pub fn new(family_head: FamilyHead, proxy: Option<Addr>, member_node: IdentityKey) -> Self {
|
||||
pub fn new(family_head: FamilyHead, member_node: IdentityKey) -> Self {
|
||||
Self {
|
||||
family_head,
|
||||
proxy,
|
||||
member_node,
|
||||
}
|
||||
}
|
||||
@@ -107,10 +102,9 @@ impl SigningPurpose for FamilyJoinPermit {
|
||||
pub fn construct_family_join_permit(
|
||||
nonce: Nonce,
|
||||
family_head: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
member_node: IdentityKey,
|
||||
) -> SignableFamilyJoinPermitMsg {
|
||||
let payload = FamilyJoinPermit::new(family_head, proxy, member_node);
|
||||
let payload = FamilyJoinPermit::new(family_head, member_node);
|
||||
|
||||
// note: we're NOT wrapping it in `ContractMessageContent` because the family head is not going to be the one
|
||||
// sending the message to the contract
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::Layer;
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
use cosmwasm_std::Coin;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Index;
|
||||
|
||||
// type aliases for better reasoning about available data
|
||||
@@ -15,6 +17,65 @@ pub type SphinxKeyRef<'a> = &'a str;
|
||||
pub type MixId = u32;
|
||||
pub type BlockHeight = u64;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct RangedValue<T> {
|
||||
pub minimum: T,
|
||||
pub maximum: T,
|
||||
}
|
||||
|
||||
impl<T> Copy for RangedValue<T> where T: Copy {}
|
||||
|
||||
pub type ProfitMarginRange = RangedValue<Percent>;
|
||||
pub type OperatingCostRange = RangedValue<Uint128>;
|
||||
|
||||
impl<T> Display for RangedValue<T>
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} - {}", self.minimum, self.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProfitMarginRange {
|
||||
fn default() -> Self {
|
||||
ProfitMarginRange {
|
||||
minimum: Percent::zero(),
|
||||
maximum: Percent::hundred(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OperatingCostRange {
|
||||
fn default() -> Self {
|
||||
OperatingCostRange {
|
||||
minimum: Uint128::zero(),
|
||||
|
||||
// 1 billion (native tokens, i.e. 1 billion * 1'000'000 base tokens) - the total supply
|
||||
maximum: Uint128::new(1_000_000_000_000_000),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RangedValue<T>
|
||||
where
|
||||
T: Copy + PartialOrd + PartialEq,
|
||||
{
|
||||
pub fn normalise(&self, value: T) -> T {
|
||||
if value < self.minimum {
|
||||
self.minimum
|
||||
} else if value > self.maximum {
|
||||
self.maximum
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
pub fn within_range(&self, value: T) -> bool {
|
||||
value >= self.minimum && value <= self.maximum
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies layer assignment for the given mixnode.
|
||||
#[cw_serde]
|
||||
pub struct LayerAssignment {
|
||||
@@ -154,4 +215,14 @@ pub struct ContractStateParams {
|
||||
|
||||
/// Minimum amount a gateway must pledge to get into the system.
|
||||
pub minimum_gateway_pledge: Coin,
|
||||
|
||||
/// Defines the allowed profit margin range of operators.
|
||||
/// default: 0% - 100%
|
||||
#[serde(default)]
|
||||
pub profit_margin: ProfitMarginRange,
|
||||
|
||||
/// Defines the allowed interval operating cost range of operators.
|
||||
/// default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)
|
||||
#[serde(default)]
|
||||
pub interval_operating_cost: OperatingCostRange,
|
||||
}
|
||||
|
||||
@@ -167,3 +167,11 @@ pub fn new_track_undelegation_event() -> Event {
|
||||
pub fn new_track_reward_event() -> Event {
|
||||
Event::new(TRACK_REWARD_EVENT_TYPE)
|
||||
}
|
||||
|
||||
pub fn new_track_migrate_mixnode_event() -> Event {
|
||||
Event::new("track_migrate_vesting_mixnode")
|
||||
}
|
||||
|
||||
pub fn new_track_migrate_delegation_event() -> Event {
|
||||
Event::new("track_migrate_vesting_delegation")
|
||||
}
|
||||
|
||||
@@ -136,6 +136,14 @@ pub enum ExecuteMsg {
|
||||
address: String,
|
||||
cap: PledgeCap,
|
||||
},
|
||||
TrackMigratedMixnode {
|
||||
owner: String,
|
||||
},
|
||||
// no need to track migrated gateways as there are no vesting gateways on mainnet
|
||||
TrackMigratedDelegation {
|
||||
owner: String,
|
||||
mix_id: MixId,
|
||||
},
|
||||
}
|
||||
|
||||
impl ExecuteMsg {
|
||||
@@ -171,6 +179,10 @@ impl ExecuteMsg {
|
||||
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
|
||||
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
|
||||
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
|
||||
ExecuteMsg::TrackMigratedMixnode { .. } => "VestingExecuteMsg::TrackMigratedMixnode",
|
||||
ExecuteMsg::TrackMigratedDelegation { .. } => {
|
||||
"VestingExecuteMsg::TrackMigratedDelegation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{v6, v7};
|
||||
|
||||
impl From<v7::response::StaticConnectFailureReason> for v6::response::StaticConnectFailureReason {
|
||||
fn from(failure: v7::response::StaticConnectFailureReason) -> Self {
|
||||
match failure {
|
||||
v7::response::StaticConnectFailureReason::RequestedIpAlreadyInUse => {
|
||||
v6::response::StaticConnectFailureReason::RequestedIpAlreadyInUse
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse => {
|
||||
v6::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::OutOfDateTimestamp => {
|
||||
v6::response::StaticConnectFailureReason::Other("out of date timestamp".to_string())
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::Other(reason) => {
|
||||
v6::response::StaticConnectFailureReason::Other(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::DynamicConnectFailureReason> for v6::response::DynamicConnectFailureReason {
|
||||
fn from(failure: v7::response::DynamicConnectFailureReason) -> Self {
|
||||
match failure {
|
||||
v7::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse => {
|
||||
v6::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
v7::response::DynamicConnectFailureReason::NoAvailableIp => {
|
||||
v6::response::DynamicConnectFailureReason::NoAvailableIp
|
||||
}
|
||||
v7::response::DynamicConnectFailureReason::Other(err) => {
|
||||
v6::response::DynamicConnectFailureReason::Other(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::InfoResponseReply> for v6::response::InfoResponseReply {
|
||||
fn from(reply: v7::response::InfoResponseReply) -> Self {
|
||||
match reply {
|
||||
v7::response::InfoResponseReply::Generic { msg } => {
|
||||
v6::response::InfoResponseReply::Generic { msg }
|
||||
}
|
||||
v7::response::InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
} => v6::response::InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
},
|
||||
v7::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
|
||||
v6::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::InfoLevel> for v6::response::InfoLevel {
|
||||
fn from(level: v7::response::InfoLevel) -> Self {
|
||||
match level {
|
||||
v7::response::InfoLevel::Info => v6::response::InfoLevel::Info,
|
||||
v7::response::InfoLevel::Warn => v6::response::InfoLevel::Warn,
|
||||
v7::response::InfoLevel::Error => v6::response::InfoLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod conversion;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
|
||||
@@ -198,6 +198,17 @@ impl IpPacketRequestData {
|
||||
| IpPacketRequestData::Health(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
|
||||
match self {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
IpPacketRequestData::Ping(_) => None,
|
||||
IpPacketRequestData::Health(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A static connect request is when the client provides the internal IP address it will use on the
|
||||
|
||||
@@ -42,6 +42,7 @@ pub struct NymNetworkDetails {
|
||||
pub endpoints: Vec<ValidatorDetails>,
|
||||
pub contracts: NymContracts,
|
||||
pub explorer_api: Option<String>,
|
||||
pub nym_vpn_api_url: Option<String>,
|
||||
}
|
||||
|
||||
// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms
|
||||
@@ -71,6 +72,7 @@ impl NymNetworkDetails {
|
||||
endpoints: Default::default(),
|
||||
contracts: Default::default(),
|
||||
explorer_api: Default::default(),
|
||||
nym_vpn_api_url: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +128,7 @@ impl NymNetworkDetails {
|
||||
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
|
||||
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
|
||||
.with_explorer_api(get_optional_env(var_names::EXPLORER_API))
|
||||
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
|
||||
}
|
||||
|
||||
pub fn new_mainnet() -> Self {
|
||||
@@ -155,6 +158,7 @@ impl NymNetworkDetails {
|
||||
),
|
||||
},
|
||||
explorer_api: parse_optional_str(mainnet::EXPLORER_API),
|
||||
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +267,19 @@ impl NymNetworkDetails {
|
||||
self.explorer_api = endpoint.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_vpn_api_url<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
|
||||
self.nym_vpn_api_url = endpoint.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nym_vpn_api_url(&self) -> Option<Url> {
|
||||
self.nym_vpn_api_url.as_ref().map(|url| {
|
||||
url.parse()
|
||||
.expect("the provided nym-vpn api url is invalid!")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -31,6 +31,7 @@ pub const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
|
||||
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
|
||||
pub const NYM_VPN_API: &str = "https://nymvpn.net/api/";
|
||||
|
||||
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
|
||||
pub const EXIT_POLICY_URL: &str =
|
||||
@@ -114,6 +115,7 @@ pub fn export_to_env() {
|
||||
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
|
||||
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
|
||||
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
|
||||
set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
|
||||
}
|
||||
|
||||
pub fn export_to_env_if_not_set() {
|
||||
|
||||
@@ -24,6 +24,7 @@ pub const NYM_API: &str = "NYM_API";
|
||||
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
|
||||
pub const EXPLORER_API: &str = "EXPLORER_API";
|
||||
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
|
||||
pub const NYM_VPN_API: &str = "NYM_VPN_API";
|
||||
|
||||
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ pub enum NymTopologyError {
|
||||
PayloadBuilder,
|
||||
|
||||
#[error("Outfox: {0}")]
|
||||
#[cfg(feature = "outfox")]
|
||||
Outfox(#[from] nym_sphinx_types::OutfoxError),
|
||||
|
||||
#[error("{0}")]
|
||||
|
||||
@@ -7,6 +7,7 @@ use nym_sphinx_routing::SphinxRouteMaker;
|
||||
use nym_sphinx_types::Node;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct NymTopologyRouteProvider<R> {
|
||||
rng: R,
|
||||
inner: NymTopology,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use nym_mixnet_contract_common::ContractsCommonError;
|
||||
use nym_validator_client::error::TendermintRpcError;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::{nyxd::error::NyxdError, ValidatorClientError};
|
||||
@@ -8,6 +9,9 @@ use thiserror::Error;
|
||||
// TODO: ask @MS why this even exists
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TypesError {
|
||||
#[error(transparent)]
|
||||
ContractsCommon(#[from] ContractsCommonError),
|
||||
|
||||
#[error("{source}")]
|
||||
NyxdError {
|
||||
#[from]
|
||||
|
||||
@@ -84,6 +84,7 @@ impl PendingEpochEventData {
|
||||
mix_id,
|
||||
amount,
|
||||
proxy,
|
||||
..
|
||||
} => Ok(PendingEpochEventData::Delegate {
|
||||
owner: owner.into_string(),
|
||||
mix_id,
|
||||
@@ -94,6 +95,7 @@ impl PendingEpochEventData {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy,
|
||||
..
|
||||
} => Ok(PendingEpochEventData::Undelegate {
|
||||
owner: owner.into_string(),
|
||||
mix_id,
|
||||
|
||||
@@ -17,6 +17,8 @@ gloo-utils = { workspace = true }
|
||||
gloo-net = { workspace = true, features = ["websocket"], optional = true }
|
||||
#gloo-net = { path = "../../../../gloo/crates/net", features = ["websocket"], optional = true }
|
||||
|
||||
console_error_panic_hook = { workspace = true, optional = true }
|
||||
|
||||
# we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums
|
||||
[dependencies.tungstenite]
|
||||
workspace = true
|
||||
@@ -28,7 +30,7 @@ workspace = true
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["sleep"]
|
||||
default = ["sleep", "console_error_panic_hook"]
|
||||
sleep = ["web-sys", "web-sys/Window"]
|
||||
websocket = [
|
||||
"getrandom",
|
||||
|
||||
@@ -27,12 +27,15 @@ pub type HmacSha256 = Hmac<Sha256>;
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub enum ClientMessage {
|
||||
Initial(InitMessage),
|
||||
Final(GatewayClient),
|
||||
Query(PeerPublicKey),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -60,6 +63,19 @@ pub struct RegistrationData {
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: u64,
|
||||
pub suspended: bool,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
||||
@@ -12,6 +12,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
defguard_wireguard_rs = { workspace = true }
|
||||
# The latest version on crates.io at the time of writing this (6.0.0) has a
|
||||
@@ -24,5 +25,6 @@ nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-wireguard-types = { path = "../wireguard-types" }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tokio-stream = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("peers in wireguard don't match with in-memory ")]
|
||||
PeerMismatch,
|
||||
|
||||
#[error("{0}")]
|
||||
Defguard(#[from] defguard_wireguard_rs::error::WireguardInterfaceError),
|
||||
}
|
||||
+25
-14
@@ -1,3 +1,6 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
// #![warn(clippy::pedantic)]
|
||||
// #![warn(clippy::expect_used)]
|
||||
@@ -6,13 +9,14 @@
|
||||
use dashmap::DashMap;
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi};
|
||||
use nym_crypto::asymmetric::encryption::KeyPair;
|
||||
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry};
|
||||
use peer_controller::PeerControlMessage;
|
||||
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry, PeerPublicKey};
|
||||
use peer_controller::PeerControlRequest;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver};
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||
|
||||
const WG_TUN_NAME: &str = "nymwg";
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod peer_controller;
|
||||
|
||||
pub struct WgApiWrapper {
|
||||
@@ -39,14 +43,14 @@ pub struct WireguardGatewayData {
|
||||
config: Config,
|
||||
keypair: Arc<KeyPair>,
|
||||
client_registry: Arc<GatewayClientRegistry>,
|
||||
peer_tx: mpsc::UnboundedSender<PeerControlMessage>,
|
||||
peer_tx: UnboundedSender<PeerControlRequest>,
|
||||
}
|
||||
|
||||
impl WireguardGatewayData {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
keypair: Arc<KeyPair>,
|
||||
) -> (Self, mpsc::UnboundedReceiver<PeerControlMessage>) {
|
||||
) -> (Self, UnboundedReceiver<PeerControlRequest>) {
|
||||
let (peer_tx, peer_rx) = mpsc::unbounded_channel();
|
||||
(
|
||||
WireguardGatewayData {
|
||||
@@ -75,20 +79,26 @@ impl WireguardGatewayData {
|
||||
let mut peer = Peer::new(Key::new(client.pub_key.to_bytes()));
|
||||
peer.allowed_ips
|
||||
.push(IpAddrMask::new(client.private_ip, 32));
|
||||
let msg = PeerControlMessage::AddPeer(peer);
|
||||
let msg = PeerControlRequest::AddPeer(peer);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
|
||||
pub fn remove_peer(&self, client: &GatewayClient) -> Result<(), Error> {
|
||||
let key = Key::new(client.pub_key().to_bytes());
|
||||
let msg = PeerControlMessage::RemovePeer(key);
|
||||
let msg = PeerControlRequest::RemovePeer(key);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
|
||||
pub fn query_bandwidth(&self, peer_public_key: PeerPublicKey) -> Result<(), Error> {
|
||||
let key = Key::new(peer_public_key.to_bytes());
|
||||
let msg = PeerControlRequest::QueryBandwidth(key);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WireguardData {
|
||||
pub inner: WireguardGatewayData,
|
||||
pub peer_rx: UnboundedReceiver<PeerControlMessage>,
|
||||
pub peer_rx: UnboundedReceiver<PeerControlRequest>,
|
||||
}
|
||||
|
||||
/// Start wireguard device
|
||||
@@ -96,6 +106,7 @@ pub struct WireguardData {
|
||||
pub async fn start_wireguard(
|
||||
task_client: nym_task::TaskClient,
|
||||
wireguard_data: WireguardData,
|
||||
control_tx: UnboundedSender<peer_controller::PeerControlResponse>,
|
||||
) -> Result<std::sync::Arc<WgApiWrapper>, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||
use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi};
|
||||
@@ -135,13 +146,13 @@ pub async fn start_wireguard(
|
||||
wg_api.configure_peer_routing(&[catch_all_peer])?;
|
||||
|
||||
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
|
||||
let mut controller = PeerController::new(wg_api.clone(), wireguard_data.peer_rx);
|
||||
let mut controller = PeerController::new(
|
||||
wg_api.clone(),
|
||||
interface_config.peers,
|
||||
wireguard_data.peer_rx,
|
||||
control_tx,
|
||||
);
|
||||
tokio::spawn(async move { controller.run(task_client).await });
|
||||
|
||||
Ok(wg_api)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub async fn start_wireguard() {
|
||||
todo!("WireGuard is currently only supported on Linux");
|
||||
}
|
||||
|
||||
@@ -1,91 +1,182 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use defguard_wireguard_rs::{
|
||||
host::{Host, Peer},
|
||||
key::Key,
|
||||
WGApi, WireguardInterfaceApi,
|
||||
};
|
||||
use chrono::{Timelike, Utc};
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, WireguardInterfaceApi};
|
||||
use nym_wireguard_types::registration::{RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY};
|
||||
use std::time::SystemTime;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::WgApiWrapper;
|
||||
|
||||
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60); // 1 hour
|
||||
// To avoid any problems, keep this stale check time bigger (>2x) then the bandwidth cap
|
||||
// reset time (currently that one is 24h, at UTC midnight)
|
||||
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60 * 24 * 3); // 3 days
|
||||
const DEFAULT_PEER_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute
|
||||
|
||||
pub enum PeerControlMessage {
|
||||
pub enum PeerControlRequest {
|
||||
AddPeer(Peer),
|
||||
RemovePeer(Key),
|
||||
QueryBandwidth(Key),
|
||||
}
|
||||
|
||||
pub enum PeerControlResponse {
|
||||
AddPeer {
|
||||
success: bool,
|
||||
},
|
||||
RemovePeer {
|
||||
success: bool,
|
||||
},
|
||||
QueryBandwidth {
|
||||
bandwidth_data: Option<RemainingBandwidthData>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct PeerController {
|
||||
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
|
||||
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
|
||||
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
active_peers: HashMap<Key, Peer>,
|
||||
suspended_peers: HashMap<Key, Peer>,
|
||||
last_seen_bandwidth: HashMap<Key, u64>,
|
||||
}
|
||||
|
||||
impl PeerController {
|
||||
pub fn new(
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
|
||||
peers: Vec<Peer>,
|
||||
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
|
||||
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
|
||||
) -> Self {
|
||||
let timeout_check_interval = tokio_stream::wrappers::IntervalStream::new(
|
||||
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
|
||||
);
|
||||
let active_peers = peers
|
||||
.into_iter()
|
||||
.map(|peer| (peer.public_key.clone(), peer))
|
||||
.collect();
|
||||
|
||||
PeerController {
|
||||
wg_api,
|
||||
peer_rx,
|
||||
request_rx,
|
||||
response_tx,
|
||||
timeout_check_interval,
|
||||
active_peers,
|
||||
suspended_peers: HashMap::new(),
|
||||
last_seen_bandwidth: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_stale_peers(wg_api: &WGApi, host: Host) {
|
||||
let current_timestamp = SystemTime::now();
|
||||
for (key, peer) in host.peers.iter() {
|
||||
if let Some(timestamp) = peer.last_handshake {
|
||||
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
|
||||
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
|
||||
if let Err(e) = wg_api.remove_peer(key) {
|
||||
log::error!("Could not remove stale peer: {:?}", e);
|
||||
} else {
|
||||
log::debug!("Removed stale peer {:?}", key);
|
||||
}
|
||||
}
|
||||
fn check_stale_peer(&self, peer: &Peer, current_timestamp: SystemTime) -> Result<bool, Error> {
|
||||
if let Some(timestamp) = peer.last_handshake {
|
||||
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
|
||||
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
|
||||
self.wg_api.inner.remove_peer(&peer.public_key)?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn check_suspend_peer(&mut self, peer: &Peer) -> Result<(), Error> {
|
||||
let prev_peer = self
|
||||
.active_peers
|
||||
.get(&peer.public_key)
|
||||
.ok_or(Error::PeerMismatch)?;
|
||||
let data_usage =
|
||||
(peer.rx_bytes + peer.tx_bytes).saturating_sub(prev_peer.rx_bytes + prev_peer.tx_bytes);
|
||||
if data_usage > BANDWIDTH_CAP_PER_DAY {
|
||||
self.wg_api.inner.remove_peer(&peer.public_key)?;
|
||||
let (moved_key, moved_peer) = self
|
||||
.active_peers
|
||||
.remove_entry(&peer.public_key)
|
||||
.ok_or(Error::PeerMismatch)?;
|
||||
self.suspended_peers.insert(moved_key, moved_peer);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_peers(&mut self) -> Result<(), Error> {
|
||||
// Add 10 seconds to cover edge cases. At worst, we give ten free seconds worth of bandwidth
|
||||
// by resetting the bandwidth twice
|
||||
let reset = Utc::now().num_seconds_from_midnight() as u64
|
||||
<= DEFAULT_PEER_TIMEOUT_CHECK.as_secs() + 10;
|
||||
|
||||
if reset {
|
||||
for (_, peer) in self.suspended_peers.drain() {
|
||||
self.wg_api.inner.configure_peer(&peer)?;
|
||||
}
|
||||
}
|
||||
let host = self.wg_api.inner.read_interface_data()?;
|
||||
self.last_seen_bandwidth = host
|
||||
.peers
|
||||
.iter()
|
||||
.map(|(key, peer)| (key.clone(), peer.rx_bytes + peer.tx_bytes))
|
||||
.collect();
|
||||
if reset {
|
||||
self.active_peers = host.peers;
|
||||
} else {
|
||||
let current_timestamp = SystemTime::now();
|
||||
for peer in host.peers.values() {
|
||||
if !self.check_stale_peer(peer, current_timestamp)? {
|
||||
self.check_suspend_peer(peer)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self, mut task_client: nym_task::TaskClient) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.timeout_check_interval.next() => {
|
||||
match self.wg_api.inner.read_interface_data() {
|
||||
Ok(host) => Self::remove_stale_peers(&self.wg_api.inner, host),
|
||||
Err(e) => { log::error!("Could not read peer data: {:?}", e); },
|
||||
if let Err(e) = self.check_peers() {
|
||||
log::error!("Error while periodically checking peers: {:?}", e);
|
||||
}
|
||||
}
|
||||
_ = task_client.recv() => {
|
||||
log::trace!("PeerController handler: Received shutdown");
|
||||
break;
|
||||
}
|
||||
msg = self.peer_rx.recv() => {
|
||||
msg = self.request_rx.recv() => {
|
||||
match msg {
|
||||
Some(PeerControlMessage::AddPeer(peer)) => {
|
||||
if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
|
||||
Some(PeerControlRequest::AddPeer(peer)) => {
|
||||
let success = if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
|
||||
log::error!("Could not configure peer: {:?}", e);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
self.active_peers.insert(peer.public_key.clone(), peer);
|
||||
true
|
||||
};
|
||||
self.response_tx.send(PeerControlResponse::AddPeer { success }).ok();
|
||||
}
|
||||
Some(PeerControlMessage::RemovePeer(peer_pubkey)) => {
|
||||
if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
|
||||
Some(PeerControlRequest::RemovePeer(peer_pubkey)) => {
|
||||
let success = if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
|
||||
log::error!("Could not remove peer: {:?}", e);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
self.active_peers.remove(&peer_pubkey);
|
||||
self.suspended_peers.remove(&peer_pubkey);
|
||||
true
|
||||
};
|
||||
self.response_tx.send(PeerControlResponse::RemovePeer { success }).ok();
|
||||
}
|
||||
Some(PeerControlRequest::QueryBandwidth(peer_pubkey)) => {
|
||||
let msg = if self.suspended_peers.contains_key(&peer_pubkey) {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: 0, suspended: true }) }
|
||||
} else if let Some(&consumed_bandwidth) = self.last_seen_bandwidth.get(&peer_pubkey) {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: BANDWIDTH_CAP_PER_DAY - consumed_bandwidth, suspended: false })}
|
||||
} else {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: None }
|
||||
};
|
||||
self.response_tx.send(msg).ok();
|
||||
}
|
||||
None => {
|
||||
log::trace!("PeerController [main loop]: stopping since channel closed");
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::support::helpers::{mix_coin, mix_coins, vesting_owner};
|
||||
use crate::support::setup::{TestSetup, MIX_DENOM};
|
||||
use cosmwasm_std::Addr;
|
||||
use cw_multi_test::Executor;
|
||||
use nym_contracts_common::Percent;
|
||||
use nym_mixnet_contract_common::error::MixnetContractError;
|
||||
use nym_mixnet_contract_common::{ContractStateParams, MixNodeCostParams};
|
||||
use nym_mixnet_contract_common::{MixOwnershipResponse, QueryMsg as MixnetQueryMsg};
|
||||
use nym_vesting_contract_common::{ExecuteMsg as VestingExecuteMsg, VestingContractError};
|
||||
|
||||
#[test]
|
||||
fn decrease_mixnode_pledge_from_vesting_account_with_minimum_pledge() {
|
||||
let mut test = TestSetup::new_simple();
|
||||
let vesting_account = "vesting-account";
|
||||
|
||||
// 0. get the minimum pledge amount
|
||||
let state_params: ContractStateParams = test
|
||||
.app
|
||||
.wrap()
|
||||
.query_wasm_smart(test.mixnet_contract(), &MixnetQueryMsg::GetStateParams {})
|
||||
.unwrap();
|
||||
let minimum_pledge = state_params.minimum_mixnode_pledge;
|
||||
|
||||
// 1. create vesting account
|
||||
let create_msg = VestingExecuteMsg::CreateAccount {
|
||||
owner_address: vesting_account.to_string(),
|
||||
staking_address: None,
|
||||
vesting_spec: None,
|
||||
cap: None,
|
||||
};
|
||||
|
||||
test.app
|
||||
.execute_contract(
|
||||
vesting_owner(),
|
||||
test.vesting_contract(),
|
||||
&create_msg,
|
||||
&mix_coins(1_000_000_000),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 2. bond mixnode with the vesting account
|
||||
let pledge = minimum_pledge.clone();
|
||||
|
||||
let cost_params = MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: mix_coin(40_000_000),
|
||||
};
|
||||
|
||||
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
|
||||
vesting_account,
|
||||
Some(test.vesting_contract()),
|
||||
cost_params.clone(),
|
||||
pledge.clone(),
|
||||
);
|
||||
|
||||
let bond_msg = VestingExecuteMsg::BondMixnode {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature,
|
||||
amount: pledge.clone(),
|
||||
};
|
||||
test.app
|
||||
.execute_contract(
|
||||
Addr::unchecked(vesting_account),
|
||||
test.vesting_contract(),
|
||||
&bond_msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 3. try to decrease the pledge
|
||||
|
||||
// trying to decrease by a zero amount - not valid
|
||||
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
|
||||
amount: mix_coin(0),
|
||||
};
|
||||
let res_zero = test
|
||||
.app
|
||||
.execute_contract(
|
||||
Addr::unchecked(vesting_account),
|
||||
test.vesting_contract(),
|
||||
&decrease_pledge_msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
VestingContractError::EmptyFunds,
|
||||
res_zero.downcast().unwrap()
|
||||
);
|
||||
|
||||
// trying to go below the cap - also not valid
|
||||
let amount = mix_coin(50_000);
|
||||
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
|
||||
amount: amount.clone(),
|
||||
};
|
||||
let res_below = test
|
||||
.app
|
||||
.execute_contract(
|
||||
Addr::unchecked(vesting_account),
|
||||
test.vesting_contract(),
|
||||
&decrease_pledge_msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
MixnetContractError::InvalidPledgeReduction {
|
||||
current: pledge.amount,
|
||||
decrease_by: amount.amount,
|
||||
minimum: minimum_pledge.amount,
|
||||
denom: minimum_pledge.denom
|
||||
},
|
||||
res_below.downcast().unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decrease_mixnode_pledge_from_vesting_account_with_sufficient_pledge() {
|
||||
let mut test = TestSetup::new_simple();
|
||||
let vesting_account = "vesting-account";
|
||||
|
||||
// 1. create vesting account
|
||||
let create_msg = VestingExecuteMsg::CreateAccount {
|
||||
owner_address: vesting_account.to_string(),
|
||||
staking_address: None,
|
||||
vesting_spec: None,
|
||||
cap: None,
|
||||
};
|
||||
|
||||
test.app
|
||||
.execute_contract(
|
||||
vesting_owner(),
|
||||
test.vesting_contract(),
|
||||
&create_msg,
|
||||
&mix_coins(10_000_000_000),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 2. bond mixnode with the vesting account
|
||||
let pledge = mix_coin(150_000_000);
|
||||
|
||||
let cost_params = MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
|
||||
interval_operating_cost: mix_coin(40_000_000),
|
||||
};
|
||||
|
||||
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
|
||||
vesting_account,
|
||||
Some(test.vesting_contract()),
|
||||
cost_params.clone(),
|
||||
pledge.clone(),
|
||||
);
|
||||
|
||||
let bond_msg = VestingExecuteMsg::BondMixnode {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature,
|
||||
amount: pledge,
|
||||
};
|
||||
test.app
|
||||
.execute_contract(
|
||||
Addr::unchecked(vesting_account),
|
||||
test.vesting_contract(),
|
||||
&bond_msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 3. try to decrease the pledge
|
||||
let before: MixOwnershipResponse = test
|
||||
.app
|
||||
.wrap()
|
||||
.query_wasm_smart(
|
||||
test.mixnet_contract(),
|
||||
&MixnetQueryMsg::GetOwnedMixnode {
|
||||
address: vesting_account.to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let balance_before = test
|
||||
.app
|
||||
.wrap()
|
||||
.query_balance(test.vesting_contract(), MIX_DENOM)
|
||||
.unwrap();
|
||||
assert_eq!(balance_before.amount.u128(), 9_850_000_000);
|
||||
|
||||
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
|
||||
amount: mix_coin(50_000_000),
|
||||
};
|
||||
test.app
|
||||
.execute_contract(
|
||||
Addr::unchecked(vesting_account),
|
||||
test.vesting_contract(),
|
||||
&decrease_pledge_msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let after_decrease: MixOwnershipResponse = test
|
||||
.app
|
||||
.wrap()
|
||||
.query_wasm_smart(
|
||||
test.mixnet_contract(),
|
||||
&MixnetQueryMsg::GetOwnedMixnode {
|
||||
address: vesting_account.to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// note: nothing has changed with the pledge because the event hasn't been resolved yet!
|
||||
assert_eq!(before.address, after_decrease.address);
|
||||
let before_details = before.mixnode_details.unwrap();
|
||||
let after_details = after_decrease.mixnode_details.unwrap();
|
||||
assert_eq!(
|
||||
before_details.rewarding_details,
|
||||
after_details.rewarding_details
|
||||
);
|
||||
assert_eq!(
|
||||
before_details.bond_information,
|
||||
after_details.bond_information
|
||||
);
|
||||
|
||||
// but we have the pending change saved now!
|
||||
assert!(before_details.pending_changes.pledge_change.is_none());
|
||||
assert_eq!(Some(1), after_details.pending_changes.pledge_change);
|
||||
|
||||
// 4. resolve events
|
||||
test.advance_mixnet_epoch();
|
||||
|
||||
let balance_after = test
|
||||
.app
|
||||
.wrap()
|
||||
.query_balance(test.vesting_contract(), MIX_DENOM)
|
||||
.unwrap();
|
||||
assert_eq!(balance_after.amount.u128(), 9_900_000_000);
|
||||
}
|
||||
@@ -24,5 +24,7 @@ pub fn default_mixnet_init_msg() -> nym_mixnet_contract_common::InstantiateMsg {
|
||||
rewarded_set_size: 240,
|
||||
active_set_size: 100,
|
||||
},
|
||||
profit_margin: Default::default(),
|
||||
interval_operating_cost: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,27 +12,33 @@ pub fn mixnet_owner() -> Addr {
|
||||
Addr::unchecked(MIXNET_OWNER)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn vesting_owner() -> Addr {
|
||||
Addr::unchecked(VESTING_OWNER)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn rewarding_validator() -> Addr {
|
||||
Addr::unchecked(REWARDING_VALIDATOR)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn mix_coins(amount: u128) -> Vec<Coin> {
|
||||
coins(amount, MIX_DENOM)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn mix_coin(amount: u128) -> Coin {
|
||||
coin(amount, MIX_DENOM)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn test_rng() -> ChaCha20Rng {
|
||||
let dummy_seed = [42u8; 32];
|
||||
ChaCha20Rng::from_seed(dummy_seed)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn mixnet_contract_wrapper() -> Box<dyn Contract<Empty>> {
|
||||
Box::new(
|
||||
ContractWrapper::new(
|
||||
|
||||
@@ -26,6 +26,7 @@ pub const VESTING_OWNER: &str = "vesting-owner";
|
||||
pub const REWARDING_VALIDATOR: &str = "rewarding-validator";
|
||||
pub const MIX_DENOM: &str = "unym";
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct ContractInstantiationResult {
|
||||
mixnet_contract_address: Addr,
|
||||
vesting_contract_address: Addr,
|
||||
@@ -69,14 +70,15 @@ impl TestSetupBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct TestSetup {
|
||||
pub app: App,
|
||||
pub rng: ChaCha20Rng,
|
||||
|
||||
pub mixnet_contract: Addr,
|
||||
pub vesting_contract: Addr,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl TestSetup {
|
||||
pub fn new_simple() -> Self {
|
||||
TestSetup::new(Default::default(), fixtures::default_mixnet_init_msg())
|
||||
@@ -91,7 +93,6 @@ impl TestSetup {
|
||||
app,
|
||||
rng: test_rng(),
|
||||
mixnet_contract: contracts.mixnet_contract_address,
|
||||
vesting_contract: contracts.vesting_contract_address,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +100,6 @@ impl TestSetup {
|
||||
self.mixnet_contract.clone()
|
||||
}
|
||||
|
||||
pub fn vesting_contract(&self) -> Addr {
|
||||
self.vesting_contract.clone()
|
||||
}
|
||||
|
||||
pub fn skip_to_current_epoch_end(&mut self) {
|
||||
let current_interval: CurrentIntervalResponse = self
|
||||
.app
|
||||
@@ -209,7 +206,6 @@ impl TestSetup {
|
||||
pub fn valid_mixnode_with_sig(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
cost_params: MixNodeCostParams,
|
||||
stake: Coin,
|
||||
) -> (MixNode, MessageSignature) {
|
||||
@@ -239,8 +235,7 @@ impl TestSetup {
|
||||
};
|
||||
|
||||
let payload = MixnodeBondingPayload::new(mixnode.clone(), cost_params);
|
||||
let content =
|
||||
ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), vec![stake], payload);
|
||||
let sign_payload = SignableMixNodeBondingMsg::new(signing_nonce, content);
|
||||
let plaintext = sign_payload.to_plaintext().unwrap();
|
||||
let signature = keypair.private_key().sign(plaintext);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod decrease_mixnode_pledge;
|
||||
mod support;
|
||||
|
||||
@@ -26,6 +26,28 @@
|
||||
"initial_rewarding_params": {
|
||||
"$ref": "#/definitions/InitialRewardingParams"
|
||||
},
|
||||
"interval_operating_cost": {
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rewarding_denom": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -112,6 +134,42 @@
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1146,6 +1204,42 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"migrate_vested_mix_node"
|
||||
],
|
||||
"properties": {
|
||||
"migrate_vested_mix_node": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"migrate_vested_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"migrate_vested_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
@@ -1172,6 +1266,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -1198,6 +1304,18 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -1532,6 +1650,38 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
@@ -8063,6 +8213,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -8089,6 +8251,62 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -8109,6 +8327,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -8135,6 +8365,18 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -8154,6 +8396,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
|
||||
@@ -1029,6 +1029,42 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"migrate_vested_mix_node"
|
||||
],
|
||||
"properties": {
|
||||
"migrate_vested_mix_node": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"migrate_vested_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"migrate_vested_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
@@ -1055,6 +1091,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -1081,6 +1129,18 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -1415,6 +1475,38 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
|
||||
@@ -22,6 +22,28 @@
|
||||
"initial_rewarding_params": {
|
||||
"$ref": "#/definitions/InitialRewardingParams"
|
||||
},
|
||||
"interval_operating_cost": {
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rewarding_denom": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -108,6 +130,42 @@
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -103,6 +115,62 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -8,6 +8,18 @@
|
||||
"minimum_mixnode_pledge"
|
||||
],
|
||||
"properties": {
|
||||
"interval_operating_cost": {
|
||||
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
|
||||
"default": {
|
||||
"maximum": "1000000000000000",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Uint128"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minimum_gateway_pledge": {
|
||||
"description": "Minimum amount a gateway must pledge to get into the system.",
|
||||
"allOf": [
|
||||
@@ -34,6 +46,18 @@
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profit_margin": {
|
||||
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
|
||||
"default": {
|
||||
"maximum": "1",
|
||||
"minimum": "0"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RangedValue_for_Percent"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -53,6 +77,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Percent": {
|
||||
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RangedValue_for_Percent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Percent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RangedValue_for_Uint128": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"maximum",
|
||||
"minimum"
|
||||
],
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
|
||||
@@ -11,7 +11,8 @@ use cosmwasm_std::{
|
||||
};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{
|
||||
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg,
|
||||
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg,
|
||||
OperatingCostRange, ProfitMarginRange, QueryMsg,
|
||||
};
|
||||
use nym_contracts_common::set_build_information;
|
||||
|
||||
@@ -24,6 +25,8 @@ fn default_initial_state(
|
||||
rewarding_validator_address: Addr,
|
||||
rewarding_denom: String,
|
||||
vesting_contract_address: Addr,
|
||||
profit_margin: ProfitMarginRange,
|
||||
interval_operating_cost: OperatingCostRange,
|
||||
) -> ContractState {
|
||||
ContractState {
|
||||
owner,
|
||||
@@ -40,6 +43,8 @@ fn default_initial_state(
|
||||
denom: rewarding_denom,
|
||||
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
|
||||
},
|
||||
profit_margin,
|
||||
interval_operating_cost,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -71,6 +76,8 @@ pub fn instantiate(
|
||||
rewarding_validator_address.clone(),
|
||||
msg.rewarding_denom,
|
||||
vesting_contract_address,
|
||||
msg.profit_margin,
|
||||
msg.interval_operating_cost,
|
||||
);
|
||||
let starting_interval =
|
||||
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
|
||||
@@ -118,44 +125,6 @@ pub fn execute(
|
||||
ExecuteMsg::KickFamilyMember { member } => {
|
||||
crate::families::transactions::try_head_kick_member(deps, info, member)
|
||||
}
|
||||
ExecuteMsg::CreateFamilyOnBehalf {
|
||||
owner_address,
|
||||
label,
|
||||
} => crate::families::transactions::try_create_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
owner_address,
|
||||
label,
|
||||
),
|
||||
ExecuteMsg::JoinFamilyOnBehalf {
|
||||
member_address,
|
||||
join_permit,
|
||||
family_head,
|
||||
} => crate::families::transactions::try_join_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
member_address,
|
||||
join_permit,
|
||||
family_head,
|
||||
),
|
||||
ExecuteMsg::LeaveFamilyOnBehalf {
|
||||
member_address,
|
||||
family_head,
|
||||
} => crate::families::transactions::try_leave_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
member_address,
|
||||
family_head,
|
||||
),
|
||||
ExecuteMsg::KickFamilyMemberOnBehalf {
|
||||
head_address,
|
||||
member,
|
||||
} => crate::families::transactions::try_head_kick_member_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
head_address,
|
||||
member,
|
||||
),
|
||||
// state/sys-params-related
|
||||
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
|
||||
crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address(
|
||||
@@ -232,62 +201,23 @@ pub fn execute(
|
||||
cost_params,
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::BondMixnodeOnBehalf {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner,
|
||||
owner_signature,
|
||||
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner,
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::PledgeMore {} => {
|
||||
crate::mixnodes::transactions::try_increase_pledge(deps, env, info)
|
||||
}
|
||||
ExecuteMsg::PledgeMoreOnBehalf { owner } => {
|
||||
crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, owner)
|
||||
}
|
||||
ExecuteMsg::DecreasePledge { decrease_by } => {
|
||||
crate::mixnodes::transactions::try_decrease_pledge(deps, env, info, decrease_by)
|
||||
}
|
||||
ExecuteMsg::DecreasePledgeOnBehalf { owner, decrease_by } => {
|
||||
crate::mixnodes::transactions::try_decrease_pledge_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
decrease_by,
|
||||
owner,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UnbondMixnode {} => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
|
||||
}
|
||||
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, env, info, owner)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
|
||||
crate::mixnodes::transactions::try_update_mixnode_cost_params(
|
||||
deps, env, info, new_costs,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner } => {
|
||||
crate::mixnodes::transactions::try_update_mixnode_cost_params_on_behalf(
|
||||
deps, env, info, new_costs, owner,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
|
||||
crate::mixnodes::transactions::try_update_mixnode_config(deps, info, new_config)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfigOnBehalf { new_config, owner } => {
|
||||
crate::mixnodes::transactions::try_update_mixnode_config_on_behalf(
|
||||
deps, info, new_config, owner,
|
||||
)
|
||||
}
|
||||
|
||||
// gateway-related:
|
||||
ExecuteMsg::BondGateway {
|
||||
@@ -300,52 +230,22 @@ pub fn execute(
|
||||
gateway,
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::BondGatewayOnBehalf {
|
||||
gateway,
|
||||
owner,
|
||||
owner_signature,
|
||||
} => crate::gateways::transactions::try_add_gateway_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
gateway,
|
||||
owner,
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::UnbondGateway {} => {
|
||||
crate::gateways::transactions::try_remove_gateway(deps, info)
|
||||
}
|
||||
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
|
||||
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
|
||||
}
|
||||
ExecuteMsg::UpdateGatewayConfig { new_config } => {
|
||||
crate::gateways::transactions::try_update_gateway_config(deps, info, new_config)
|
||||
}
|
||||
ExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner } => {
|
||||
crate::gateways::transactions::try_update_gateway_config_on_behalf(
|
||||
deps, info, new_config, owner,
|
||||
)
|
||||
}
|
||||
|
||||
// delegation-related:
|
||||
ExecuteMsg::DelegateToMixnode { mix_id } => {
|
||||
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_id)
|
||||
}
|
||||
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate } => {
|
||||
crate::delegations::transactions::try_delegate_to_mixnode_on_behalf(
|
||||
deps, env, info, mix_id, delegate,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
|
||||
crate::delegations::transactions::try_remove_delegation_from_mixnode(
|
||||
deps, env, info, mix_id,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, delegate } => {
|
||||
crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
|
||||
deps, env, info, mix_id, delegate,
|
||||
)
|
||||
}
|
||||
|
||||
// reward-related
|
||||
ExecuteMsg::RewardMixnode {
|
||||
@@ -356,16 +256,37 @@ pub fn execute(
|
||||
ExecuteMsg::WithdrawOperatorReward {} => {
|
||||
crate::rewards::transactions::try_withdraw_operator_reward(deps, info)
|
||||
}
|
||||
ExecuteMsg::WithdrawOperatorRewardOnBehalf { owner } => {
|
||||
crate::rewards::transactions::try_withdraw_operator_reward_on_behalf(deps, info, owner)
|
||||
}
|
||||
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
|
||||
crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id)
|
||||
}
|
||||
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => {
|
||||
crate::rewards::transactions::try_withdraw_delegator_reward_on_behalf(
|
||||
deps, info, mix_id, owner,
|
||||
)
|
||||
|
||||
// vesting migration:
|
||||
ExecuteMsg::MigrateVestedMixNode { .. } => {
|
||||
crate::vesting_migration::try_migrate_vested_mixnode(deps, info)
|
||||
}
|
||||
ExecuteMsg::MigrateVestedDelegation { mix_id } => {
|
||||
crate::vesting_migration::try_migrate_vested_delegation(deps, info, mix_id)
|
||||
}
|
||||
|
||||
// legacy vesting
|
||||
ExecuteMsg::CreateFamilyOnBehalf { .. }
|
||||
| ExecuteMsg::JoinFamilyOnBehalf { .. }
|
||||
| ExecuteMsg::LeaveFamilyOnBehalf { .. }
|
||||
| ExecuteMsg::KickFamilyMemberOnBehalf { .. }
|
||||
| ExecuteMsg::BondMixnodeOnBehalf { .. }
|
||||
| ExecuteMsg::PledgeMoreOnBehalf { .. }
|
||||
| ExecuteMsg::DecreasePledgeOnBehalf { .. }
|
||||
| ExecuteMsg::UnbondMixnodeOnBehalf { .. }
|
||||
| ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. }
|
||||
| ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. }
|
||||
| ExecuteMsg::BondGatewayOnBehalf { .. }
|
||||
| ExecuteMsg::UnbondGatewayOnBehalf { .. }
|
||||
| ExecuteMsg::UpdateGatewayConfigOnBehalf { .. }
|
||||
| ExecuteMsg::DelegateToMixnodeOnBehalf { .. }
|
||||
| ExecuteMsg::UndelegateFromMixnodeOnBehalf { .. }
|
||||
| ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. }
|
||||
| ExecuteMsg::WithdrawDelegatorRewardOnBehalf { .. } => {
|
||||
Err(MixnetContractError::DisabledVestingOperation)
|
||||
}
|
||||
|
||||
// testing-only
|
||||
@@ -607,13 +528,15 @@ pub fn query(
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(
|
||||
deps: DepsMut<'_>,
|
||||
mut deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
msg: MigrateMsg,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
set_build_information!(deps.storage)?;
|
||||
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
|
||||
crate::queued_migrations::vesting_purge(deps.branch())?;
|
||||
|
||||
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
|
||||
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
|
||||
// environment, one of the contracts will HAVE TO go through a migration
|
||||
@@ -631,7 +554,7 @@ pub fn migrate(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::Decimal;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
use mixnet_contract_common::reward_params::{IntervalRewardParams, RewardingParams};
|
||||
use mixnet_contract_common::{InitialRewardingParams, Percent};
|
||||
use std::time::Duration;
|
||||
@@ -657,6 +580,14 @@ mod tests {
|
||||
rewarded_set_size: 543,
|
||||
active_set_size: 123,
|
||||
},
|
||||
profit_margin: ProfitMarginRange {
|
||||
minimum: "0.05".parse().unwrap(),
|
||||
maximum: "0.95".parse().unwrap(),
|
||||
},
|
||||
interval_operating_cost: OperatingCostRange {
|
||||
minimum: "1000".parse().unwrap(),
|
||||
maximum: "10000".parse().unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
let sender = mock_info("sender", &[]);
|
||||
@@ -678,6 +609,14 @@ mod tests {
|
||||
denom: "uatom".into(),
|
||||
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
|
||||
},
|
||||
profit_margin: ProfitMarginRange {
|
||||
minimum: Percent::from_percentage_value(5).unwrap(),
|
||||
maximum: Percent::from_percentage_value(95).unwrap(),
|
||||
},
|
||||
interval_operating_cost: OperatingCostRange {
|
||||
minimum: Uint128::new(1000),
|
||||
maximum: Uint128::new(10000),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -302,10 +302,7 @@ mod tests {
|
||||
|
||||
mod delegator_delegations {
|
||||
use super::*;
|
||||
use crate::delegations::transactions::try_delegate_to_mixnode_on_behalf;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{coin, Addr};
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
#[test]
|
||||
fn obeys_limits() {
|
||||
@@ -453,25 +450,10 @@ mod tests {
|
||||
#[test]
|
||||
fn all_retrieved_delegations_are_from_the_specified_delegator() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
// it means we have, for example, delegation from "delegator1" towards mix1, mix2, ...., from "delegator2" towards mix1, mix2, ...., etc
|
||||
add_dummy_mixes_with_delegations(&mut test, 50, 100);
|
||||
|
||||
// add some proxies while we're at it to make sure they're queried for separately
|
||||
let with_proxy = "delegator42";
|
||||
let vesting_contract = test.vesting_contract();
|
||||
for mix_id in 1..=25 {
|
||||
try_delegate_to_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(vesting_contract.as_ref(), &[coin(100_000, TEST_COIN_DENOM)]),
|
||||
mix_id,
|
||||
with_proxy.into(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
test.execute_all_pending_events();
|
||||
|
||||
// make few queries
|
||||
let res1 =
|
||||
query_delegator_delegations_paged(test.deps(), "delegator2".into(), None, None)
|
||||
@@ -490,59 +472,6 @@ mod tests {
|
||||
.delegations
|
||||
.into_iter()
|
||||
.all(|d| d.owner == Addr::unchecked("delegator35")));
|
||||
|
||||
let with_proxy_full =
|
||||
query_delegator_delegations_paged(test.deps(), with_proxy.into(), None, None)
|
||||
.unwrap();
|
||||
assert_eq!(with_proxy_full.delegations.len(), 125);
|
||||
|
||||
// all delegations have correct owner
|
||||
assert!(with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.all(|d| d.owner == Addr::unchecked(with_proxy)));
|
||||
|
||||
// and we have 100 delegations without proxy and 25 with
|
||||
let no_proxy = with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_none())
|
||||
.count();
|
||||
assert_eq!(no_proxy, 100);
|
||||
let proxy = with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_some())
|
||||
.count();
|
||||
assert_eq!(proxy, 25);
|
||||
|
||||
assert!(with_proxy_full
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_some())
|
||||
.all(|d| d.proxy.as_ref().unwrap() == vesting_contract));
|
||||
|
||||
// now make sure that if we do it in paged manner, we'll get exactly the same result
|
||||
let per_page = Some(15);
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = query_delegator_delegations_paged(
|
||||
test.deps(),
|
||||
with_proxy.into(),
|
||||
start_after,
|
||||
per_page,
|
||||
)
|
||||
.unwrap();
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(with_proxy_full.delegations, delegations)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::support::helpers::{
|
||||
ensure_epoch_in_progress_state, ensure_sent_by_vesting_contract, validate_delegation_stake,
|
||||
};
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut, Env, MessageInfo, Response};
|
||||
use crate::support::helpers::{ensure_epoch_in_progress_state, validate_delegation_stake};
|
||||
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_pending_delegation_event, new_pending_undelegation_event,
|
||||
@@ -21,30 +19,6 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_delegate_to_mixnode(deps, env, mix_id, info.sender, info.funds, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_delegate_to_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
delegate: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let delegate = deps.api.addr_validate(&delegate)?;
|
||||
_try_delegate_to_mixnode(deps, env, mix_id, delegate, info.funds, Some(info.sender))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_delegate_to_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
mix_id: MixId,
|
||||
delegate: Addr,
|
||||
amount: Vec<Coin>,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// delegation is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
@@ -52,7 +26,7 @@ pub(crate) fn _try_delegate_to_mixnode(
|
||||
// check if the delegation contains any funds of the appropriate denomination
|
||||
let contract_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
|
||||
let delegation = validate_delegation_stake(
|
||||
amount,
|
||||
info.funds,
|
||||
contract_state.params.minimum_mixnode_delegation,
|
||||
contract_state.rewarding_denom,
|
||||
)?;
|
||||
@@ -67,14 +41,9 @@ pub(crate) fn _try_delegate_to_mixnode(
|
||||
}
|
||||
|
||||
// push the event onto the queue and wait for it to be picked up at the end of the epoch
|
||||
let cosmos_event = new_pending_delegation_event(&delegate, &proxy, &delegation, mix_id);
|
||||
let cosmos_event = new_pending_delegation_event(&info.sender, &delegation, mix_id);
|
||||
|
||||
let epoch_event = PendingEpochEventKind::Delegate {
|
||||
owner: delegate,
|
||||
mix_id,
|
||||
amount: delegation,
|
||||
proxy,
|
||||
};
|
||||
let epoch_event = PendingEpochEventKind::new_delegate(info.sender, mix_id, delegation);
|
||||
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
|
||||
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
@@ -85,35 +54,12 @@ pub(crate) fn try_remove_delegation_from_mixnode(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_remove_delegation_from_mixnode(deps, env, mix_id, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
delegate: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let delegate = deps.api.addr_validate(&delegate)?;
|
||||
_try_remove_delegation_from_mixnode(deps, env, mix_id, delegate, Some(info.sender))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_delegation_from_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
mix_id: MixId,
|
||||
delegate: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// undelegation is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
// see if the delegation even exists
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &delegate, proxy.as_ref());
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
|
||||
|
||||
if storage::delegations()
|
||||
.may_load(deps.storage, storage_key)?
|
||||
@@ -121,19 +67,15 @@ pub(crate) fn _try_remove_delegation_from_mixnode(
|
||||
{
|
||||
return Err(MixnetContractError::NoMixnodeDelegationFound {
|
||||
mix_id,
|
||||
address: delegate.into_string(),
|
||||
proxy: proxy.map(Addr::into_string),
|
||||
address: info.sender.into_string(),
|
||||
proxy: None,
|
||||
});
|
||||
}
|
||||
|
||||
// push the event onto the queue and wait for it to be picked up at the end of the epoch
|
||||
let cosmos_event = new_pending_undelegation_event(&delegate, &proxy, mix_id);
|
||||
let cosmos_event = new_pending_undelegation_event(&info.sender, mix_id);
|
||||
|
||||
let epoch_event = PendingEpochEventKind::Undelegate {
|
||||
owner: delegate,
|
||||
mix_id,
|
||||
proxy,
|
||||
};
|
||||
let epoch_event = PendingEpochEventKind::new_undelegate(info.sender, mix_id);
|
||||
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
|
||||
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
@@ -151,7 +93,7 @@ mod tests {
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{coin, Decimal};
|
||||
use cosmwasm_std::{coin, Addr, Decimal};
|
||||
use mixnet_contract_common::{EpochState, EpochStatus};
|
||||
|
||||
#[test]
|
||||
@@ -368,65 +310,17 @@ mod tests {
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
|
||||
let amount2 = coin(50_000_000, TEST_COIN_DENOM);
|
||||
|
||||
let sender1 = mock_info(owner, &[amount1.clone()]);
|
||||
let sender2 = mock_info(test.vesting_contract().as_str(), &[amount2.clone()]);
|
||||
|
||||
try_delegate_to_mixnode(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
|
||||
try_delegate_to_mixnode_on_behalf(test.deps_mut(), env, sender2, mix_id, owner.into())
|
||||
.unwrap();
|
||||
|
||||
let events = test.pending_epoch_events();
|
||||
|
||||
assert_eq!(
|
||||
events[0].kind,
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked(owner),
|
||||
mix_id,
|
||||
amount: amount1,
|
||||
proxy: None
|
||||
}
|
||||
PendingEpochEventKind::new_delegate(Addr::unchecked(owner), mix_id, amount1,)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
events[1].kind,
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked(owner),
|
||||
mix_id,
|
||||
amount: amount2,
|
||||
proxy: Some(test.vesting_contract())
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "delegator";
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let res = try_delegate_to_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
mix_id,
|
||||
owner.into(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,40 +467,5 @@ mod tests {
|
||||
);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "delegator";
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
test.add_immediate_delegation_with_illegal_proxy(
|
||||
owner,
|
||||
10000u32,
|
||||
mix_id,
|
||||
illegal_proxy.clone(),
|
||||
);
|
||||
|
||||
let res = try_remove_delegation_from_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
mix_id,
|
||||
owner.into(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::decode_ed25519_identity_key;
|
||||
use cosmwasm_std::{Addr, Deps};
|
||||
use cosmwasm_std::Deps;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::{construct_family_join_permit, IdentityKeyRef};
|
||||
@@ -13,7 +13,6 @@ use nym_contracts_common::signing::{MessageSignature, Verifier};
|
||||
pub(crate) fn verify_family_join_permit(
|
||||
deps: Deps<'_>,
|
||||
granter: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
member: IdentityKeyRef,
|
||||
signature: MessageSignature,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
@@ -32,7 +31,7 @@ pub(crate) fn verify_family_join_permit(
|
||||
});
|
||||
};
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, head_mixnode.owner)?;
|
||||
let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned());
|
||||
let msg = construct_family_join_permit(nonce, granter, member.to_owned());
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage::{
|
||||
@@ -7,41 +7,20 @@ use super::storage::{
|
||||
};
|
||||
use crate::families::queries::get_family_by_label;
|
||||
use crate::families::signature_helpers::verify_family_join_permit;
|
||||
use crate::support::helpers::{ensure_bonded, ensure_sent_by_vesting_contract};
|
||||
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
|
||||
use crate::support::helpers::ensure_bonded;
|
||||
use cosmwasm_std::{DepsMut, MessageInfo, Response};
|
||||
use mixnet_contract_common::families::{Family, FamilyHead};
|
||||
use mixnet_contract_common::{error::MixnetContractError, IdentityKey};
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
|
||||
/// Creates a new MixNode family with senders node as head
|
||||
pub fn try_create_family(
|
||||
pub(crate) fn try_create_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
label: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_create_family(deps, &info.sender, label, None)
|
||||
}
|
||||
|
||||
pub fn try_create_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
owner_address: String,
|
||||
label: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner_address = deps.api.addr_validate(&owner_address)?;
|
||||
_try_create_family(deps, &owner_address, label, Some(info.sender))
|
||||
}
|
||||
|
||||
fn _try_create_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
label: String,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -60,43 +39,19 @@ fn _try_create_family(
|
||||
return Err(MixnetContractError::FamilyWithLabelExists(label));
|
||||
}
|
||||
|
||||
let family = Family::new(family_head, proxy, label);
|
||||
let family = Family::new(family_head, label);
|
||||
save_family(&family, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_join_family(
|
||||
pub(crate) fn try_join_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_join_family(deps, &info.sender, join_permit, family_head, None)
|
||||
}
|
||||
|
||||
pub fn try_join_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member_address: String,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let member_address = deps.api.addr_validate(&member_address)?;
|
||||
let proxy = Some(info.sender);
|
||||
_try_join_family(deps, &member_address, join_permit, family_head, proxy)
|
||||
}
|
||||
|
||||
fn _try_join_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -116,7 +71,6 @@ fn _try_join_family(
|
||||
verify_family_join_permit(
|
||||
deps.as_ref(),
|
||||
family_head.clone(),
|
||||
proxy,
|
||||
existing_bond.identity(),
|
||||
join_permit,
|
||||
)?;
|
||||
@@ -128,33 +82,13 @@ fn _try_join_family(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_leave_family(
|
||||
pub(crate) fn try_leave_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_leave_family(deps, &info.sender, family_head)
|
||||
}
|
||||
|
||||
pub fn try_leave_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member_address: String,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let member_address = deps.api.addr_validate(&member_address)?;
|
||||
_try_leave_family(deps, &member_address, family_head)
|
||||
}
|
||||
|
||||
fn _try_leave_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -178,32 +112,13 @@ fn _try_leave_family(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_head_kick_member(
|
||||
pub(crate) fn try_head_kick_member(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_head_kick_member(deps, &info.sender, member)
|
||||
}
|
||||
|
||||
pub fn try_head_kick_member_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
head_address: String,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let head_address = deps.api.addr_validate(&head_address)?;
|
||||
_try_head_kick_member(deps, &head_address, member)
|
||||
}
|
||||
|
||||
fn _try_head_kick_member(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let head_bond = crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
let head_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
// make sure we're still in the mixnet
|
||||
ensure_bonded(&head_bond)?;
|
||||
@@ -321,7 +236,7 @@ mod test {
|
||||
assert_eq!(family.head_identity(), family_head.identity());
|
||||
|
||||
let join_permit =
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
@@ -345,7 +260,7 @@ mod test {
|
||||
);
|
||||
|
||||
let new_join_permit =
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
@@ -373,189 +288,4 @@ mod test {
|
||||
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod creating_family {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let head = "alice";
|
||||
|
||||
test.add_dummy_mixnode(head, None);
|
||||
|
||||
let res = try_create_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
head.to_string(),
|
||||
"label".to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod joining_family {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let head = "alice";
|
||||
let label = "family";
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
let res = try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
join_permit,
|
||||
family_head,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod leaving_family {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let head = "alice";
|
||||
let label = "family";
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
true,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
join_permit,
|
||||
family_head.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = try_leave_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
family_head,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod kicking_family_member {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let head = "alice";
|
||||
let label = "family";
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
true,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
|
||||
try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
join_permit,
|
||||
family_head,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = try_head_kick_member_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
head.to_string(),
|
||||
member_keys.public_key().to_base58_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use nym_contracts_common::signing::Verifier;
|
||||
pub(crate) fn verify_gateway_bonding_signature(
|
||||
deps: Deps<'_>,
|
||||
sender: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
gateway: Gateway,
|
||||
signature: MessageSignature,
|
||||
@@ -22,7 +21,7 @@ pub(crate) fn verify_gateway_bonding_signature(
|
||||
|
||||
// reconstruct the payload
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
|
||||
let msg = construct_gateway_bonding_sign_payload(nonce, sender, proxy, pledge, gateway);
|
||||
let msg = construct_gateway_bonding_sign_payload(nonce, sender, pledge, gateway);
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::helpers::must_get_gateway_bond_by_owner;
|
||||
@@ -6,10 +6,8 @@ use super::storage;
|
||||
use crate::gateways::signature_helpers::verify_gateway_bonding_signature;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::{
|
||||
ensure_no_existing_bond, ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
|
||||
};
|
||||
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
|
||||
use crate::support::helpers::{ensure_no_existing_bond, validate_pledge};
|
||||
use cosmwasm_std::{BankMsg, DepsMut, Env, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_gateway_bonding_event, new_gateway_config_update_event, new_gateway_unbonding_event,
|
||||
@@ -17,72 +15,28 @@ use mixnet_contract_common::events::{
|
||||
use mixnet_contract_common::gateway::GatewayConfigUpdate;
|
||||
use mixnet_contract_common::{Gateway, GatewayBond};
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
pub fn try_add_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_add_gateway(
|
||||
deps,
|
||||
env,
|
||||
gateway,
|
||||
info.funds,
|
||||
info.sender,
|
||||
owner_signature,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_add_gateway_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
owner: String,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_add_gateway(
|
||||
deps,
|
||||
env,
|
||||
gateway,
|
||||
info.funds,
|
||||
owner,
|
||||
owner_signature,
|
||||
Some(proxy),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
|
||||
// so that we could return a better error message if it doesn't match?
|
||||
pub(crate) fn _try_add_gateway(
|
||||
pub(crate) fn try_add_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
pledge: Vec<Coin>,
|
||||
owner: Addr,
|
||||
owner_signature: MessageSignature,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// check if the pledge contains any funds of the appropriate denomination
|
||||
let minimum_pledge = mixnet_params_storage::minimum_gateway_pledge(deps.storage)?;
|
||||
let pledge = validate_pledge(pledge, minimum_pledge)?;
|
||||
let pledge = validate_pledge(info.funds, minimum_pledge)?;
|
||||
|
||||
// if the client has an active bonded mixnode or gateway, don't allow bonding
|
||||
ensure_no_existing_bond(&owner, deps.storage)?;
|
||||
ensure_no_existing_bond(&info.sender, deps.storage)?;
|
||||
|
||||
// check if somebody else has already bonded a gateway with this identity
|
||||
if let Some(existing_bond) =
|
||||
storage::gateways().may_load(deps.storage, &gateway.identity_key)?
|
||||
{
|
||||
if existing_bond.owner != owner {
|
||||
if existing_bond.owner != info.sender {
|
||||
return Err(MixnetContractError::DuplicateGateway {
|
||||
owner: existing_bond.owner,
|
||||
});
|
||||
@@ -92,105 +46,62 @@ pub(crate) fn _try_add_gateway(
|
||||
// check if this sender actually owns the gateway by checking the signature
|
||||
verify_gateway_bonding_signature(
|
||||
deps.as_ref(),
|
||||
owner.clone(),
|
||||
proxy.clone(),
|
||||
info.sender.clone(),
|
||||
pledge.clone(),
|
||||
gateway.clone(),
|
||||
owner_signature,
|
||||
)?;
|
||||
|
||||
// update the signing nonce associated with this sender so that the future signature would be made on the new value
|
||||
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
|
||||
signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?;
|
||||
|
||||
let gateway_identity = gateway.identity_key.clone();
|
||||
let bond = GatewayBond::new(
|
||||
pledge.clone(),
|
||||
owner.clone(),
|
||||
info.sender.clone(),
|
||||
env.block.height,
|
||||
gateway,
|
||||
proxy.clone(),
|
||||
);
|
||||
|
||||
storage::gateways().save(deps.storage, bond.identity(), &bond)?;
|
||||
|
||||
Ok(Response::new().add_event(new_gateway_bonding_event(
|
||||
&owner,
|
||||
&proxy,
|
||||
&info.sender,
|
||||
&pledge,
|
||||
&gateway_identity,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn try_remove_gateway_on_behalf(
|
||||
pub(crate) fn try_remove_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_remove_gateway(deps, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn try_remove_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_remove_gateway(deps, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_gateway(
|
||||
deps: DepsMut<'_>,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// try to find the node of the sender
|
||||
let gateway_bond = match storage::gateways()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, owner.clone())?
|
||||
.item(deps.storage, info.sender.clone())?
|
||||
{
|
||||
Some(record) => record.1,
|
||||
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner }),
|
||||
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner: info.sender }),
|
||||
};
|
||||
|
||||
if proxy != gateway_bond.proxy {
|
||||
return Err(MixnetContractError::ProxyMismatch {
|
||||
existing: gateway_bond
|
||||
.proxy
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
// send bonded funds back to the bond owner
|
||||
let return_tokens = BankMsg::Send {
|
||||
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
|
||||
to_address: info.sender.to_string(),
|
||||
amount: vec![gateway_bond.pledge_amount()],
|
||||
};
|
||||
|
||||
// remove the bond
|
||||
storage::gateways().remove(deps.storage, gateway_bond.identity())?;
|
||||
|
||||
let mut response = Response::new().add_message(return_tokens);
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
let msg = VestingContractExecuteMsg::TrackUnbondGateway {
|
||||
owner: owner.as_str().to_string(),
|
||||
amount: gateway_bond.pledge_amount(),
|
||||
};
|
||||
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
response = response.add_message(track_unbond_message);
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_gateway_unbonding_event(
|
||||
&owner,
|
||||
&proxy,
|
||||
&gateway_bond.pledge_amount,
|
||||
gateway_bond.identity(),
|
||||
)))
|
||||
Ok(Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_gateway_unbonding_event(
|
||||
&info.sender,
|
||||
&gateway_bond.pledge_amount,
|
||||
gateway_bond.identity(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn try_update_gateway_config(
|
||||
@@ -198,36 +109,9 @@ pub(crate) fn try_update_gateway_config(
|
||||
info: MessageInfo,
|
||||
new_config: GatewayConfigUpdate,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let owner = info.sender;
|
||||
_try_update_gateway_config(deps, new_config, owner, None)
|
||||
}
|
||||
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &info.sender)?;
|
||||
let cfg_update_event = new_gateway_config_update_event(&info.sender, &new_config);
|
||||
|
||||
pub(crate) fn try_update_gateway_config_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
new_config: GatewayConfigUpdate,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
let proxy = info.sender;
|
||||
_try_update_gateway_config(deps, new_config, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_update_gateway_config(
|
||||
deps: DepsMut,
|
||||
new_config: GatewayConfigUpdate,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &owner)?;
|
||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||
|
||||
let cfg_update_event = new_gateway_config_update_event(&owner, &proxy, &new_config);
|
||||
|
||||
// clippy beta 1.70.0-beta.1 false positive
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let mut updated_bond = existing_bond.clone();
|
||||
updated_bond.gateway.host = new_config.host;
|
||||
updated_bond.gateway.mix_port = new_config.mix_port;
|
||||
@@ -254,10 +138,10 @@ pub mod tests {
|
||||
use crate::mixnet_contract_settings::storage::minimum_gateway_pledge;
|
||||
use crate::support::tests;
|
||||
use crate::support::tests::fixtures;
|
||||
use crate::support::tests::fixtures::{good_gateway_pledge, good_mixnode_pledge};
|
||||
use crate::support::tests::fixtures::good_mixnode_pledge;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::Uint128;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use mixnet_contract_common::ExecuteMsg;
|
||||
|
||||
#[test]
|
||||
@@ -392,42 +276,12 @@ pub mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(1, updated_nonce);
|
||||
|
||||
_try_remove_gateway(test.deps_mut(), Addr::unchecked(sender), None).unwrap();
|
||||
try_remove_gateway(test.deps_mut(), info.clone()).unwrap();
|
||||
|
||||
let res = try_add_gateway(test.deps_mut(), env, info, gateway, signature);
|
||||
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_add_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
let (gateway, sig) = test.gateway_with_signature(owner, None);
|
||||
|
||||
let res = try_add_gateway_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()),
|
||||
gateway,
|
||||
owner.to_string(),
|
||||
sig,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_remove() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -495,7 +349,6 @@ pub mod tests {
|
||||
.add_message(expected_message)
|
||||
.add_event(new_gateway_unbonding_event(
|
||||
&Addr::unchecked("fred"),
|
||||
&None,
|
||||
&tests::fixtures::good_gateway_pledge()[0],
|
||||
&fred_identity,
|
||||
));
|
||||
@@ -510,33 +363,6 @@ pub mod tests {
|
||||
assert_eq!(&Addr::unchecked("bob"), nodes[0].owner());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_remove_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
|
||||
let res = try_remove_gateway_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()),
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_gateway_config() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -561,22 +387,6 @@ pub mod tests {
|
||||
);
|
||||
|
||||
test.add_dummy_gateway(owner, None);
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// attempted to remove on behalf with invalid proxy (current is `None`)
|
||||
let res = try_update_gateway_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
update.clone(),
|
||||
owner.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: vesting_contract.into_string()
|
||||
})
|
||||
);
|
||||
|
||||
// "normal" update succeeds
|
||||
let res = try_update_gateway_config(test.deps_mut(), info, update.clone());
|
||||
@@ -591,39 +401,4 @@ pub mod tests {
|
||||
assert_eq!(bond.gateway.location, update.location);
|
||||
assert_eq!(bond.gateway.version, update.version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_gateway_config_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
let update = GatewayConfigUpdate {
|
||||
host: "1.1.1.1:1234".to_string(),
|
||||
mix_port: 1234,
|
||||
clients_port: 1235,
|
||||
location: "at home".to_string(),
|
||||
version: "v1.2.3".to_string(),
|
||||
};
|
||||
|
||||
let res = try_update_gateway_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
update,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::interval::storage;
|
||||
use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_details_by_id};
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::rewards::storage as rewards_storage;
|
||||
use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking};
|
||||
use crate::support::helpers::AttachSendTokens;
|
||||
|
||||
pub(crate) trait ContractExecutableEvent {
|
||||
// note: the error only means a HARD error like we failed to read from storage.
|
||||
@@ -40,7 +40,6 @@ pub(crate) fn delegate(
|
||||
owner: Addr,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// check if the target node still exists (it might have unbonded between this event getting created
|
||||
// and being executed). Do note that it's absolutely possible for a mixnode to get immediately
|
||||
@@ -56,20 +55,9 @@ pub(crate) fn delegate(
|
||||
_ => {
|
||||
// if mixnode is no longer bonded or in the process of unbonding, return the tokens back to the
|
||||
// delegator;
|
||||
// (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`)
|
||||
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![amount.clone()]);
|
||||
let response = Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_delegation_on_unbonded_node_event(
|
||||
&owner, &proxy, mix_id,
|
||||
))
|
||||
.maybe_add_track_vesting_undelegation_message(
|
||||
deps.storage,
|
||||
proxy,
|
||||
owner.to_string(),
|
||||
mix_id,
|
||||
amount,
|
||||
)?;
|
||||
.send_tokens(&owner, amount.clone())
|
||||
.add_event(new_delegation_on_unbonded_node_event(&owner, mix_id));
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
@@ -84,7 +72,7 @@ pub(crate) fn delegate(
|
||||
|
||||
// if there's an existing delegation, then withdraw the full reward and create a new delegation
|
||||
// with the sum of both
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, None);
|
||||
let old_delegation = if let Some(existing_delegation) =
|
||||
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
|
||||
{
|
||||
@@ -106,7 +94,6 @@ pub(crate) fn delegate(
|
||||
let cosmos_event = new_delegation_event(
|
||||
created_at,
|
||||
&owner,
|
||||
&proxy,
|
||||
&new_delegation_amount,
|
||||
mix_id,
|
||||
mix_rewarding.total_unit_reward,
|
||||
@@ -118,7 +105,6 @@ pub(crate) fn delegate(
|
||||
mix_rewarding.total_unit_reward,
|
||||
stored_delegation_amount,
|
||||
env.block.height,
|
||||
proxy,
|
||||
);
|
||||
|
||||
// save on reading since `.save()` would have attempted to read old data that we already have on hand
|
||||
@@ -138,11 +124,10 @@ pub(crate) fn undelegate(
|
||||
created_at: BlockHeight,
|
||||
owner: Addr,
|
||||
mix_id: MixId,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// see if the delegation still exists (in case of impatient user who decided to send multiple
|
||||
// undelegation requests in an epoch)
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, None);
|
||||
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
|
||||
None => return Ok(Response::default()),
|
||||
Some(delegation) => delegation,
|
||||
@@ -155,18 +140,9 @@ pub(crate) fn undelegate(
|
||||
let tokens_to_return =
|
||||
delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?;
|
||||
|
||||
// (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`)
|
||||
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![tokens_to_return.clone()]);
|
||||
let response = Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_undelegation_event(created_at, &owner, &proxy, mix_id))
|
||||
.maybe_add_track_vesting_undelegation_message(
|
||||
deps.storage,
|
||||
proxy,
|
||||
owner.to_string(),
|
||||
mix_id,
|
||||
tokens_to_return,
|
||||
)?;
|
||||
.send_tokens(&owner, tokens_to_return.clone())
|
||||
.add_event(new_undelegation_event(created_at, &owner, mix_id));
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -197,25 +173,15 @@ pub(crate) fn unbond_mixnode(
|
||||
.rewarding_details
|
||||
.operator_pledge_with_reward(rewarding_denom);
|
||||
|
||||
let proxy = &node_details.bond_information.proxy;
|
||||
let owner = &node_details.bond_information.owner;
|
||||
|
||||
// send bonded funds (alongside all earned rewards) to the bond owner
|
||||
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![tokens.clone()]);
|
||||
|
||||
// remove the bond and if there are no delegations left, also the rewarding information
|
||||
// decrement the associated layer count
|
||||
cleanup_post_unbond_mixnode_storage(deps.storage, env, &node_details)?;
|
||||
|
||||
let response = Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_mixnode_unbonding_event(created_at, mix_id))
|
||||
.maybe_add_track_vesting_unbond_mixnode_message(
|
||||
deps.storage,
|
||||
proxy.clone(),
|
||||
owner.clone().into_string(),
|
||||
tokens,
|
||||
)?;
|
||||
.send_tokens(owner, tokens.clone())
|
||||
.add_event(new_mixnode_unbonding_event(created_at, mix_id));
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -311,12 +277,8 @@ pub(crate) fn decrease_pledge(
|
||||
updated_bond.original_pledge.amount -= decrease_by.amount;
|
||||
updated_rewarding.decrease_operator_uint128(decrease_by.amount)?;
|
||||
|
||||
let proxy = &mix_details.bond_information.proxy;
|
||||
let owner = &mix_details.bond_information.owner;
|
||||
|
||||
// send the removed tokens back to the operator
|
||||
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![decrease_by.clone()]);
|
||||
|
||||
// update all: bond information, rewarding details and pending pledge changes
|
||||
mixnodes_storage::mixnode_bonds().replace(
|
||||
deps.storage,
|
||||
@@ -328,14 +290,8 @@ pub(crate) fn decrease_pledge(
|
||||
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
|
||||
|
||||
let response = Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by))
|
||||
.maybe_add_track_vesting_decrease_mixnode_pledge(
|
||||
deps.storage,
|
||||
proxy.clone(),
|
||||
owner.clone().to_string(),
|
||||
decrease_by,
|
||||
)?;
|
||||
.send_tokens(owner, decrease_by.clone())
|
||||
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by));
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -349,13 +305,11 @@ impl ContractExecutableEvent for PendingEpochEventData {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
proxy,
|
||||
} => delegate(deps, env, self.created_at, owner, mix_id, amount, proxy),
|
||||
PendingEpochEventKind::Undelegate {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy,
|
||||
} => undelegate(deps, self.created_at, owner, mix_id, proxy),
|
||||
..
|
||||
} => delegate(deps, env, self.created_at, owner, mix_id, amount),
|
||||
PendingEpochEventKind::Undelegate { owner, mix_id, .. } => {
|
||||
undelegate(deps, self.created_at, owner, mix_id)
|
||||
}
|
||||
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
|
||||
increase_pledge(deps, self.created_at, mix_id, amount)
|
||||
}
|
||||
@@ -472,33 +426,25 @@ impl ContractExecutableEvent for PendingIntervalEventData {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
|
||||
use mixnet_contract_common::Percent;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
use super::*;
|
||||
use crate::support::tests::test_helpers;
|
||||
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
|
||||
|
||||
use super::*;
|
||||
use cosmwasm_std::Decimal;
|
||||
use mixnet_contract_common::Percent;
|
||||
use std::time::Duration;
|
||||
|
||||
// note that authorization and basic validation has already been performed for all of those
|
||||
// before being pushed onto the event queues
|
||||
|
||||
#[cfg(test)]
|
||||
mod delegating {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use super::*;
|
||||
use crate::mixnodes::transactions::try_remove_mixnode;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
|
||||
use super::*;
|
||||
use cosmwasm_std::coin;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
#[test]
|
||||
fn returns_the_tokens_if_mixnode_has_unbonded() {
|
||||
@@ -523,7 +469,6 @@ mod tests {
|
||||
Addr::unchecked(owner1),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -549,7 +494,6 @@ mod tests {
|
||||
Addr::unchecked(owner2),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key =
|
||||
@@ -588,7 +532,6 @@ mod tests {
|
||||
Addr::unchecked(owner1),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -614,7 +557,6 @@ mod tests {
|
||||
Addr::unchecked(owner2),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key =
|
||||
@@ -650,7 +592,6 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin_new,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -725,7 +666,6 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin_new,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -797,7 +737,6 @@ mod tests {
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(get_bank_send_msg(&res).is_none());
|
||||
@@ -816,117 +755,13 @@ mod tests {
|
||||
Decimal::from_atomics(delegation, 0).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let delegation = 120_000_000u128;
|
||||
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
|
||||
let owner = "delegator";
|
||||
|
||||
let env = test.env();
|
||||
unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// for a fresh delegation, nothing was added to the storage either
|
||||
let res_vesting = delegate(
|
||||
test.deps_mut(),
|
||||
&env,
|
||||
123,
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin.clone(),
|
||||
Some(vesting_contract.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key = Delegation::generate_storage_key(
|
||||
mix_id,
|
||||
&Addr::unchecked(owner),
|
||||
Some(vesting_contract.clone()).as_ref(),
|
||||
);
|
||||
assert!(delegations_storage::delegations()
|
||||
.may_load(test.deps().storage, storage_key)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
// and all tokens are returned back to the proxy
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap();
|
||||
assert_eq!(receiver, vesting_contract.as_str());
|
||||
assert_eq!(sent_amount[0], delegation_coin);
|
||||
|
||||
// and we get appropriate track message
|
||||
let mut found_track = true;
|
||||
for msg in &res_vesting.messages {
|
||||
if let CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr,
|
||||
msg,
|
||||
funds,
|
||||
}) = &msg.msg
|
||||
{
|
||||
found_track = true;
|
||||
assert_eq!(contract_addr, vesting_contract.as_str());
|
||||
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: owner.to_string(),
|
||||
mix_id,
|
||||
amount: delegation_coin.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(&expected_msg, msg);
|
||||
assert!(funds.is_empty())
|
||||
}
|
||||
}
|
||||
assert!(found_track);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_error_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let delegation = 120_000_000u128;
|
||||
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
|
||||
let owner = "delegator";
|
||||
let dummy_proxy = Addr::unchecked("not-vesting-contract");
|
||||
|
||||
let env = test.env();
|
||||
unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// try to add illegal delegation (with invalid proxy)
|
||||
let res_other_proxy = delegate(
|
||||
test.deps_mut(),
|
||||
&env,
|
||||
123,
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
delegation_coin,
|
||||
Some(dummy_proxy.clone()),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res_other_proxy,
|
||||
MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: dummy_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod undelegating {
|
||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
|
||||
use super::*;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
#[test]
|
||||
fn doesnt_return_any_tokens_if_it_doesnt_exist() {
|
||||
@@ -935,7 +770,7 @@ mod tests {
|
||||
|
||||
let owner = Addr::unchecked("delegator");
|
||||
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None).unwrap();
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id).unwrap();
|
||||
assert!(get_bank_send_msg(&res).is_none());
|
||||
}
|
||||
|
||||
@@ -950,7 +785,7 @@ mod tests {
|
||||
// this should never happen in actual code, but if we manually messed something up,
|
||||
// lets make sure this throws an error
|
||||
rewards_storage::MIXNODE_REWARDING.remove(test.deps_mut().storage, mix_id);
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None);
|
||||
let res = undelegate(test.deps_mut(), 123, owner, mix_id);
|
||||
assert!(matches!(
|
||||
res,
|
||||
Err(MixnetContractError::InconsistentState { .. })
|
||||
@@ -996,8 +831,7 @@ mod tests {
|
||||
|
||||
let expected_return = delegation + truncated_reward.u128();
|
||||
|
||||
let res =
|
||||
undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id, None).unwrap();
|
||||
let res = undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id).unwrap();
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
|
||||
assert_eq!(receiver, owner);
|
||||
assert_eq!(sent_amount[0].amount.u128(), expected_return);
|
||||
@@ -1015,117 +849,19 @@ mod tests {
|
||||
assert!(rewarding.delegates.is_zero());
|
||||
assert_eq!(rewarding.unique_delegations, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let delegation = 120_000_000u128;
|
||||
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
|
||||
let owner = "delegator";
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
test.add_immediate_delegation_with_legal_proxy(owner, delegation, mix_id);
|
||||
|
||||
let res_vesting = undelegate(
|
||||
test.deps_mut(),
|
||||
123,
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
Some(vesting_contract.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
let storage_key = Delegation::generate_storage_key(
|
||||
mix_id,
|
||||
&Addr::unchecked(owner),
|
||||
Some(vesting_contract.clone()).as_ref(),
|
||||
);
|
||||
assert!(delegations_storage::delegations()
|
||||
.may_load(test.deps().storage, storage_key)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
// and all tokens are returned back to the proxy
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap();
|
||||
assert_eq!(receiver, vesting_contract.as_str());
|
||||
assert_eq!(sent_amount[0], delegation_coin);
|
||||
|
||||
// and we get appropriate track message
|
||||
let mut found_track = true;
|
||||
for msg in &res_vesting.messages {
|
||||
if let CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr,
|
||||
msg,
|
||||
funds,
|
||||
}) = &msg.msg
|
||||
{
|
||||
found_track = true;
|
||||
assert_eq!(contract_addr, vesting_contract.as_str());
|
||||
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: owner.to_string(),
|
||||
mix_id,
|
||||
amount: delegation_coin.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(&expected_msg, msg);
|
||||
assert!(funds.is_empty())
|
||||
}
|
||||
}
|
||||
assert!(found_track);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_error_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let delegation = 120_000_000u128;
|
||||
let owner = "delegator1";
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
let dummy_proxy = Addr::unchecked("not-vesting-contract");
|
||||
|
||||
test.add_immediate_delegation_with_illegal_proxy(
|
||||
owner,
|
||||
delegation,
|
||||
mix_id,
|
||||
dummy_proxy.clone(),
|
||||
);
|
||||
|
||||
let res_other_proxy = undelegate(
|
||||
test.deps_mut(),
|
||||
123,
|
||||
Addr::unchecked(owner),
|
||||
mix_id,
|
||||
Some(dummy_proxy.clone()),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
res_other_proxy,
|
||||
MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: dummy_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod mixnode_unbonding {
|
||||
use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
|
||||
|
||||
use super::*;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::mixnodes::transactions::{try_decrease_pledge, try_increase_pledge};
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::Uint128;
|
||||
use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::mixnodes::transactions::{_try_decrease_pledge, _try_increase_pledge};
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::get_bank_send_msg;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
||||
// this should have never happened so hard error MUST be thrown here
|
||||
@@ -1150,12 +886,10 @@ mod tests {
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
|
||||
|
||||
_try_increase_pledge(
|
||||
try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
change.clone(),
|
||||
Addr::unchecked(owner),
|
||||
None,
|
||||
mock_info(owner, &change.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1170,12 +904,11 @@ mod tests {
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
|
||||
|
||||
_try_decrease_pledge(
|
||||
try_decrease_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(owner, &[]),
|
||||
change[0].clone(),
|
||||
Addr::unchecked(owner),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1263,79 +996,6 @@ mod tests {
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
let pledge_coin = coin(250_000_000, TEST_COIN_DENOM);
|
||||
let owner = "mix-owner1";
|
||||
let mix_id_vesting = test.add_dummy_mixnode_with_legal_proxy(owner, Some(pledge));
|
||||
|
||||
let env = test.env();
|
||||
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id_vesting).unwrap();
|
||||
|
||||
assert!(mixnodes_storage::mixnode_bonds()
|
||||
.may_load(test.deps().storage, mix_id_vesting)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
// and all tokens are returned back to the proxy
|
||||
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
|
||||
assert_eq!(receiver, vesting_contract.as_str());
|
||||
assert_eq!(sent_amount[0], pledge_coin);
|
||||
|
||||
// and we get appropriate track message
|
||||
let mut found_track = true;
|
||||
for msg in &res.messages {
|
||||
if let CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr,
|
||||
msg,
|
||||
funds,
|
||||
}) = &msg.msg
|
||||
{
|
||||
found_track = true;
|
||||
assert_eq!(contract_addr, vesting_contract.as_str());
|
||||
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUnbondMixnode {
|
||||
owner: owner.to_string(),
|
||||
amount: pledge_coin.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(&expected_msg, msg);
|
||||
assert!(funds.is_empty())
|
||||
}
|
||||
}
|
||||
assert!(found_track);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_error_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let dummy_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let env = test.env();
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
let owner = "mix-owner";
|
||||
let pledge = Uint128::new(250_000_000);
|
||||
|
||||
let mix_id_illegal_proxy =
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(pledge), dummy_proxy.clone());
|
||||
|
||||
// this is the halting issue that should have never occurred
|
||||
let res_other_proxy =
|
||||
unbond_mixnode(test.deps_mut(), &env, 123, mix_id_illegal_proxy).unwrap_err();
|
||||
assert_eq!(
|
||||
res_other_proxy,
|
||||
MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: dummy_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1615,11 +1275,9 @@ mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod decreasing_pledge {
|
||||
use cosmwasm_std::{to_binary, BankMsg, CosmosMsg, Uint128, WasmMsg};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use super::*;
|
||||
use cosmwasm_std::{BankMsg, CosmosMsg, Uint128};
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
#[test]
|
||||
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
||||
@@ -1699,64 +1357,6 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_tokens_back_to_the_proxy_if_bonded_with_vesting() {
|
||||
let mut test = TestSetup::new();
|
||||
let owner = "mix-owner";
|
||||
let mix_id = test.add_dummy_mixnode_with_legal_proxy(owner, None);
|
||||
test.set_pending_pledge_change(mix_id, None);
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let amount = test.coin(12345);
|
||||
let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
|
||||
|
||||
assert_eq!(res.messages.len(), 2);
|
||||
assert_eq!(
|
||||
res.messages[0].msg,
|
||||
CosmosMsg::Bank(BankMsg::Send {
|
||||
to_address: vesting_contract.to_string(),
|
||||
amount: vec![amount],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attaches_vesting_track_message() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id_no_proxy = test.add_dummy_mixnode("mix-owner1", None);
|
||||
test.set_pending_pledge_change(mix_id_no_proxy, None);
|
||||
|
||||
let mix_id_proxy = test.add_dummy_mixnode_with_legal_proxy("mix-owner2", None);
|
||||
test.set_pending_pledge_change(mix_id_proxy, None);
|
||||
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let amount = test.coin(12345);
|
||||
let res_no_proxy =
|
||||
decrease_pledge(test.deps_mut(), 123, mix_id_no_proxy, amount.clone()).unwrap();
|
||||
|
||||
// nothing was attached (apart from bank message tested in `returns_tokens_back_to_the_owner`)
|
||||
// because it wasn't done with proxy!
|
||||
assert_eq!(res_no_proxy.messages.len(), 1);
|
||||
|
||||
let res_proxy =
|
||||
decrease_pledge(test.deps_mut(), 123, mix_id_proxy, amount.clone()).unwrap();
|
||||
assert_eq!(res_proxy.messages.len(), 2);
|
||||
assert_eq!(
|
||||
res_proxy.messages[1].msg,
|
||||
CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: vesting_contract.to_string(),
|
||||
msg: to_binary(&VestingContractExecuteMsg::TrackDecreasePledge {
|
||||
owner: "mix-owner2".to_string(),
|
||||
amount,
|
||||
})
|
||||
.unwrap(),
|
||||
funds: vec![],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_any_events_in_between_is_equivalent_to_pledging_the_same_amount_immediately() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
@@ -159,11 +159,8 @@ mod tests {
|
||||
}
|
||||
|
||||
fn push_dummy_epoch_action(test: &mut TestSetup) {
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let dummy_action =
|
||||
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
|
||||
let env = test.env();
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
}
|
||||
@@ -571,11 +568,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// it exists
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let dummy_action =
|
||||
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
|
||||
let env = test.env();
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action.clone()).unwrap();
|
||||
let expected = PendingEpochEventResponse {
|
||||
|
||||
@@ -222,11 +222,10 @@ mod tests {
|
||||
let env = test.env();
|
||||
|
||||
for _ in 0..500 {
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let dummy_action = PendingEpochEventKind::new_undelegate(
|
||||
Addr::unchecked("foomp"),
|
||||
test.rng.next_u32(),
|
||||
);
|
||||
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
|
||||
assert_eq!(expected, id);
|
||||
@@ -235,11 +234,10 @@ mod tests {
|
||||
test.execute_all_pending_events();
|
||||
|
||||
for _ in 0..10 {
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: test.rng.next_u32(),
|
||||
proxy: None,
|
||||
};
|
||||
let dummy_action = PendingEpochEventKind::new_undelegate(
|
||||
Addr::unchecked("foomp"),
|
||||
test.rng.next_u32(),
|
||||
);
|
||||
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
|
||||
assert_eq!(expected, id);
|
||||
|
||||
@@ -373,18 +373,14 @@ mod tests {
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::Addr;
|
||||
use mixnet_contract_common::pending_events::PendingEpochEventKind;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) {
|
||||
// if you attempt to undelegate non-existent delegation,
|
||||
// it will return an empty response, but will not fail
|
||||
let env = test.env();
|
||||
for i in 0..n {
|
||||
let dummy_action = PendingEpochEventKind::Undelegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: i as MixId,
|
||||
proxy: None,
|
||||
};
|
||||
let dummy_action =
|
||||
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), i as MixId);
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -406,7 +402,7 @@ mod tests {
|
||||
mod performing_pending_epoch_actions {
|
||||
use super::*;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use cosmwasm_std::{coin, coins, wasm_execute, BankMsg, Empty, SubMsg};
|
||||
use cosmwasm_std::{coin, coins, BankMsg, Empty, SubMsg};
|
||||
use mixnet_contract_common::events::{
|
||||
new_active_set_update_event, new_delegation_on_unbonded_node_event,
|
||||
new_undelegation_event,
|
||||
@@ -495,7 +491,6 @@ mod tests {
|
||||
#[test]
|
||||
fn catches_all_events_and_messages_from_executed_actions() {
|
||||
let mut test = TestSetup::new();
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let env = test.env();
|
||||
let legit_mix = test.add_dummy_mixnode("mix-owner", None);
|
||||
@@ -509,17 +504,15 @@ mod tests {
|
||||
// delegate to node that doesn't exist,
|
||||
// we expect to receive BankMsg with tokens being returned,
|
||||
// and event regarding delegation
|
||||
let non_existent_delegation = PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
proxy: None,
|
||||
};
|
||||
let non_existent_delegation = PendingEpochEventKind::new_delegate(
|
||||
Addr::unchecked("foomp"),
|
||||
123,
|
||||
coin(123, TEST_COIN_DENOM),
|
||||
);
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
|
||||
.unwrap();
|
||||
expected_events.push(new_delegation_on_unbonded_node_event(
|
||||
&Addr::unchecked("foomp"),
|
||||
&None,
|
||||
123,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
@@ -527,33 +520,6 @@ mod tests {
|
||||
amount: coins(123, TEST_COIN_DENOM),
|
||||
}));
|
||||
|
||||
// delegation to node that doesn't exist with vesting contract
|
||||
// we expect the same as above PLUS TrackUndelegation message
|
||||
let non_existent_delegation = PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked("foomp2"),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
proxy: Some(vesting_contract.clone()),
|
||||
};
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
|
||||
.unwrap();
|
||||
expected_events.push(new_delegation_on_unbonded_node_event(
|
||||
&Addr::unchecked("foomp2"),
|
||||
&Some(vesting_contract.clone()),
|
||||
123,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
to_address: vesting_contract.clone().into_string(),
|
||||
amount: coins(123, TEST_COIN_DENOM),
|
||||
}));
|
||||
let msg = VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: "foomp2".to_string(),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
};
|
||||
let track_undelegate_message = wasm_execute(vesting_contract, &msg, vec![]).unwrap();
|
||||
expected_messages.push(SubMsg::new(track_undelegate_message));
|
||||
|
||||
// updating active set should only emit events and no cosmos messages
|
||||
let action_with_event = PendingEpochEventKind::UpdateActiveSetSize { new_size: 50 };
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, action_with_event)
|
||||
@@ -561,16 +527,12 @@ mod tests {
|
||||
expected_events.push(new_active_set_update_event(env.block.height, 50));
|
||||
|
||||
// undelegation just returns tokens and emits event
|
||||
let legit_undelegate = PendingEpochEventKind::Undelegate {
|
||||
owner: delegator.clone(),
|
||||
mix_id: legit_mix,
|
||||
proxy: None,
|
||||
};
|
||||
let legit_undelegate =
|
||||
PendingEpochEventKind::new_undelegate(delegator.clone(), legit_mix);
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, legit_undelegate).unwrap();
|
||||
expected_events.push(new_undelegation_event(
|
||||
env.block.height,
|
||||
&delegator,
|
||||
&None,
|
||||
legit_mix,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
@@ -583,9 +545,9 @@ mod tests {
|
||||
let mut expected = Response::new().add_events(expected_events);
|
||||
expected.messages = expected_messages;
|
||||
assert_eq!(res, expected);
|
||||
assert_eq!(executed, 4);
|
||||
assert_eq!(executed, 3);
|
||||
assert_eq!(
|
||||
4,
|
||||
3,
|
||||
storage::LAST_PROCESSED_EPOCH_EVENT
|
||||
.load(test.deps().storage)
|
||||
.unwrap()
|
||||
@@ -1330,17 +1292,15 @@ mod tests {
|
||||
let mut expected_messages: Vec<SubMsg<Empty>> = Vec::new();
|
||||
|
||||
// epoch event
|
||||
let non_existent_delegation = PendingEpochEventKind::Delegate {
|
||||
owner: Addr::unchecked("foomp"),
|
||||
mix_id: 123,
|
||||
amount: coin(123, TEST_COIN_DENOM),
|
||||
proxy: None,
|
||||
};
|
||||
let non_existent_delegation = PendingEpochEventKind::new_delegate(
|
||||
Addr::unchecked("foomp"),
|
||||
123,
|
||||
coin(123, TEST_COIN_DENOM),
|
||||
);
|
||||
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
|
||||
.unwrap();
|
||||
expected_events.push(new_delegation_on_unbonded_node_event(
|
||||
&Addr::unchecked("foomp"),
|
||||
&None,
|
||||
123,
|
||||
));
|
||||
expected_messages.push(SubMsg::new(BankMsg::Send {
|
||||
|
||||
@@ -19,3 +19,4 @@ mod support;
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
mod testing;
|
||||
mod vesting_migration;
|
||||
|
||||
@@ -45,6 +45,8 @@ pub(crate) mod tests {
|
||||
minimum_mixnode_delegation: None,
|
||||
minimum_mixnode_pledge: coin(123u128, "unym"),
|
||||
minimum_gateway_pledge: coin(456u128, "unym"),
|
||||
profit_margin: Default::default(),
|
||||
interval_operating_cost: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use cosmwasm_std::{Addr, Storage};
|
||||
use cosmwasm_std::{Coin, StdResult};
|
||||
use cw_storage_plus::Item;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::ContractState;
|
||||
use mixnet_contract_common::{ContractState, OperatingCostRange, ProfitMarginRange};
|
||||
|
||||
pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new(CONTRACT_STATE_KEY);
|
||||
|
||||
@@ -28,6 +28,22 @@ pub(crate) fn minimum_gateway_pledge(storage: &dyn Storage) -> Result<Coin, Mixn
|
||||
.map(|state| state.params.minimum_gateway_pledge)?)
|
||||
}
|
||||
|
||||
pub(crate) fn profit_margin_range(
|
||||
storage: &dyn Storage,
|
||||
) -> Result<ProfitMarginRange, MixnetContractError> {
|
||||
Ok(CONTRACT_STATE
|
||||
.load(storage)
|
||||
.map(|state| state.params.profit_margin)?)
|
||||
}
|
||||
|
||||
pub(crate) fn interval_oprating_cost_range(
|
||||
storage: &dyn Storage,
|
||||
) -> Result<OperatingCostRange, MixnetContractError> {
|
||||
Ok(CONTRACT_STATE
|
||||
.load(storage)
|
||||
.map(|state| state.params.interval_operating_cost)?)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn minimum_delegation_stake(
|
||||
storage: &dyn Storage,
|
||||
|
||||
@@ -121,6 +121,8 @@ pub mod tests {
|
||||
denom,
|
||||
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT + Uint128::new(1234),
|
||||
},
|
||||
profit_margin: Default::default(),
|
||||
interval_operating_cost: Default::default(),
|
||||
};
|
||||
|
||||
let initial_params = storage::CONTRACT_STATE
|
||||
|
||||
@@ -97,7 +97,6 @@ pub(crate) fn save_new_mixnode(
|
||||
mixnode: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
) -> Result<(MixId, Layer), MixnetContractError> {
|
||||
let layer = assign_layer(storage)?;
|
||||
@@ -105,15 +104,7 @@ pub(crate) fn save_new_mixnode(
|
||||
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
|
||||
|
||||
let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?;
|
||||
let mixnode_bond = MixNodeBond::new(
|
||||
mix_id,
|
||||
owner,
|
||||
pledge,
|
||||
layer,
|
||||
mixnode,
|
||||
proxy,
|
||||
env.block.height,
|
||||
);
|
||||
let mixnode_bond = MixNodeBond::new(mix_id, owner, pledge, layer, mixnode, env.block.height);
|
||||
|
||||
// save mixnode bond data
|
||||
// note that this implicitly checks for uniqueness on identity key, sphinx key and owner
|
||||
@@ -411,7 +402,6 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
owner.clone(),
|
||||
None,
|
||||
pledge.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -444,7 +434,6 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
Addr::unchecked("different-owner"),
|
||||
None,
|
||||
pledge.clone(),
|
||||
);
|
||||
assert!(res.is_err());
|
||||
@@ -457,7 +446,6 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params.clone(),
|
||||
owner,
|
||||
None,
|
||||
pledge.clone(),
|
||||
);
|
||||
assert!(res.is_err());
|
||||
@@ -471,7 +459,6 @@ pub(crate) mod tests {
|
||||
mixnode,
|
||||
cost_params,
|
||||
Addr::unchecked("different-owner"),
|
||||
None,
|
||||
pledge,
|
||||
);
|
||||
assert!(res.is_err());
|
||||
|
||||
@@ -12,7 +12,6 @@ use nym_contracts_common::signing::Verifier;
|
||||
pub(crate) fn verify_mixnode_bonding_signature(
|
||||
deps: Deps<'_>,
|
||||
sender: Addr,
|
||||
proxy: Option<Addr>,
|
||||
pledge: Coin,
|
||||
mixnode: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
@@ -23,8 +22,7 @@ pub(crate) fn verify_mixnode_bonding_signature(
|
||||
|
||||
// reconstruct the payload
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
|
||||
let msg =
|
||||
construct_mixnode_bonding_sign_payload(nonce, sender, proxy, pledge, mixnode, cost_params);
|
||||
let msg = construct_mixnode_bonding_sign_payload(nonce, sender, pledge, mixnode, cost_params);
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
|
||||
|
||||
use cosmwasm_std::{coin, Coin, DepsMut, Env, MessageInfo, Response, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_mixnode_bonding_event, new_mixnode_config_update_event,
|
||||
@@ -25,8 +24,8 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::{
|
||||
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
|
||||
ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
|
||||
validate_pledge,
|
||||
ensure_no_pending_pledge_changes, ensure_operating_cost_within_range,
|
||||
ensure_profit_margin_within_range, validate_pledge,
|
||||
};
|
||||
|
||||
use super::storage;
|
||||
@@ -61,74 +60,30 @@ pub fn assign_mixnode_layer(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_add_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_node: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_add_mixnode(
|
||||
deps,
|
||||
env,
|
||||
mix_node,
|
||||
cost_params,
|
||||
info.funds,
|
||||
info.sender,
|
||||
owner_signature,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_add_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_node: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
owner: String,
|
||||
owner_signature: MessageSignature,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_add_mixnode(
|
||||
deps,
|
||||
env,
|
||||
mix_node,
|
||||
cost_params,
|
||||
info.funds,
|
||||
owner,
|
||||
owner_signature,
|
||||
Some(proxy),
|
||||
)
|
||||
}
|
||||
|
||||
// I'm not entirely sure how to deal with this warning at the current moment
|
||||
//
|
||||
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
|
||||
// so that we could return a better error message if it doesn't match?
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn _try_add_mixnode(
|
||||
pub(crate) fn try_add_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mixnode: MixNode,
|
||||
cost_params: MixNodeCostParams,
|
||||
pledge: Vec<Coin>,
|
||||
owner: Addr,
|
||||
owner_signature: MessageSignature,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// ensure the profit margin is within the defined range
|
||||
ensure_profit_margin_within_range(deps.storage, cost_params.profit_margin_percent)?;
|
||||
|
||||
// ensure the operating cost is within the defined range
|
||||
ensure_operating_cost_within_range(deps.storage, &cost_params.interval_operating_cost)?;
|
||||
|
||||
// check if the pledge contains any funds of the appropriate denomination
|
||||
let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
|
||||
let pledge = validate_pledge(pledge, minimum_pledge)?;
|
||||
let pledge = validate_pledge(info.funds, minimum_pledge)?;
|
||||
|
||||
// if the client has an active bonded mixnode or gateway, don't allow bonding
|
||||
// note that this has to be done explicitly as `UniqueIndex` constraint would not protect us
|
||||
// against attempting to use different node types (i.e. gateways and mixnodes)
|
||||
ensure_no_existing_bond(&owner, deps.storage)?;
|
||||
ensure_no_existing_bond(&info.sender, deps.storage)?;
|
||||
|
||||
// there's no need to explicitly check whether there already exists mixnode with the same
|
||||
// identity or sphinx keys as this is going to be done implicitly when attempting to save
|
||||
@@ -137,8 +92,7 @@ fn _try_add_mixnode(
|
||||
// check if this sender actually owns the mixnode by checking the signature
|
||||
verify_mixnode_bonding_signature(
|
||||
deps.as_ref(),
|
||||
owner.clone(),
|
||||
proxy.clone(),
|
||||
info.sender.clone(),
|
||||
pledge.clone(),
|
||||
mixnode.clone(),
|
||||
cost_params.clone(),
|
||||
@@ -146,7 +100,7 @@ fn _try_add_mixnode(
|
||||
)?;
|
||||
|
||||
// update the signing nonce associated with this sender so that the future signature would be made on the new value
|
||||
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
|
||||
signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?;
|
||||
|
||||
let node_identity = mixnode.identity_key.clone();
|
||||
let (node_id, layer) = save_new_mixnode(
|
||||
@@ -154,14 +108,12 @@ fn _try_add_mixnode(
|
||||
env,
|
||||
mixnode,
|
||||
cost_params,
|
||||
owner.clone(),
|
||||
proxy.clone(),
|
||||
info.sender.clone(),
|
||||
pledge.clone(),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(new_mixnode_bonding_event(
|
||||
&owner,
|
||||
&proxy,
|
||||
&info.sender,
|
||||
&pledge,
|
||||
&node_identity,
|
||||
node_id,
|
||||
@@ -174,43 +126,19 @@ pub fn try_increase_pledge(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_increase_pledge(deps, env, info.funds, info.sender, None)
|
||||
}
|
||||
|
||||
pub fn try_increase_pledge_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_increase_pledge(deps, env, info.funds, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn _try_increase_pledge(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
increase: Vec<Coin>,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
|
||||
let mut pending_changes = mix_details.pending_changes;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
// increasing pledge is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
|
||||
ensure_bonded(&mix_details.bond_information)?;
|
||||
ensure_no_pending_pledge_changes(&pending_changes)?;
|
||||
|
||||
let rewarding_denom = rewarding_denom(deps.storage)?;
|
||||
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
|
||||
let pledge_increase = validate_pledge(info.funds, coin(1, rewarding_denom))?;
|
||||
|
||||
let cosmos_event = new_pending_pledge_increase_event(mix_id, &pledge_increase);
|
||||
|
||||
@@ -232,39 +160,14 @@ pub fn try_decrease_pledge(
|
||||
info: MessageInfo,
|
||||
decrease_by: Coin,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_decrease_pledge(deps, env, decrease_by, info.sender, None)
|
||||
}
|
||||
|
||||
pub fn try_decrease_pledge_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
decrease_by: Coin,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_decrease_pledge(deps, env, decrease_by, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn _try_decrease_pledge(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
decrease_by: Coin,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
|
||||
let mut pending_changes = mix_details.pending_changes;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
// decreasing pledge is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
|
||||
ensure_bonded(&mix_details.bond_information)?;
|
||||
ensure_no_pending_pledge_changes(&pending_changes)?;
|
||||
|
||||
@@ -312,34 +215,12 @@ pub fn _try_decrease_pledge(
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_remove_mixnode(deps, env, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode(
|
||||
pub(crate) fn try_remove_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_remove_mixnode(deps, env, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
let pending_changes = storage::PENDING_MIXNODE_CHANGES
|
||||
.may_load(deps.storage, existing_bond.mix_id)?
|
||||
.unwrap_or_default();
|
||||
@@ -348,15 +229,12 @@ pub(crate) fn _try_remove_mixnode(
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
// see if the proxy matches
|
||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
// if there are any pending requests to change the pledge, wait for them to resolve before allowing the unbonding
|
||||
ensure_no_pending_pledge_changes(&pending_changes)?;
|
||||
|
||||
// set `is_unbonding` field
|
||||
// clippy beta 1.70.0-beta.1 false positive
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let mut updated_bond = existing_bond.clone();
|
||||
updated_bond.is_unbonding = true;
|
||||
storage::mixnode_bonds().replace(
|
||||
@@ -375,7 +253,6 @@ pub(crate) fn _try_remove_mixnode(
|
||||
Ok(
|
||||
Response::new().add_event(new_pending_mixnode_unbonding_event(
|
||||
&existing_bond.owner,
|
||||
&existing_bond.proxy,
|
||||
existing_bond.identity(),
|
||||
existing_bond.mix_id,
|
||||
)),
|
||||
@@ -387,39 +264,13 @@ pub(crate) fn try_update_mixnode_config(
|
||||
info: MessageInfo,
|
||||
new_config: MixNodeConfigUpdate,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let owner = info.sender;
|
||||
_try_update_mixnode_config(deps, new_config, owner, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_update_mixnode_config_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
new_config: MixNodeConfigUpdate,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
let proxy = info.sender;
|
||||
_try_update_mixnode_config(deps, new_config, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_update_mixnode_config(
|
||||
deps: DepsMut<'_>,
|
||||
new_config: MixNodeConfigUpdate,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||
|
||||
let cfg_update_event =
|
||||
new_mixnode_config_update_event(existing_bond.mix_id, &owner, &proxy, &new_config);
|
||||
new_mixnode_config_update_event(existing_bond.mix_id, &info.sender, &new_config);
|
||||
|
||||
// clippy beta 1.70.0-beta.1 false positive
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let mut updated_bond = existing_bond.clone();
|
||||
updated_bond.mix_node.host = new_config.host;
|
||||
updated_bond.mix_node.mix_port = new_config.mix_port;
|
||||
@@ -442,45 +293,24 @@ pub(crate) fn try_update_mixnode_cost_params(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
new_costs: MixNodeCostParams,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let owner = info.sender;
|
||||
_try_update_mixnode_cost_params(deps, env, new_costs, owner, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_update_mixnode_cost_params_on_behalf(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
new_costs: MixNodeCostParams,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
let proxy = info.sender;
|
||||
_try_update_mixnode_cost_params(deps, env, new_costs, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_update_mixnode_cost_params(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
new_costs: MixNodeCostParams,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// see if the node still exists
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
|
||||
|
||||
// changing cost params is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
// ensure the profit margin is within the defined range
|
||||
ensure_profit_margin_within_range(deps.storage, new_costs.profit_margin_percent)?;
|
||||
|
||||
// ensure the operating cost is within the defined range
|
||||
ensure_operating_cost_within_range(deps.storage, &new_costs.interval_operating_cost)?;
|
||||
|
||||
let cosmos_event = new_mixnode_pending_cost_params_update_event(
|
||||
existing_bond.mix_id,
|
||||
&owner,
|
||||
&proxy,
|
||||
&info.sender,
|
||||
&new_costs,
|
||||
);
|
||||
|
||||
@@ -497,7 +327,7 @@ pub(crate) fn _try_update_mixnode_cost_params(
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::{Order, StdResult, Uint128};
|
||||
use cosmwasm_std::{Addr, Order, StdResult, Uint128};
|
||||
|
||||
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, ExecuteMsg, LayerDistribution, Percent};
|
||||
@@ -680,38 +510,6 @@ pub mod tests {
|
||||
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_add_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
let (mixnode, sig, _) = test.mixnode_with_signature(owner, None);
|
||||
let cost_params = fixtures::mix_node_cost_params_fixture();
|
||||
|
||||
let res = try_add_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &good_mixnode_pledge()),
|
||||
mixnode,
|
||||
cost_params,
|
||||
owner.to_string(),
|
||||
sig,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_mixnode_cant_be_performed_if_epoch_transition_is_in_progress() {
|
||||
let bad_states = vec![
|
||||
@@ -761,23 +559,6 @@ pub mod tests {
|
||||
);
|
||||
|
||||
let mix_id = test.add_dummy_mixnode(owner, None);
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// attempted to remove on behalf with invalid proxy (current is `None`)
|
||||
let res = try_remove_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
owner.to_string(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: vesting_contract.into_string(),
|
||||
})
|
||||
);
|
||||
|
||||
// "normal" unbonding succeeds and unbonding event is pushed to the pending epoch events
|
||||
let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone());
|
||||
@@ -799,35 +580,6 @@ pub mod tests {
|
||||
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_remove_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
|
||||
let res = try_remove_mixnode_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_remove_is_not_allowed_if_there_are_pending_pledge_changes() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -908,22 +660,7 @@ pub mod tests {
|
||||
);
|
||||
|
||||
let mix_id = test.add_dummy_mixnode(owner, None);
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// attempted to remove on behalf with invalid proxy (current is `None`)
|
||||
let res = try_update_mixnode_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
update.clone(),
|
||||
owner.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: vesting_contract.into_string(),
|
||||
})
|
||||
);
|
||||
// "normal" update succeeds
|
||||
let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone());
|
||||
assert!(res.is_ok());
|
||||
@@ -943,41 +680,6 @@ pub mod tests {
|
||||
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_mixnode_config_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
let update = MixNodeConfigUpdate {
|
||||
host: "1.1.1.1:1234".to_string(),
|
||||
mix_port: 1234,
|
||||
verloc_port: 1235,
|
||||
http_api_port: 1236,
|
||||
version: "v1.2.3".to_string(),
|
||||
};
|
||||
|
||||
let res = try_update_mixnode_config_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
update,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_cost_params_cant_be_updated_when_epoch_transition_is_in_progress() {
|
||||
let bad_states = vec![
|
||||
@@ -1042,23 +744,7 @@ pub mod tests {
|
||||
);
|
||||
|
||||
let mix_id = test.add_dummy_mixnode(owner, None);
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
// attempted to remove on behalf with invalid proxy (current is `None`)
|
||||
let res = try_update_mixnode_cost_params_on_behalf(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
update.clone(),
|
||||
owner.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: vesting_contract.into_string(),
|
||||
})
|
||||
);
|
||||
// "normal" update succeeds
|
||||
let res = try_update_mixnode_cost_params(
|
||||
test.deps_mut(),
|
||||
@@ -1099,40 +785,6 @@ pub mod tests {
|
||||
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_mixnode_cost_params_with_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
let update = MixNodeCostParams {
|
||||
profit_margin_percent: Percent::from_percentage_value(42).unwrap(),
|
||||
interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM),
|
||||
};
|
||||
|
||||
let res = try_update_mixnode_cost_params_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
update,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adding_mixnode_with_duplicate_sphinx_key_errors_out() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -1240,69 +892,6 @@ pub mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_allowed_if_theres_proxy_mismatch() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let owner_without_proxy = Addr::unchecked("no-proxy");
|
||||
let owner_with_proxy = Addr::unchecked("with-proxy");
|
||||
let proxy = Addr::unchecked("proxy");
|
||||
let wrong_proxy = Addr::unchecked("unrelated-proxy");
|
||||
|
||||
test.add_dummy_mixnode(owner_without_proxy.as_str(), None);
|
||||
test.add_dummy_mixnode_with_illegal_proxy(
|
||||
owner_with_proxy.as_str(),
|
||||
None,
|
||||
proxy.clone(),
|
||||
);
|
||||
|
||||
let res = _try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
Vec::new(),
|
||||
owner_without_proxy.clone(),
|
||||
Some(proxy),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: "proxy".to_string(),
|
||||
})
|
||||
);
|
||||
|
||||
let res = _try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
Vec::new(),
|
||||
owner_with_proxy.clone(),
|
||||
None,
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "proxy".to_string(),
|
||||
incoming: "None".to_string(),
|
||||
})
|
||||
);
|
||||
|
||||
let res = _try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
Vec::new(),
|
||||
owner_with_proxy.clone(),
|
||||
Some(wrong_proxy),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "proxy".to_string(),
|
||||
incoming: "unrelated-proxy".to_string(),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -1457,35 +1046,6 @@ pub mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
|
||||
|
||||
let res = try_increase_pledge_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1546,73 +1106,6 @@ pub mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_allowed_if_theres_proxy_mismatch() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let owner_without_proxy = Addr::unchecked("no-proxy");
|
||||
let owner_with_proxy = Addr::unchecked("with-proxy");
|
||||
let proxy = Addr::unchecked("proxy");
|
||||
let wrong_proxy = Addr::unchecked("unrelated-proxy");
|
||||
|
||||
// just to make sure that after decrease the value would still be above the minimum
|
||||
let stake = Uint128::new(100_000_000_000);
|
||||
let decrease = test.coin(1000);
|
||||
|
||||
test.add_dummy_mixnode(owner_without_proxy.as_str(), Some(stake));
|
||||
test.add_dummy_mixnode_with_illegal_proxy(
|
||||
owner_with_proxy.as_str(),
|
||||
Some(stake),
|
||||
proxy.clone(),
|
||||
);
|
||||
|
||||
let res = _try_decrease_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
decrease.clone(),
|
||||
owner_without_proxy.clone(),
|
||||
Some(proxy),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "None".to_string(),
|
||||
incoming: "proxy".to_string(),
|
||||
})
|
||||
);
|
||||
|
||||
let res = _try_decrease_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
decrease.clone(),
|
||||
owner_with_proxy.clone(),
|
||||
None,
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "proxy".to_string(),
|
||||
incoming: "None".to_string(),
|
||||
})
|
||||
);
|
||||
|
||||
let res = _try_decrease_pledge(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
decrease,
|
||||
owner_with_proxy.clone(),
|
||||
Some(wrong_proxy),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::ProxyMismatch {
|
||||
existing: "proxy".to_string(),
|
||||
incoming: "unrelated-proxy".to_string(),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
|
||||
let mut test = TestSetup::new();
|
||||
@@ -1809,38 +1302,5 @@ pub mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
|
||||
let stake = Uint128::new(100_000_000_000);
|
||||
let decrease = test.coin(1000);
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "alice";
|
||||
|
||||
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(stake), illegal_proxy.clone());
|
||||
|
||||
let res = try_decrease_pledge_on_behalf(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
decrease,
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,51 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::interval::storage as interval_storage;
|
||||
use cosmwasm_std::{DepsMut, Order, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::PendingEpochEventKind;
|
||||
|
||||
fn ensure_no_pending_proxy_events(storage: &dyn Storage) -> Result<(), MixnetContractError> {
|
||||
let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(storage)?;
|
||||
let last_inserted = interval_storage::EPOCH_EVENT_ID_COUNTER.load(storage)?;
|
||||
|
||||
// no pending events
|
||||
if last_executed == last_inserted {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for maybe_event in
|
||||
interval_storage::PENDING_EPOCH_EVENTS.range(storage, None, None, Order::Ascending)
|
||||
{
|
||||
let (id, event_data) = maybe_event?;
|
||||
match event_data.kind {
|
||||
PendingEpochEventKind::Delegate { proxy, .. } => {
|
||||
if proxy.is_some() {
|
||||
return Err(MixnetContractError::FailedMigration {
|
||||
comment: format!(
|
||||
"there is a pending vesting contract delegation with id {id}"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
PendingEpochEventKind::Undelegate { proxy, .. } => {
|
||||
if proxy.is_some() {
|
||||
return Err(MixnetContractError::FailedMigration {
|
||||
comment: format!(
|
||||
"there is a pending vesting contract undelegation with id {id}"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> {
|
||||
ensure_no_pending_proxy_events(deps.storage)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response};
|
||||
|
||||
use super::storage;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::rewards::helpers;
|
||||
use crate::rewards::helpers::update_and_save_last_rewarded;
|
||||
use crate::support::helpers::{
|
||||
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
|
||||
AttachSendTokens,
|
||||
};
|
||||
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_active_set_update_event, new_mix_rewarding_event,
|
||||
@@ -16,22 +28,6 @@ use mixnet_contract_common::reward_params::{
|
||||
IntervalRewardingParamsUpdate, NodeRewardParams, Performance,
|
||||
};
|
||||
use mixnet_contract_common::{Delegation, EpochState, MixId};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::rewards::helpers;
|
||||
use crate::rewards::helpers::update_and_save_last_rewarded;
|
||||
use crate::support::helpers::{
|
||||
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
|
||||
ensure_proxy_match, ensure_sent_by_vesting_contract, send_to_proxy_or_owner,
|
||||
};
|
||||
|
||||
use super::storage;
|
||||
|
||||
pub(crate) fn try_reward_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
@@ -111,6 +107,14 @@ pub(crate) fn try_reward_mixnode(
|
||||
);
|
||||
}
|
||||
|
||||
// make sure node's profit margin is within the allowed range,
|
||||
// if not adjust it accordingly
|
||||
let params = mixnet_params_storage::CONTRACT_STATE
|
||||
.load(deps.storage)?
|
||||
.params;
|
||||
mix_rewarding.normalise_profit_margin(params.profit_margin);
|
||||
mix_rewarding.normalise_operating_cost(params.interval_operating_cost);
|
||||
|
||||
let rewarding_params = storage::REWARDING_PARAMS.load(deps.storage)?;
|
||||
let node_reward_params = NodeRewardParams::new(node_performance, node_status.is_active());
|
||||
|
||||
@@ -140,37 +144,16 @@ pub(crate) fn try_withdraw_operator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_withdraw_operator_reward(deps, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_withdraw_operator_reward_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_withdraw_operator_reward(deps, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_withdraw_operator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// we need to grab all of the node's details so we'd known original pledge alongside
|
||||
// we need to grab all of the node's details, so we'd known original pledge alongside
|
||||
// all the earned rewards (and obviously to know if this node even exists and is still
|
||||
// in the bonded state)
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?.ok_or(
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or(
|
||||
MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner.clone(),
|
||||
owner: info.sender.clone(),
|
||||
},
|
||||
)?;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
|
||||
ensure_bonded(&mix_details.bond_information)?;
|
||||
|
||||
let reward = helpers::withdraw_operator_reward(deps.storage, mix_details)?;
|
||||
@@ -178,26 +161,13 @@ pub(crate) fn _try_withdraw_operator_reward(
|
||||
|
||||
// if the reward is zero, don't track or send anything - there's no point
|
||||
if !reward.amount.is_zero() {
|
||||
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]);
|
||||
response = response.add_message(return_tokens);
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
|
||||
// otherwise, we don't care
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
if proxy == vesting_contract {
|
||||
let msg = VestingContractExecuteMsg::TrackReward {
|
||||
amount: reward.clone(),
|
||||
address: owner.clone().into_string(),
|
||||
};
|
||||
let track_reward_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
response = response.add_message(track_reward_message);
|
||||
}
|
||||
}
|
||||
response = response.send_tokens(&info.sender, reward.clone())
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_withdraw_operator_reward_event(
|
||||
&owner, &proxy, reward, mix_id,
|
||||
&info.sender,
|
||||
reward,
|
||||
mix_id,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -205,37 +175,15 @@ pub(crate) fn try_withdraw_delegator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_withdraw_delegator_reward(deps, mix_id, info.sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_withdraw_delegator_reward_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
owner: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
_try_withdraw_delegator_reward(deps, mix_id, owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_withdraw_delegator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
mix_id: MixId,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// see if the delegation even exists
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
|
||||
let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
|
||||
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
|
||||
None => {
|
||||
return Err(MixnetContractError::NoMixnodeDelegationFound {
|
||||
mix_id,
|
||||
address: owner.into_string(),
|
||||
proxy: proxy.map(Addr::into_string),
|
||||
address: info.sender.into_string(),
|
||||
proxy: None,
|
||||
});
|
||||
}
|
||||
Some(delegation) => delegation,
|
||||
@@ -257,33 +205,18 @@ pub(crate) fn _try_withdraw_delegator_reward(
|
||||
_ => (),
|
||||
};
|
||||
|
||||
ensure_proxy_match(&proxy, &delegation.proxy)?;
|
||||
|
||||
let reward = helpers::withdraw_delegator_reward(deps.storage, delegation, mix_rewarding)?;
|
||||
let mut response = Response::new();
|
||||
|
||||
// if the reward is zero, don't track or send anything - there's no point
|
||||
if !reward.amount.is_zero() {
|
||||
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]);
|
||||
response = response.add_message(return_tokens);
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
|
||||
// otherwise, we don't care
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
if proxy == vesting_contract {
|
||||
let msg = VestingContractExecuteMsg::TrackReward {
|
||||
amount: reward.clone(),
|
||||
address: owner.clone().into_string(),
|
||||
};
|
||||
let track_reward_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
response = response.add_message(track_reward_message);
|
||||
}
|
||||
}
|
||||
response = response.send_tokens(&info.sender, reward.clone())
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_withdraw_delegator_reward_event(
|
||||
&owner, &proxy, reward, mix_id,
|
||||
&info.sender,
|
||||
reward,
|
||||
mix_id,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1405,13 +1338,10 @@ pub mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod withdrawing_delegator_reward {
|
||||
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128};
|
||||
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use crate::interval::pending_events;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
|
||||
use cosmwasm_std::{BankMsg, CosmosMsg, Decimal, Uint128};
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -1742,60 +1672,14 @@ pub mod tests {
|
||||
let accumulated_actual = truncate_reward_amount(accumulated_quad);
|
||||
assert_eq_with_leeway(total_claimed, accumulated_actual, Uint128::new(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id =
|
||||
test.add_dummy_mixnode("mix-owner1", Some(Uint128::new(1_000_000_000_000)));
|
||||
|
||||
let delegator = "delegator";
|
||||
|
||||
test.add_immediate_delegation_with_illegal_proxy(
|
||||
delegator,
|
||||
100_000_000u128,
|
||||
mix_id,
|
||||
illegal_proxy.clone(),
|
||||
);
|
||||
|
||||
// reward the node
|
||||
test.skip_to_next_epoch_end();
|
||||
test.force_change_rewarded_set(vec![mix_id]);
|
||||
test.start_epoch_transition();
|
||||
test.reward_with_distribution(mix_id, test_helpers::performance(100.0));
|
||||
|
||||
let res = try_withdraw_delegator_reward_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
mix_id,
|
||||
delegator.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod withdrawing_operator_reward {
|
||||
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128};
|
||||
|
||||
use crate::interval::pending_events;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
|
||||
use super::*;
|
||||
use crate::interval::pending_events;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::{Addr, BankMsg, CosmosMsg, Uint128};
|
||||
|
||||
#[test]
|
||||
fn can_only_be_done_if_bond_exists() {
|
||||
@@ -1908,42 +1792,6 @@ pub mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_illegal_proxy() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let illegal_proxy = Addr::unchecked("not-vesting-contract");
|
||||
let vesting_contract = test.vesting_contract();
|
||||
|
||||
let owner = "mix-owner1";
|
||||
let mix_id = test.add_dummy_mixnode_with_illegal_proxy(
|
||||
owner,
|
||||
Some(Uint128::new(1_000_000_000_000)),
|
||||
illegal_proxy.clone(),
|
||||
);
|
||||
|
||||
// reward the node
|
||||
test.skip_to_next_epoch_end();
|
||||
test.force_change_rewarded_set(vec![mix_id]);
|
||||
test.start_epoch_transition();
|
||||
test.reward_with_distribution(mix_id, test_helpers::performance(100.0));
|
||||
|
||||
let res = try_withdraw_operator_reward_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
|
||||
owner.to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
MixnetContractError::SenderIsNotVestingContract {
|
||||
received: illegal_proxy,
|
||||
vesting_contract,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
use crate::gateways::storage as gateways_storage;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
|
||||
use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Response, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixNodeBond};
|
||||
use nym_contracts_common::Percent;
|
||||
|
||||
// helper trait to attach `Msg` to a response if it's provided
|
||||
#[allow(dead_code)]
|
||||
@@ -26,131 +26,16 @@ impl<T> AttachOptionalMessage<T> for Response<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// another helper trait to remove some duplicate code and consolidate comments regarding
|
||||
// possible epoch progression halting behaviour
|
||||
pub(crate) trait VestingTracking
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn maybe_add_track_vesting_undelegation_message(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError>;
|
||||
|
||||
fn maybe_add_track_vesting_unbond_mixnode_message(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError>;
|
||||
|
||||
fn maybe_add_track_vesting_decrease_mixnode_pledge(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError>;
|
||||
pub(crate) trait AttachSendTokens {
|
||||
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self;
|
||||
}
|
||||
|
||||
impl VestingTracking for Response {
|
||||
fn maybe_add_track_vesting_undelegation_message(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
// if there's a proxy set (i.e. the vesting contract), send the track message
|
||||
if let Some(proxy) = proxy {
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
|
||||
|
||||
// Note: this can INTENTIONALLY cause epoch progression halt if the proxy is not the vesting contract
|
||||
// But this is fine, since this situation should have NEVER occurred in the first place
|
||||
// (as all 'on_behalf' methods, including 'DelegateToMixnodeOnBehalf' that got us here,
|
||||
// explicitly require the proxy to be the vesting contract)
|
||||
// 'fixing' it would require manually inspecting the problematic event, investigating
|
||||
// it's cause and manually (presumably via migration) clearing it.
|
||||
if proxy != vesting_contract {
|
||||
return Err(MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: proxy,
|
||||
vesting_contract,
|
||||
});
|
||||
}
|
||||
|
||||
let msg = VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
};
|
||||
|
||||
let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
Ok(self.add_message(track_undelegate_message))
|
||||
} else {
|
||||
// there's no proxy so nothing to do
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_add_track_vesting_unbond_mixnode_message(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
// if there's a proxy set (i.e. the vesting contract), send the track message
|
||||
if let Some(proxy) = proxy {
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
|
||||
|
||||
// exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`.
|
||||
if proxy != vesting_contract {
|
||||
return Err(MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: proxy,
|
||||
vesting_contract,
|
||||
});
|
||||
}
|
||||
|
||||
let msg = VestingContractExecuteMsg::TrackUnbondMixnode { owner, amount };
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
Ok(self.add_message(track_unbond_message))
|
||||
} else {
|
||||
// there's no proxy so nothing to do
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_add_track_vesting_decrease_mixnode_pledge(
|
||||
self,
|
||||
storage: &dyn Storage,
|
||||
proxy: Option<Addr>,
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
if let Some(proxy) = proxy {
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
|
||||
|
||||
// exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`.
|
||||
if proxy != vesting_contract {
|
||||
return Err(MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: proxy,
|
||||
vesting_contract,
|
||||
});
|
||||
}
|
||||
|
||||
let msg = VestingContractExecuteMsg::TrackDecreasePledge { owner, amount };
|
||||
let track_decrease_pledge_message = wasm_execute(proxy, &msg, vec![])?;
|
||||
Ok(self.add_message(track_decrease_pledge_message))
|
||||
} else {
|
||||
// there's no proxy so nothing to do
|
||||
Ok(self)
|
||||
}
|
||||
impl<T> AttachSendTokens for Response<T> {
|
||||
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self {
|
||||
self.add_message(BankMsg::Send {
|
||||
to_address: to.as_ref().to_string(),
|
||||
amount: vec![amount],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,20 +43,6 @@ impl VestingTracking for Response {
|
||||
// api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
|
||||
// }
|
||||
|
||||
/// Attempts to construct a `BankMsg` to send specified tokens to the provided
|
||||
/// proxy address. If that's unavailable, the `BankMsg` will use the "owner" as the
|
||||
/// "to_address".
|
||||
pub(crate) fn send_to_proxy_or_owner(
|
||||
proxy: &Option<Addr>,
|
||||
owner: &Addr,
|
||||
amount: Vec<Coin>,
|
||||
) -> BankMsg {
|
||||
BankMsg::Send {
|
||||
to_address: proxy.as_ref().unwrap_or(owner).to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_pledge(
|
||||
mut pledge: Vec<Coin>,
|
||||
minimum_pledge: Coin,
|
||||
@@ -337,39 +208,6 @@ pub(crate) fn ensure_is_owner(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_proxy_match(
|
||||
actual: &Option<Addr>,
|
||||
expected: &Option<Addr>,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
if actual != expected {
|
||||
return Err(MixnetContractError::ProxyMismatch {
|
||||
existing: expected
|
||||
.as_ref()
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
incoming: actual
|
||||
.as_ref()
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_sent_by_vesting_contract(
|
||||
info: &MessageInfo,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let vesting_contract_address =
|
||||
crate::mixnet_contract_settings::storage::vesting_contract_address(storage)?;
|
||||
if info.sender != vesting_contract_address {
|
||||
Err(MixnetContractError::SenderIsNotVestingContract {
|
||||
received: info.sender.clone(),
|
||||
vesting_contract: vesting_contract_address,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> {
|
||||
if bond.is_unbonding {
|
||||
return Err(MixnetContractError::MixnodeIsUnbonding {
|
||||
@@ -431,3 +269,34 @@ pub(crate) fn decode_ed25519_identity_key(
|
||||
|
||||
Ok(public_key)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_profit_margin_within_range(
|
||||
storage: &dyn Storage,
|
||||
profit_margin: Percent,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let range = mixnet_params_storage::profit_margin_range(storage)?;
|
||||
if !range.within_range(profit_margin) {
|
||||
return Err(MixnetContractError::ProfitMarginOutsideRange {
|
||||
provided: profit_margin,
|
||||
range,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_operating_cost_within_range(
|
||||
storage: &dyn Storage,
|
||||
operating_cost: &Coin,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let range = mixnet_params_storage::interval_oprating_cost_range(storage)?;
|
||||
if !range.within_range(operating_cost.amount) {
|
||||
return Err(MixnetContractError::OperatingCostOutsideRange {
|
||||
denom: operating_cost.denom.clone(),
|
||||
provided: operating_cost.amount,
|
||||
range,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub(crate) fn valid_bond_gateway_msg(
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
|
||||
let msg = gateway_bonding_sign_payload(deps, sender, None, gateway.clone(), stake);
|
||||
let msg = gateway_bonding_sign_payload(deps, sender, gateway.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let identity_key = keypair.public_key().to_base58_string();
|
||||
|
||||
@@ -14,8 +14,7 @@ pub mod test_helpers {
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::delegations::transactions::try_delegate_to_mixnode;
|
||||
use crate::families::transactions::{try_create_family, try_join_family};
|
||||
use crate::gateways::storage as gateways_storage;
|
||||
use crate::gateways::transactions::{try_add_gateway, try_add_gateway_on_behalf};
|
||||
use crate::gateways::transactions::try_add_gateway;
|
||||
use crate::interval::transactions::{
|
||||
perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition,
|
||||
};
|
||||
@@ -27,9 +26,7 @@ pub mod test_helpers {
|
||||
};
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::mixnodes::storage::mixnode_bonds;
|
||||
use crate::mixnodes::transactions::{
|
||||
try_add_mixnode, try_add_mixnode_on_behalf, try_remove_mixnode,
|
||||
};
|
||||
use crate::mixnodes::transactions::{try_add_mixnode, try_remove_mixnode};
|
||||
use crate::rewards::queries::{
|
||||
query_pending_delegator_reward, query_pending_mixnode_operator_reward,
|
||||
};
|
||||
@@ -45,7 +42,7 @@ pub mod test_helpers {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::testing::MockApi;
|
||||
use cosmwasm_std::testing::MockQuerier;
|
||||
use cosmwasm_std::{coin, coins, Addr, Api, BankMsg, CosmosMsg, Storage};
|
||||
use cosmwasm_std::{coin, coins, Addr, BankMsg, CosmosMsg, Storage};
|
||||
use cosmwasm_std::{Coin, Order};
|
||||
use cosmwasm_std::{Decimal, Empty, MemoryStorage};
|
||||
use cosmwasm_std::{Deps, OwnedDeps};
|
||||
@@ -148,13 +145,6 @@ pub mod test_helpers {
|
||||
self.owner.clone()
|
||||
}
|
||||
|
||||
pub fn vesting_contract(&self) -> Addr {
|
||||
mixnet_params_storage::CONTRACT_STATE
|
||||
.load(self.deps().storage)
|
||||
.unwrap()
|
||||
.vesting_contract_address
|
||||
}
|
||||
|
||||
pub fn coin(&self, amount: u128) -> Coin {
|
||||
coin(amount, rewarding_denom(self.deps().storage).unwrap())
|
||||
}
|
||||
@@ -178,7 +168,6 @@ pub mod test_helpers {
|
||||
&mut self,
|
||||
family_owner_keys: &identity::KeyPair,
|
||||
member_node: IdentityKeyRef,
|
||||
vesting: bool,
|
||||
) -> MessageSignature {
|
||||
let identity = family_owner_keys.public_key().to_base58_string();
|
||||
|
||||
@@ -195,14 +184,7 @@ pub mod test_helpers {
|
||||
|
||||
let nonce = signing_storage::get_signing_nonce(self.deps().storage, owner).unwrap();
|
||||
|
||||
let proxy = if vesting {
|
||||
Some(self.vesting_contract())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let msg =
|
||||
construct_family_join_permit(nonce, family_head, proxy, member_node.to_owned());
|
||||
let msg = construct_family_join_permit(nonce, family_head, member_node.to_owned());
|
||||
|
||||
let sig_bytes = family_owner_keys
|
||||
.private_key()
|
||||
@@ -217,13 +199,11 @@ pub mod test_helpers {
|
||||
member: &str,
|
||||
member_keys: &identity::KeyPair,
|
||||
head_keys: &identity::KeyPair,
|
||||
vesting: bool,
|
||||
) {
|
||||
let member_identity = member_keys.public_key().to_base58_string();
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
|
||||
let join_permit =
|
||||
self.generate_family_join_permit(head_keys, &member_identity, vesting);
|
||||
let join_permit = self.generate_family_join_permit(head_keys, &member_identity);
|
||||
let family_head = FamilyHead::new(head_identity);
|
||||
|
||||
try_join_family(
|
||||
@@ -235,12 +215,13 @@ pub mod test_helpers {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_dummy_mixnode_with_new_family(
|
||||
&mut self,
|
||||
head: &str,
|
||||
label: &str,
|
||||
) -> (MixId, identity::KeyPair) {
|
||||
let (mix_id, keys) = self.add_dummy_mixnode_with_proxy_and_keypair(head, None);
|
||||
let (mix_id, keys) = self.add_dummy_mixnode_with_keypair(head, None);
|
||||
|
||||
try_create_family(self.deps_mut(), mock_info(head, &[]), label.to_string()).unwrap();
|
||||
(mix_id, keys)
|
||||
@@ -338,7 +319,7 @@ pub mod test_helpers {
|
||||
stake: Option<Uint128>,
|
||||
) -> MessageSignature {
|
||||
let stake = self.make_mix_pledge(stake);
|
||||
let msg = mixnode_bonding_sign_payload(self.deps(), owner, None, mixnode, stake);
|
||||
let msg = mixnode_bonding_sign_payload(self.deps(), owner, mixnode, stake);
|
||||
ed25519_sign_message(msg, key)
|
||||
}
|
||||
|
||||
@@ -359,13 +340,8 @@ pub mod test_helpers {
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
};
|
||||
|
||||
let msg = mixnode_bonding_sign_payload(
|
||||
self.deps(),
|
||||
owner,
|
||||
None,
|
||||
mixnode.clone(),
|
||||
stake.clone(),
|
||||
);
|
||||
let msg =
|
||||
mixnode_bonding_sign_payload(self.deps(), owner, mixnode.clone(), stake.clone());
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let info = mock_info(owner, &stake);
|
||||
@@ -389,156 +365,6 @@ pub mod test_helpers {
|
||||
(current_id_counter + 1, keypair)
|
||||
}
|
||||
|
||||
pub fn add_dummy_mixnode_with_proxy_and_keypair(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
) -> (MixId, identity::KeyPair) {
|
||||
let stake = self.make_mix_pledge(stake);
|
||||
|
||||
let proxy = self.vesting_contract();
|
||||
|
||||
let keypair = identity::KeyPair::new(&mut self.rng);
|
||||
let identity_key = keypair.public_key().to_base58_string();
|
||||
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
|
||||
|
||||
let mixnode = MixNode {
|
||||
identity_key,
|
||||
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
};
|
||||
|
||||
let msg = mixnode_bonding_sign_payload(
|
||||
self.deps(),
|
||||
owner,
|
||||
Some(proxy.clone()),
|
||||
mixnode.clone(),
|
||||
stake.clone(),
|
||||
);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let info = mock_info(proxy.as_str(), &stake);
|
||||
let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER
|
||||
.may_load(self.deps().storage)
|
||||
.unwrap()
|
||||
.unwrap_or_default();
|
||||
|
||||
let env = self.env();
|
||||
try_add_mixnode_on_behalf(
|
||||
self.deps_mut(),
|
||||
env,
|
||||
info,
|
||||
mixnode,
|
||||
tests::fixtures::mix_node_cost_params_fixture(),
|
||||
owner.to_string(),
|
||||
owner_signature,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// newly added mixnode gets assigned the current counter + 1
|
||||
(current_id_counter + 1, keypair)
|
||||
}
|
||||
|
||||
pub fn add_dummy_mixnode_with_legal_proxy(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
) -> MixId {
|
||||
self.add_dummy_mixnode_with_proxy_and_keypair(owner, stake)
|
||||
.0
|
||||
}
|
||||
|
||||
pub fn set_illegal_mixnode_proxy(&mut self, mix_id: MixId, proxy: Addr) {
|
||||
let mut bond_details = mixnodes_storage::mixnode_bonds()
|
||||
.load(self.deps().storage, mix_id)
|
||||
.unwrap();
|
||||
bond_details.proxy = Some(proxy);
|
||||
mixnodes_storage::mixnode_bonds()
|
||||
.save(self.deps_mut().storage, mix_id, &bond_details)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_dummy_gateway_with_illegal_proxy(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
proxy: Addr,
|
||||
) -> IdentityKey {
|
||||
let gateway_identity = self.add_dummy_gateway_with_legal_proxy(owner, stake);
|
||||
self.set_illegal_gateway_proxy(&gateway_identity, proxy);
|
||||
gateway_identity
|
||||
}
|
||||
|
||||
pub fn set_illegal_gateway_proxy(&mut self, gateway_id: &str, proxy: Addr) {
|
||||
let mut gateway = gateways_storage::gateways()
|
||||
.load(self.deps().storage, gateway_id)
|
||||
.unwrap();
|
||||
gateway.proxy = Some(proxy);
|
||||
gateways_storage::gateways()
|
||||
.save(self.deps_mut().storage, gateway_id, &gateway)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_dummy_gateway_with_legal_proxy(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
) -> IdentityKey {
|
||||
let stake = match stake {
|
||||
Some(amount) => {
|
||||
let denom = rewarding_denom(self.deps().storage).unwrap();
|
||||
Coin { denom, amount }
|
||||
}
|
||||
None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(),
|
||||
};
|
||||
|
||||
let keypair = identity::KeyPair::new(&mut self.rng);
|
||||
let identity_key = keypair.public_key().to_base58_string();
|
||||
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
|
||||
|
||||
let proxy = self.vesting_contract();
|
||||
|
||||
let gateway = Gateway {
|
||||
identity_key,
|
||||
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
|
||||
let msg = gateway_bonding_sign_payload(
|
||||
self.deps(),
|
||||
owner,
|
||||
Some(proxy.clone()),
|
||||
gateway.clone(),
|
||||
vec![stake.clone()],
|
||||
);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let env = self.env();
|
||||
let info = mock_info(proxy.as_ref(), &[stake]);
|
||||
|
||||
try_add_gateway_on_behalf(
|
||||
self.deps_mut(),
|
||||
env,
|
||||
info,
|
||||
gateway,
|
||||
owner.to_string(),
|
||||
owner_signature,
|
||||
)
|
||||
.unwrap();
|
||||
keypair.public_key().to_base58_string()
|
||||
}
|
||||
|
||||
pub fn add_dummy_mixnode_with_illegal_proxy(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
proxy: Addr,
|
||||
) -> MixId {
|
||||
let mix_id = self.add_dummy_mixnode_with_legal_proxy(owner, stake);
|
||||
self.set_illegal_mixnode_proxy(mix_id, proxy);
|
||||
mix_id
|
||||
}
|
||||
|
||||
pub fn mixnode_with_signature(
|
||||
&mut self,
|
||||
sender: &str,
|
||||
@@ -555,8 +381,7 @@ pub mod test_helpers {
|
||||
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
};
|
||||
let msg =
|
||||
mixnode_bonding_sign_payload(self.deps(), sender, None, mixnode.clone(), stake);
|
||||
let msg = mixnode_bonding_sign_payload(self.deps(), sender, mixnode.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
(mixnode, owner_signature, keypair)
|
||||
@@ -579,8 +404,7 @@ pub mod test_helpers {
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
|
||||
let msg =
|
||||
gateway_bonding_sign_payload(self.deps(), sender, None, gateway.clone(), stake);
|
||||
let msg = gateway_bonding_sign_payload(self.deps(), sender, gateway.clone(), stake);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
(gateway, owner_signature)
|
||||
@@ -625,87 +449,10 @@ pub mod test_helpers {
|
||||
Addr::unchecked(delegator),
|
||||
target,
|
||||
amount,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_immediate_delegation_with_legal_proxy(
|
||||
&mut self,
|
||||
delegator: &str,
|
||||
amount: impl Into<Uint128>,
|
||||
target: MixId,
|
||||
) {
|
||||
let denom = rewarding_denom(self.deps().storage).unwrap();
|
||||
let amount = Coin {
|
||||
denom,
|
||||
amount: amount.into(),
|
||||
};
|
||||
let env = self.env();
|
||||
let proxy = self.vesting_contract();
|
||||
pending_events::delegate(
|
||||
self.deps_mut(),
|
||||
&env,
|
||||
env.block.height,
|
||||
Addr::unchecked(delegator),
|
||||
target,
|
||||
amount,
|
||||
Some(proxy),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// to set illegal proxy we have to bypass "normal" flow and put the value
|
||||
// directly into the storage
|
||||
pub fn add_immediate_delegation_with_illegal_proxy(
|
||||
&mut self,
|
||||
delegator: &str,
|
||||
amount: impl Into<Uint128>,
|
||||
target: MixId,
|
||||
proxy: Addr,
|
||||
) {
|
||||
let denom = rewarding_denom(self.deps().storage).unwrap();
|
||||
let amount = Coin {
|
||||
denom,
|
||||
amount: amount.into(),
|
||||
};
|
||||
|
||||
let owner = self.deps.api.addr_validate(delegator).unwrap();
|
||||
let storage_key = Delegation::generate_storage_key(target, &owner, Some(&proxy));
|
||||
|
||||
let mut mix_rewarding = self.mix_rewarding(target);
|
||||
|
||||
let mut stored_delegation_amount = amount;
|
||||
|
||||
if let Some(existing_delegation) = delegations_storage::delegations()
|
||||
.may_load(&self.deps.storage, storage_key.clone())
|
||||
.unwrap()
|
||||
{
|
||||
let og_with_reward = mix_rewarding.undelegate(&existing_delegation).unwrap();
|
||||
stored_delegation_amount.amount += og_with_reward.amount;
|
||||
}
|
||||
|
||||
mix_rewarding
|
||||
.add_base_delegation(stored_delegation_amount.amount)
|
||||
.unwrap();
|
||||
|
||||
let delegation = Delegation::new(
|
||||
owner,
|
||||
target,
|
||||
mix_rewarding.total_unit_reward,
|
||||
stored_delegation_amount,
|
||||
self.env.block.height,
|
||||
Some(proxy),
|
||||
);
|
||||
|
||||
delegations_storage::delegations()
|
||||
.save(&mut self.deps.storage, storage_key, &delegation)
|
||||
.unwrap();
|
||||
rewards_storage::MIXNODE_REWARDING
|
||||
.save(&mut self.deps.storage, target, &mix_rewarding)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn add_delegation(
|
||||
&mut self,
|
||||
@@ -724,14 +471,8 @@ pub mod test_helpers {
|
||||
|
||||
pub fn remove_immediate_delegation(&mut self, delegator: &str, target: MixId) {
|
||||
let height = self.env.block.height;
|
||||
pending_events::undelegate(
|
||||
self.deps_mut(),
|
||||
height,
|
||||
Addr::unchecked(delegator),
|
||||
target,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
pending_events::undelegate(self.deps_mut(), height, Addr::unchecked(delegator), target)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn start_epoch_transition(&mut self) {
|
||||
@@ -1109,7 +850,6 @@ pub mod test_helpers {
|
||||
Addr::unchecked(format!("owner{}", i)),
|
||||
mix_id,
|
||||
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1205,7 +945,6 @@ pub mod test_helpers {
|
||||
pub fn mixnode_bonding_sign_payload(
|
||||
deps: Deps<'_>,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
mixnode: MixNode,
|
||||
stake: Vec<Coin>,
|
||||
) -> SignableMixNodeBondingMsg {
|
||||
@@ -1214,14 +953,13 @@ pub mod test_helpers {
|
||||
signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap();
|
||||
|
||||
let payload = MixnodeBondingPayload::new(mixnode, cost_params);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), stake, payload);
|
||||
SignableMixNodeBondingMsg::new(nonce, content)
|
||||
}
|
||||
|
||||
pub fn gateway_bonding_sign_payload(
|
||||
deps: Deps<'_>,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
gateway: Gateway,
|
||||
stake: Vec<Coin>,
|
||||
) -> SignableGatewayBondingMsg {
|
||||
@@ -1229,7 +967,7 @@ pub mod test_helpers {
|
||||
signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap();
|
||||
|
||||
let payload = GatewayBondingPayload::new(gateway);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
|
||||
let content = ContractMessageContent::new(Addr::unchecked(owner), stake, payload);
|
||||
SignableGatewayBondingMsg::new(nonce, content)
|
||||
}
|
||||
|
||||
@@ -1258,6 +996,8 @@ pub mod test_helpers {
|
||||
epochs_in_interval: 720,
|
||||
epoch_duration: Duration::from_secs(60 * 60),
|
||||
initial_rewarding_params: initial_rewarding_params(),
|
||||
profit_margin: Default::default(),
|
||||
interval_operating_cost: Default::default(),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::support::helpers::{
|
||||
ensure_bonded, ensure_epoch_in_progress_state, ensure_no_pending_pledge_changes,
|
||||
};
|
||||
use cosmwasm_std::{wasm_execute, DepsMut, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{Delegation, MixId};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingExecuteMsg;
|
||||
|
||||
pub(crate) fn try_migrate_vested_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or(
|
||||
MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: info.sender.clone(),
|
||||
},
|
||||
)?;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
ensure_no_pending_pledge_changes(&mix_details.pending_changes)?;
|
||||
ensure_bonded(&mix_details.bond_information)?;
|
||||
|
||||
let Some(proxy) = &mix_details.bond_information.proxy else {
|
||||
return Err(MixnetContractError::NotAVestingMixnode);
|
||||
};
|
||||
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
if proxy != vesting_contract {
|
||||
return Err(MixnetContractError::ProxyIsNotVestingContract {
|
||||
received: proxy.clone(),
|
||||
vesting_contract,
|
||||
});
|
||||
}
|
||||
|
||||
let mut updated_bond = mix_details.bond_information.clone();
|
||||
updated_bond.proxy = None;
|
||||
mixnodes_storage::mixnode_bonds().replace(
|
||||
deps.storage,
|
||||
mix_id,
|
||||
Some(&updated_bond),
|
||||
Some(&mix_details.bond_information),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_message(wasm_execute(
|
||||
vesting_contract,
|
||||
&VestingExecuteMsg::TrackMigratedMixnode {
|
||||
owner: info.sender.into_string(),
|
||||
},
|
||||
vec![],
|
||||
)?))
|
||||
}
|
||||
|
||||
pub(crate) fn try_migrate_vested_delegation(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
mix_id: MixId,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
|
||||
let storage_key =
|
||||
Delegation::generate_storage_key(mix_id, &info.sender, Some(&vesting_contract));
|
||||
let Some(mut delegation) =
|
||||
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
|
||||
else {
|
||||
return Err(MixnetContractError::NotAVestingDelegation);
|
||||
};
|
||||
|
||||
// sanity check that's meant to blow up the contract
|
||||
assert_eq!(delegation.proxy, Some(vesting_contract.clone()));
|
||||
|
||||
// update the delegation and save it under the correct storage key
|
||||
delegation.proxy = None;
|
||||
let updated_storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
|
||||
delegations_storage::delegations().remove(deps.storage, storage_key)?;
|
||||
delegations_storage::delegations().save(deps.storage, updated_storage_key, &delegation)?;
|
||||
|
||||
Ok(Response::new().add_message(wasm_execute(
|
||||
vesting_contract,
|
||||
&VestingExecuteMsg::TrackMigratedDelegation {
|
||||
owner: info.sender.into_string(),
|
||||
mix_id,
|
||||
},
|
||||
vec![],
|
||||
)?))
|
||||
}
|
||||
@@ -691,6 +691,54 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id",
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
|
||||
@@ -669,6 +669,54 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"track_migrated_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"track_migrated_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_id",
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"mix_id": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
|
||||
@@ -70,55 +70,12 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, VestingContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::CreateFamily { label } => try_create_family(info, deps, label),
|
||||
ExecuteMsg::JoinFamily {
|
||||
join_permit,
|
||||
family_head,
|
||||
} => try_join_family(info, deps, join_permit, family_head),
|
||||
ExecuteMsg::LeaveFamily { family_head } => try_leave_family(info, deps, family_head),
|
||||
ExecuteMsg::KickFamilyMember { member } => try_kick_family_member(info, deps, member),
|
||||
ExecuteMsg::UpdateLockedPledgeCap { address, cap } => {
|
||||
try_update_locked_pledge_cap(address, cap, info, deps)
|
||||
}
|
||||
ExecuteMsg::TrackReward { amount, address } => {
|
||||
try_track_reward(deps, info, amount, &address)
|
||||
}
|
||||
ExecuteMsg::ClaimOperatorReward {} => try_claim_operator_reward(deps, info),
|
||||
ExecuteMsg::ClaimDelegatorReward { mix_id } => {
|
||||
try_claim_delegator_reward(deps, info, mix_id)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
|
||||
try_update_mixnode_config(new_config, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
|
||||
try_update_mixnode_cost_params(new_costs, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnetAddress { address } => {
|
||||
try_update_mixnet_address(address, info, deps)
|
||||
}
|
||||
ExecuteMsg::DelegateToMixnode {
|
||||
mix_id,
|
||||
amount,
|
||||
on_behalf_of,
|
||||
} => try_delegate_to_mixnode(mix_id, amount, on_behalf_of, info, env, deps),
|
||||
ExecuteMsg::UndelegateFromMixnode {
|
||||
mix_id,
|
||||
on_behalf_of,
|
||||
} => try_undelegate_from_mixnode(mix_id, on_behalf_of, info, deps),
|
||||
ExecuteMsg::CreateAccount {
|
||||
owner_address,
|
||||
staking_address,
|
||||
vesting_spec,
|
||||
cap,
|
||||
} => try_create_periodic_vesting_account(
|
||||
&owner_address,
|
||||
staking_address,
|
||||
vesting_spec,
|
||||
cap,
|
||||
info,
|
||||
env,
|
||||
deps,
|
||||
),
|
||||
ExecuteMsg::WithdrawVestedCoins { amount } => {
|
||||
try_withdraw_vested_coins(amount, env, info, deps)
|
||||
}
|
||||
@@ -127,47 +84,22 @@ pub fn execute(
|
||||
mix_id,
|
||||
amount,
|
||||
} => try_track_undelegation(&owner, mix_id, amount, info, deps),
|
||||
ExecuteMsg::BondMixnode {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature,
|
||||
amount,
|
||||
} => try_bond_mixnode(
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature,
|
||||
amount,
|
||||
info,
|
||||
env,
|
||||
deps,
|
||||
),
|
||||
ExecuteMsg::PledgeMore { amount } => try_pledge_more(deps, env, info, amount),
|
||||
ExecuteMsg::DecreasePledge { amount } => try_decrease_pledge(deps, info, amount),
|
||||
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
|
||||
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
|
||||
try_track_unbond_mixnode(&owner, amount, info, deps)
|
||||
}
|
||||
ExecuteMsg::TrackDecreasePledge { owner, amount } => {
|
||||
try_track_decrease_mixnode_pledge(&owner, amount, info, deps)
|
||||
}
|
||||
ExecuteMsg::BondGateway {
|
||||
gateway,
|
||||
owner_signature,
|
||||
amount,
|
||||
} => try_bond_gateway(gateway, owner_signature, amount, info, env, deps),
|
||||
ExecuteMsg::UnbondGateway {} => try_unbond_gateway(info, deps),
|
||||
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
|
||||
try_track_unbond_gateway(&owner, amount, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateGatewayConfig { new_config } => {
|
||||
try_update_gateway_config(new_config, info, deps)
|
||||
}
|
||||
ExecuteMsg::TransferOwnership { to_address } => {
|
||||
try_transfer_ownership(to_address, info, deps)
|
||||
}
|
||||
ExecuteMsg::UpdateStakingAddress { to_address } => {
|
||||
try_update_staking_address(to_address, info, deps)
|
||||
ExecuteMsg::TrackMigratedMixnode { owner } => try_track_migrate_mixnode(&owner, info, deps),
|
||||
ExecuteMsg::TrackMigratedDelegation { owner, mix_id } => {
|
||||
try_track_migrate_delegation(&owner, mix_id, info, deps)
|
||||
}
|
||||
_ => Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ pub trait MixnodeBondingAccount {
|
||||
new_costs: MixNodeCostParams,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, VestingContractError>;
|
||||
fn try_track_migrated_mixnode(
|
||||
&self,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError>;
|
||||
}
|
||||
|
||||
pub trait GatewayBondingAccount {
|
||||
|
||||
@@ -44,4 +44,9 @@ pub trait DelegatingAccount {
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError>;
|
||||
fn track_migrated_delegation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError>;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ use mixnet_contract_common::{
|
||||
use vesting_contract_common::events::{
|
||||
new_ownership_transfer_event, new_periodic_vesting_account_event,
|
||||
new_staking_address_update_event, new_track_gateway_unbond_event,
|
||||
new_track_mixnode_pledge_decrease_event, new_track_mixnode_unbond_event,
|
||||
new_track_reward_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
|
||||
new_track_migrate_mixnode_event, new_track_mixnode_pledge_decrease_event,
|
||||
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
|
||||
new_vested_coins_withdraw_event,
|
||||
};
|
||||
use vesting_contract_common::{Account, PledgeCap, VestingContractError, VestingSpecification};
|
||||
|
||||
@@ -255,6 +256,35 @@ pub fn try_track_unbond_gateway(
|
||||
Ok(Response::new().add_event(new_track_gateway_unbond_event()))
|
||||
}
|
||||
|
||||
/// Track vesting mixnode being converted into the usage of liquid tokens. invoked by the mixnet contract after successful migration.
|
||||
pub fn try_track_migrate_mixnode(
|
||||
owner: &str,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, VestingContractError> {
|
||||
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
||||
return Err(VestingContractError::NotMixnetContract(info.sender));
|
||||
}
|
||||
let account = account_from_address(owner, deps.storage, deps.api)?;
|
||||
account.try_track_migrated_mixnode(deps.storage)?;
|
||||
Ok(Response::new().add_event(new_track_migrate_mixnode_event()))
|
||||
}
|
||||
|
||||
/// Track vesting delegation being converted into the usage of liquid tokens. invoked by the mixnet contract after successful migration.
|
||||
pub fn try_track_migrate_delegation(
|
||||
owner: &str,
|
||||
mix_id: MixId,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, VestingContractError> {
|
||||
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
||||
return Err(VestingContractError::NotMixnetContract(info.sender));
|
||||
}
|
||||
let account = account_from_address(owner, deps.storage, deps.api)?;
|
||||
account.track_migrated_delegation(mix_id, deps.storage)?;
|
||||
Ok(Response::new().add_event(new_track_migrate_mixnode_event()))
|
||||
}
|
||||
|
||||
/// Bond a mixnode, sends [mixnet_contract_common::ExecuteMsg::BondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
pub fn try_bond_mixnode(
|
||||
mix_node: MixNode,
|
||||
|
||||
@@ -125,4 +125,25 @@ impl DelegatingAccount for Account {
|
||||
self.save_balance(new_balance, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_migrated_delegation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError> {
|
||||
let delegation = self.total_delegations_for_mix(mix_id, storage)?;
|
||||
if delegation.is_zero() {
|
||||
return Err(VestingContractError::NoSuchDelegation(
|
||||
self.owner_address.clone(),
|
||||
mix_id,
|
||||
));
|
||||
}
|
||||
|
||||
// treat the tokens that were used for delegation as 'withdrawn'
|
||||
let current_withdrawn = self.load_withdrawn(storage)?;
|
||||
self.save_withdrawn(current_withdrawn + delegation, storage)?;
|
||||
|
||||
// remove the delegation data since it no longer belongs to the vesting contract
|
||||
self.remove_delegations_for_mix(mix_id, storage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,4 +221,24 @@ impl MixnodeBondingAccount for Account {
|
||||
.add_message(update_mixnode_costs_msg)
|
||||
.add_event(new_vesting_update_mixnode_cost_params_event()))
|
||||
}
|
||||
|
||||
fn try_track_migrated_mixnode(
|
||||
&self,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), VestingContractError> {
|
||||
let Some(pledge) = self.load_mixnode_pledge(storage)? else {
|
||||
return Err(VestingContractError::NoBondFound(
|
||||
self.owner_address().as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
// treat the tokens that were used for bonding as 'withdrawn'
|
||||
let current_withdrawn = self.load_withdrawn(storage)?;
|
||||
self.save_withdrawn(current_withdrawn + pledge.amount.amount, storage)?;
|
||||
|
||||
// don't change the balance as the tokens are left in the mixnet contract
|
||||
|
||||
// remove the pledge data since it no longer belongs to the vesting account
|
||||
self.remove_mixnode_pledge(storage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ pub fn populate_vesting_periods(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::contract::*;
|
||||
use crate::storage::*;
|
||||
|
||||
use crate::support::tests::helpers::vesting_account_percent_fixture;
|
||||
use crate::support::tests::helpers::{
|
||||
init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM,
|
||||
};
|
||||
use crate::traits::DelegatingAccount;
|
||||
use crate::traits::GatewayBondingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
use crate::vesting::account::StorableVestingAccountExt;
|
||||
use crate::vesting::populate_vesting_periods;
|
||||
use contracts_common::signing::MessageSignature;
|
||||
@@ -36,162 +36,56 @@ mod tests {
|
||||
fn test_account_creation() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("not_admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
|
||||
|
||||
let msg = ExecuteMsg::CreateAccount {
|
||||
owner_address: "owner".to_string(),
|
||||
staking_address: Some("staking".to_string()),
|
||||
vesting_spec: None,
|
||||
cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
|
||||
};
|
||||
// Try creating an account when not admin
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
assert!(response.is_err());
|
||||
|
||||
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
|
||||
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
||||
let created_account = load_account(Addr::unchecked("owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
||||
assert_eq!(
|
||||
created_account.load_balance(&deps.storage).unwrap(),
|
||||
// One was liquidated
|
||||
Uint128::new(1_000_000_000_000)
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
// nothing is saved for "staking" account!
|
||||
let created_account_test_by_staking =
|
||||
load_account(Addr::unchecked("staking"), &deps.storage).unwrap();
|
||||
assert!(created_account_test_by_staking.is_none());
|
||||
|
||||
// but we can stake on its behalf!
|
||||
let stake_msg = ExecuteMsg::DelegateToMixnode {
|
||||
on_behalf_of: Some("owner".to_string()),
|
||||
mix_id: 42,
|
||||
amount: coin(500, TEST_COIN_DENOM),
|
||||
};
|
||||
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("staking", &[]),
|
||||
stake_msg,
|
||||
);
|
||||
assert!(response.is_ok());
|
||||
|
||||
assert_eq!(
|
||||
created_account.load_balance(&deps.storage).unwrap(),
|
||||
// One was liquidated
|
||||
Uint128::new(999_999_999_500)
|
||||
);
|
||||
|
||||
// Try create the same account again
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert!(response.is_err());
|
||||
|
||||
let account_again = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
assert_eq!(created_account.storage_key(), 1);
|
||||
assert_ne!(created_account.storage_key(), account_again.storage_key());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ownership_transfer() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let env = mock_env();
|
||||
let info = mock_info("owner", &[]);
|
||||
let account = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
let staker = account.staking_address().unwrap();
|
||||
let msg = ExecuteMsg::TransferOwnership {
|
||||
to_address: "new_owner".to_string(),
|
||||
};
|
||||
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap();
|
||||
let new_owner_account = load_account(Addr::unchecked("new_owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
|
||||
assert_eq!(
|
||||
new_owner_account.load_balance(&deps.storage),
|
||||
account.load_balance(&deps.storage)
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
// Check old account is gone
|
||||
let old_owner_account = load_account(Addr::unchecked("owner"), &deps.storage).unwrap();
|
||||
assert!(old_owner_account.is_none());
|
||||
|
||||
// Not the owner
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert!(response.is_err());
|
||||
|
||||
// can't stake on behalf of the original owner anymore, but we can do it for the new one!
|
||||
let stake_msg = ExecuteMsg::DelegateToMixnode {
|
||||
on_behalf_of: Some("owner".to_string()),
|
||||
mix_id: 42,
|
||||
amount: coin(500, TEST_COIN_DENOM),
|
||||
};
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info(staker.as_ref(), &[]),
|
||||
stake_msg,
|
||||
);
|
||||
assert!(response.is_err());
|
||||
|
||||
let new_stake_msg = ExecuteMsg::DelegateToMixnode {
|
||||
on_behalf_of: Some("new_owner".to_string()),
|
||||
mix_id: 42,
|
||||
amount: coin(500, TEST_COIN_DENOM),
|
||||
};
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info(staker.as_ref(), &[]),
|
||||
new_stake_msg,
|
||||
);
|
||||
assert!(response.is_ok());
|
||||
|
||||
let info = mock_info("new_owner", &[]);
|
||||
let msg = ExecuteMsg::UpdateStakingAddress {
|
||||
to_address: Some("new_staking".to_string()),
|
||||
};
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
|
||||
let msg = ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: Coin {
|
||||
amount: Uint128::new(1),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
};
|
||||
let info = mock_info("new_owner", &[]);
|
||||
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
|
||||
assert!(response.is_ok());
|
||||
|
||||
let info = mock_info("owner", &[]);
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert!(response.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_staking_account() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
let env = mock_env();
|
||||
let info = mock_info("staking", &[]);
|
||||
let msg = ExecuteMsg::TransferOwnership {
|
||||
to_address: "new_owner".to_string(),
|
||||
};
|
||||
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg);
|
||||
// Only owner can transfer
|
||||
assert!(response.is_err());
|
||||
|
||||
let msg = ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: Coin {
|
||||
amount: Uint128::new(1),
|
||||
denom: "nym".to_string(),
|
||||
},
|
||||
};
|
||||
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
|
||||
let response = execute(deps.as_mut(), env, info, msg);
|
||||
// Only owner can withdraw
|
||||
assert!(response.is_err());
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -213,31 +107,12 @@ mod tests {
|
||||
mock_info(original_staker.as_ref(), &[]),
|
||||
stake_msg.clone(),
|
||||
);
|
||||
assert!(response.is_ok());
|
||||
|
||||
let info = mock_info("owner", &[]);
|
||||
let msg = ExecuteMsg::UpdateStakingAddress {
|
||||
to_address: Some("new_staking".to_string()),
|
||||
};
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
|
||||
// the old staking account can't do any staking anymore!
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info(original_staker.as_ref(), &[]),
|
||||
stake_msg.clone(),
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
assert!(response.is_err());
|
||||
|
||||
// but the new one can
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("new_staking", &[]),
|
||||
stake_msg,
|
||||
);
|
||||
assert!(response.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -245,66 +120,27 @@ mod tests {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let amount1 = coin(1000000000, "unym");
|
||||
let amount2 = coin(100, "unym");
|
||||
let amount = coin(1000000000, "unym");
|
||||
|
||||
// create the accounts
|
||||
let msg1 = ExecuteMsg::CreateAccount {
|
||||
let msg = ExecuteMsg::CreateAccount {
|
||||
owner_address: "vesting1".to_string(),
|
||||
staking_address: None,
|
||||
vesting_spec: None,
|
||||
cap: None,
|
||||
};
|
||||
let res1 = execute(
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("admin", &[amount1.clone()]),
|
||||
msg1,
|
||||
);
|
||||
assert!(res1.is_ok());
|
||||
|
||||
let msg2 = ExecuteMsg::CreateAccount {
|
||||
owner_address: "vesting2".to_string(),
|
||||
staking_address: None,
|
||||
vesting_spec: None,
|
||||
cap: None,
|
||||
};
|
||||
let res2 = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("admin", &[amount2.clone()]),
|
||||
msg2,
|
||||
);
|
||||
assert!(res2.is_ok());
|
||||
|
||||
let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap();
|
||||
assert_eq!(vesting1, amount1);
|
||||
|
||||
let vesting2 = try_get_vesting_coins("vesting2", None, env.clone(), deps.as_ref()).unwrap();
|
||||
assert_eq!(vesting2, amount2);
|
||||
|
||||
let staking_address_change = ExecuteMsg::UpdateStakingAddress {
|
||||
to_address: Some("vesting1".to_string()),
|
||||
};
|
||||
let res = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("vesting2", &[]),
|
||||
staking_address_change,
|
||||
mock_info("admin", &[amount.clone()]),
|
||||
msg,
|
||||
);
|
||||
assert_eq!(
|
||||
Err(VestingContractError::StakingAccountExists(
|
||||
"vesting1".to_string()
|
||||
)),
|
||||
res
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
// ensure nothing has changed!
|
||||
let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap();
|
||||
assert_eq!(vesting1, amount1);
|
||||
|
||||
let vesting2 = try_get_vesting_coins("vesting2", None, env, deps.as_ref()).unwrap();
|
||||
assert_eq!(vesting2, amount2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -566,63 +402,13 @@ mod tests {
|
||||
};
|
||||
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
|
||||
|
||||
let _response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
let account = load_account(Addr::unchecked("owner"), &deps.storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Try delegating too much
|
||||
let err = account.try_delegate_to_mixnode(
|
||||
1,
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_001),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let ok = account.try_delegate_to_mixnode(
|
||||
1,
|
||||
Coin {
|
||||
amount: Uint128::new(90_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
// Fails due to delegation locked delegation cap
|
||||
let ok = account.try_delegate_to_mixnode(
|
||||
1,
|
||||
Coin {
|
||||
amount: Uint128::new(20_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_err());
|
||||
|
||||
let balance = account.load_balance(&deps.storage).unwrap();
|
||||
assert_eq!(balance, Uint128::new(910000000000));
|
||||
|
||||
// Try delegating too much againcalca
|
||||
let err = account.try_delegate_to_mixnode(
|
||||
1,
|
||||
Coin {
|
||||
amount: Uint128::new(500_000_000_001),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let total_delegations = account.total_delegations_for_mix(1, &deps.storage).unwrap();
|
||||
assert_eq!(Uint128::new(90_000_000_000), total_delegations);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -649,52 +435,24 @@ mod tests {
|
||||
amount: Uint128::new(40),
|
||||
},
|
||||
};
|
||||
// Try delegating too much
|
||||
let err = account.try_bond_mixnode(
|
||||
mix_node.clone(),
|
||||
cost_params.clone(),
|
||||
MessageSignature::from(vec![1, 2, 3]),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_001),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let ok = account.try_bond_mixnode(
|
||||
mix_node.clone(),
|
||||
cost_params.clone(),
|
||||
MessageSignature::from(vec![1, 2, 3]),
|
||||
Coin {
|
||||
let msg = ExecuteMsg::BondMixnode {
|
||||
mix_node,
|
||||
cost_params,
|
||||
owner_signature: vec![1, 2, 3, 4].into(),
|
||||
amount: Coin {
|
||||
amount: Uint128::new(90_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
};
|
||||
let info = mock_info(account.owner_address.as_str(), &[]);
|
||||
let response = execute(deps.as_mut(), env.clone(), info, msg);
|
||||
assert_eq!(
|
||||
response,
|
||||
Err(VestingContractError::Other {
|
||||
message: "the contract has been disabled".to_string()
|
||||
})
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
let balance = account.load_balance(&deps.storage).unwrap();
|
||||
assert_eq!(balance, Uint128::new(910_000_000_000));
|
||||
|
||||
// Try delegating too much again
|
||||
let err = account.try_bond_mixnode(
|
||||
mix_node,
|
||||
cost_params,
|
||||
MessageSignature::from(vec![1, 2, 3]),
|
||||
Coin {
|
||||
amount: Uint128::new(10_000_000_001),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let pledge = account.load_mixnode_pledge(&deps.storage).unwrap().unwrap();
|
||||
assert_eq!(Uint128::new(90_000_000_000), pledge.amount().amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user