diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml index a78eadfff4..929913fb4f 100644 --- a/.github/workflows/build-and-upload-binaries-ci.yml +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -80,7 +80,7 @@ jobs: components: rustfmt, clippy - name: Install wasm-opt - run: cargo install wasm-opt + run: cargo install --version 0.110.0 wasm-opt - name: Build release contracts run: make wasm @@ -99,9 +99,14 @@ jobs: cp target/release/nym-network-statistics $OUTPUT_DIR cp target/release/nym-cli $OUTPUT_DIR cp target/release/credential $OUTPUT_DIR + cp target/release/explorer-api $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_bandwidth.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR - name: Deploy branch to CI www continue-on-error: true diff --git a/.github/workflows/contracts-build.yml b/.github/workflows/contracts-build.yml index 839a8804e6..36c809daf6 100644 --- a/.github/workflows/contracts-build.yml +++ b/.github/workflows/contracts-build.yml @@ -20,7 +20,7 @@ jobs: components: rustfmt, clippy - name: Install wasm-opt - run: cargo install wasm-opt + run: cargo install --version 0.110.0 wasm-opt - name: Build release contracts run: make wasm diff --git a/.gitignore b/.gitignore index 2b0cb82a78..82f4286297 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,5 @@ validator-api-config.toml dist storybook-static envs/qwerty.env -Cargo.lock -nym-connect/Cargo.lock .parcel-cache **/.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b033c5c4..dbc5cfaefb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [v1.1.13] (2023-03-15) + +- NE - instead of throwing a "Mixnode/Gateway not found" error for blacklisted nodes due to bad performance, show their history but tag them as "Having poor performance" ([#2979]) +- NE - Upgrade Sandbox and make below changes: ([#2332]) +- Explorer - Updates ([#3168]) +- Fix contracts and nym-api audit findings ([#3026]) +- Website v2 - deploy infrastructure for strapi and CI ([#2213]) +- add blockstream green to sp list ([#3180]) +- mock-nym-api: fix .storybook lint error ([#3178]) +- Validating new interval config parameters to prevent division by zero ([#3153]) + +[#2979]: https://github.com/nymtech/nym/issues/2979 +[#2332]: https://github.com/nymtech/nym/issues/2332 +[#3168]: https://github.com/nymtech/nym/issues/3168 +[#3026]: https://github.com/nymtech/nym/issues/3026 +[#2213]: https://github.com/nymtech/nym/issues/2213 +[#3180]: https://github.com/nymtech/nym/pull/3180 +[#3178]: https://github.com/nymtech/nym/pull/3178 +[#3153]: https://github.com/nymtech/nym/pull/3153 + ## [v1.1.12] (2023-03-07) - Fix generated docs for mixnet and vesting contract on docs.rs ([#3093]) diff --git a/Cargo.lock b/Cargo.lock index 30fbdeaa1d..dda9321c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,7 +214,7 @@ dependencies = [ "http", "http-body", "hyper", - "itoa 1.0.5", + "itoa", "matchit", "memchr", "mime", @@ -246,14 +246,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "bandwidth-claim-contract" -version = "0.1.0" -dependencies = [ - "schemars", - "serde", -] - [[package]] name = "base16ct" version = "0.1.1" @@ -433,18 +425,6 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - [[package]] name = "bumpalo" version = "3.12.0" @@ -587,17 +567,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags", - "textwrap 0.11.0", - "unicode-width", -] - [[package]] name = "clap" version = "3.2.23" @@ -607,7 +576,7 @@ dependencies = [ "bitflags", "clap_lex 0.2.4", "indexmap", - "textwrap 0.16.0", + "textwrap", ] [[package]] @@ -677,7 +646,7 @@ dependencies = [ [[package]] name = "client-core" -version = "1.1.11" +version = "1.1.13" dependencies = [ "async-trait", "dashmap 5.4.0", @@ -724,39 +693,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "coconut-bandwidth-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "multisig-contract-common", - "nym-contracts-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58", - "getset", - "nymcoconut", - "serde", - "thiserror", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -919,9 +855,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5abeeb891e6d0098402e4d3d042f90451db52651d2fe14b170e69a1dd3e4115" +checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" dependencies = [ "syn", ] @@ -997,12 +933,12 @@ version = "0.1.0" dependencies = [ "bip39", "clap 4.1.4", - "coconut-interface", - "credential-storage", - "credentials", "log", "nym-bin-common", + "nym-coconut-interface", "nym-config", + "nym-credential-storage", + "nym-credentials", "nym-crypto", "nym-network-defaults", "nym-pemstore", @@ -1014,57 +950,6 @@ dependencies = [ "validator-client", ] -[[package]] -name = "credential-storage" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "sqlx 0.5.13", - "thiserror", - "tokio", -] - -[[package]] -name = "credentials" -version = "0.1.0" -dependencies = [ - "bls12_381 0.5.0", - "coconut-interface", - "cosmrs", - "nym-api-requests", - "nym-crypto", - "rand 0.7.3", - "thiserror", - "validator-client", -] - -[[package]] -name = "criterion" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" -dependencies = [ - "atty", - "cast", - "clap 2.34.0", - "criterion-plot 0.4.5", - "csv", - "itertools", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - [[package]] name = "criterion" version = "0.4.0" @@ -1076,7 +961,7 @@ dependencies = [ "cast", "ciborium", "clap 3.2.23", - "criterion-plot 0.5.0", + "criterion-plot", "itertools", "lazy_static", "num-traits", @@ -1091,16 +976,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "criterion-plot" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" -dependencies = [ - "cast", - "itertools", -] - [[package]] name = "criterion-plot" version = "0.5.0" @@ -1238,28 +1113,6 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "ct-logs" version = "0.8.0" @@ -1579,29 +1432,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dkg" -version = "0.1.0" -dependencies = [ - "bitvec", - "bls12_381 0.6.0", - "bs58", - "criterion 0.3.6", - "ff 0.11.1", - "group 0.11.0", - "lazy_static", - "nym-contracts-common", - "nym-pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - [[package]] name = "doc-comment" version = "0.3.3" @@ -1782,11 +1612,11 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "explorer-api" -version = "1.1.11" +version = "1.1.13" dependencies = [ "chrono", "clap 4.1.4", - "dotenv", + "dotenvy", "humantime-serde", "isocountry", "itertools", @@ -2052,14 +1882,14 @@ name = "gateway-client" version = "0.1.0" dependencies = [ "async-trait", - "coconut-interface", - "credential-storage", - "credentials", "futures", "gateway-requests", "getrandom 0.2.8", "log", "mobile-storage", + "nym-coconut-interface", + "nym-credential-storage", + "nym-credentials", "nym-crypto", "nym-network-defaults", "nym-pemstore", @@ -2085,10 +1915,10 @@ name = "gateway-requests" version = "0.1.0" dependencies = [ "bs58", - "coconut-interface", - "credentials", "futures", "log", + "nym-coconut-interface", + "nym-credentials", "nym-crypto", "nym-pemstore", "nym-sphinx", @@ -2240,15 +2070,6 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "group-contract-common" -version = "0.1.0" -dependencies = [ - "cw4", - "schemars", - "serde", -] - [[package]] name = "h2" version = "0.3.15" @@ -2474,7 +2295,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.5", + "itoa", ] [[package]] @@ -2546,7 +2367,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.5", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -2776,12 +2597,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.5" @@ -3151,20 +2966,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw3-fixed-multisig", - "cw4", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -3261,7 +3062,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.12" +version = "1.1.14" dependencies = [ "anyhow", "async-trait", @@ -3269,33 +3070,32 @@ dependencies = [ "bs58", "cfg-if", "clap 4.1.4", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", - "coconut-interface", "console-subscriber", "cosmwasm-std", - "credential-storage", - "credentials", "cw-utils", "cw3", "cw4", "dirs", - "dkg", - "dotenv", "futures", "gateway-client", "getset", "humantime-serde", "lazy_static", "log", - "multisig-contract-common", "nym-api-requests", "nym-bin-common", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", "nym-config", "nym-contracts-common", + "nym-credential-storage", + "nym-credentials", "nym-crypto", + "nym-dkg", "nym-inclusion-probability", "nym-mixnet-contract-common", + "nym-multisig-contract-common", "nym-pemstore", "nym-sphinx", "nym-task", @@ -3330,19 +3130,27 @@ name = "nym-api-requests" version = "0.1.0" dependencies = [ "bs58", - "coconut-interface", "cosmrs", "cosmwasm-std", "getset", + "nym-coconut-interface", "nym-mixnet-contract-common", "schemars", "serde", "ts-rs", ] +[[package]] +name = "nym-bandwidth-claim-contract" +version = "0.1.0" +dependencies = [ + "schemars", + "serde", +] + [[package]] name = "nym-bin-common" -version = "0.2.0" +version = "0.3.0" dependencies = [ "clap 4.1.4", "clap_complete", @@ -3371,7 +3179,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.11" +version = "1.1.13" dependencies = [ "anyhow", "base64 0.13.1", @@ -3380,7 +3188,7 @@ dependencies = [ "clap 4.1.4", "clap_complete", "clap_complete_fig", - "dotenv", + "dotenvy", "log", "nym-bin-common", "nym-cli-commands", @@ -3402,8 +3210,6 @@ dependencies = [ "bs58", "cfg-if", "clap 4.1.4", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", "comfy-table", "cosmrs", "cosmwasm-std", @@ -3412,8 +3218,10 @@ dependencies = [ "humantime-serde", "k256", "log", - "multisig-contract-common", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", "nym-mixnet-contract-common", + "nym-multisig-contract-common", "nym-network-defaults", "nym-vesting-contract-common", "rand 0.6.5", @@ -3429,13 +3237,10 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.11" +version = "1.1.13" dependencies = [ "clap 4.1.4", "client-core", - "coconut-interface", - "credential-storage", - "credentials", "dirs", "futures", "gateway-client", @@ -3443,7 +3248,10 @@ dependencies = [ "lazy_static", "log", "nym-bin-common", + "nym-coconut-interface", "nym-config", + "nym-credential-storage", + "nym-credentials", "nym-crypto", "nym-network-defaults", "nym-pemstore", @@ -3463,6 +3271,39 @@ dependencies = [ "websocket-requests", ] +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-interface" +version = "0.1.0" +dependencies = [ + "bs58", + "getset", + "nymcoconut", + "serde", + "thiserror", +] + [[package]] name = "nym-config" version = "0.1.0" @@ -3478,7 +3319,7 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bs58", "cosmwasm-std", @@ -3488,6 +3329,31 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-credential-storage" +version = "0.1.0" +dependencies = [ + "async-trait", + "log", + "sqlx 0.5.13", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-credentials" +version = "0.1.0" +dependencies = [ + "bls12_381 0.5.0", + "cosmrs", + "nym-api-requests", + "nym-coconut-interface", + "nym-crypto", + "rand 0.7.3", + "thiserror", + "validator-client", +] + [[package]] name = "nym-crypto" version = "0.2.0" @@ -3513,6 +3379,29 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "nym-dkg" +version = "0.1.0" +dependencies = [ + "bitvec", + "bls12_381 0.6.0", + "bs58", + "criterion", + "ff 0.11.1", + "group 0.11.0", + "lazy_static", + "nym-contracts-common", + "nym-pemstore", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + [[package]] name = "nym-execute" version = "0.1.0" @@ -3523,7 +3412,7 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.11" +version = "1.1.13" dependencies = [ "anyhow", "async-trait", @@ -3531,12 +3420,10 @@ dependencies = [ "bip39", "bs58", "clap 4.1.4", - "coconut-interface", "colored", - "credentials", "dashmap 4.0.2", "dirs", - "dotenv", + "dotenvy", "futures", "gateway-requests", "humantime-serde", @@ -3546,7 +3433,9 @@ dependencies = [ "mixnode-common", "nym-api-requests", "nym-bin-common", + "nym-coconut-interface", "nym-config", + "nym-credentials", "nym-crypto", "nym-network-defaults", "nym-pemstore", @@ -3570,6 +3459,15 @@ dependencies = [ "validator-client", ] +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +dependencies = [ + "cw4", + "schemars", + "serde", +] + [[package]] name = "nym-inclusion-probability" version = "0.1.0" @@ -3581,7 +3479,7 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bs58", "cosmwasm-std", @@ -3600,7 +3498,7 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.12" +version = "1.1.14" dependencies = [ "anyhow", "atty", @@ -3609,7 +3507,6 @@ dependencies = [ "colored", "cupid", "dirs", - "dotenv", "futures", "humantime-serde", "lazy_static", @@ -3640,12 +3537,26 @@ dependencies = [ "validator-client", ] +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw3-fixed-multisig", + "cw4", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "nym-network-defaults" version = "0.1.0" dependencies = [ "cfg-if", - "dotenv", + "dotenvy", "hex-literal", "once_cell", "serde", @@ -3655,7 +3566,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.11" +version = "1.1.13" dependencies = [ "async-trait", "clap 4.1.4", @@ -3669,19 +3580,19 @@ dependencies = [ "nym-config", "nym-crypto", "nym-network-defaults", + "nym-ordered-buffer", "nym-sdk", + "nym-service-providers-common", + "nym-socks5-proxy-helpers", + "nym-socks5-requests", "nym-sphinx", "nym-statistics-common", "nym-task", - "ordered-buffer", "pretty_env_logger", - "proxy-helpers", "publicsuffix", "rand 0.7.3", "reqwest", "serde", - "service-providers-common", - "socks5-requests", "sqlx 0.6.2", "tap", "thiserror", @@ -3693,7 +3604,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.11" +version = "1.1.13" dependencies = [ "dirs", "log", @@ -3719,6 +3630,14 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "nym-ordered-buffer" +version = "0.1.0" +dependencies = [ + "log", + "thiserror", +] + [[package]] name = "nym-outfox" version = "0.1.0" @@ -3726,7 +3645,7 @@ dependencies = [ "blake3", "chacha20", "chacha20poly1305", - "criterion 0.4.0", + "criterion", "curve25519-dalek", "fastrand", "getrandom 0.2.8", @@ -3767,15 +3686,28 @@ dependencies = [ "validator-client", ] +[[package]] +name = "nym-service-providers-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "log", + "nym-bin-common", + "nym-sdk", + "nym-sphinx-anonymous-replies", + "serde", + "serde_json", + "thiserror", + "tokio", +] + [[package]] name = "nym-socks5-client" -version = "1.1.11" +version = "1.1.13" dependencies = [ "clap 4.1.4", "client-core", - "coconut-interface", - "credential-storage", - "credentials", "dirs", "futures", "gateway-client", @@ -3784,22 +3716,25 @@ dependencies = [ "log", "mobile-storage", "nym-bin-common", + "nym-coconut-interface", "nym-config", + "nym-credential-storage", + "nym-credentials", "nym-crypto", "nym-network-defaults", + "nym-ordered-buffer", "nym-pemstore", + "nym-service-providers-common", + "nym-socks5-proxy-helpers", + "nym-socks5-requests", "nym-sphinx", "nym-task", "nym-topology", - "ordered-buffer", "pin-project", "pretty_env_logger", - "proxy-helpers", "rand 0.7.3", "serde", "serde_json", - "service-providers-common", - "socks5-requests", "tap", "thiserror", "tokio", @@ -3807,6 +3742,32 @@ dependencies = [ "validator-client", ] +[[package]] +name = "nym-socks5-proxy-helpers" +version = "0.1.0" +dependencies = [ + "bytes", + "futures", + "log", + "nym-ordered-buffer", + "nym-socks5-requests", + "nym-task", + "tokio", + "tokio-test", + "tokio-util", +] + +[[package]] +name = "nym-socks5-requests" +version = "0.1.0" +dependencies = [ + "nym-service-providers-common", + "nym-sphinx-addressing", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "nym-sphinx" version = "0.1.0" @@ -3980,12 +3941,12 @@ dependencies = [ name = "nym-types" version = "1.0.0" dependencies = [ - "coconut-interface", "cosmrs", "cosmwasm-std", "eyre", "itertools", "log", + "nym-coconut-interface", "nym-config", "nym-mixnet-contract-common", "nym-vesting-contract", @@ -4004,14 +3965,17 @@ dependencies = [ [[package]] name = "nym-vesting-contract" -version = "1.1.4" +version = "1.2.0-pre.1" dependencies = [ + "cosmwasm-derive", "cosmwasm-std", "cw-storage-plus", + "cw2", "nym-contracts-common", "nym-mixnet-contract-common", "nym-vesting-contract-common", "schemars", + "semver 1.0.16", "serde", "thiserror", "vergen", @@ -4019,7 +3983,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" -version = "0.2.0" +version = "0.3.0" dependencies = [ "cosmwasm-std", "nym-contracts-common", @@ -4055,14 +4019,14 @@ version = "0.5.0" dependencies = [ "bls12_381 0.6.0", "bs58", - "criterion 0.3.6", + "criterion", "digest 0.9.0", - "dkg", "doc-comment", "ff 0.11.1", "getrandom 0.2.8", "group 0.11.0", "itertools", + "nym-dkg", "nym-pemstore", "rand 0.8.5", "rand_chacha 0.3.1", @@ -4153,14 +4117,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ordered-buffer" -version = "0.1.0" -dependencies = [ - "log", - "thiserror", -] - [[package]] name = "os_str_bytes" version = "6.4.1" @@ -4593,21 +4549,6 @@ dependencies = [ "prost 0.11.6", ] -[[package]] -name = "proxy-helpers" -version = "0.1.0" -dependencies = [ - "bytes", - "futures", - "log", - "nym-task", - "ordered-buffer", - "socks5-requests", - "tokio", - "tokio-test", - "tokio-util", -] - [[package]] name = "publicsuffix" version = "1.5.4" @@ -5433,16 +5374,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.152" @@ -5471,7 +5402,7 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ - "itoa 1.0.5", + "itoa", "ryu", "serde", ] @@ -5494,27 +5425,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.5", + "itoa", "ryu", "serde", ] -[[package]] -name = "service-providers-common" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "log", - "nym-bin-common", - "nym-sdk", - "nym-sphinx-anonymous-replies", - "serde", - "serde_json", - "thiserror", - "tokio", -] - [[package]] name = "sha-1" version = "0.9.8" @@ -5691,17 +5606,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "socks5-requests" -version = "0.1.0" -dependencies = [ - "nym-sphinx-addressing", - "serde", - "serde_json", - "service-providers-common", - "thiserror", -] - [[package]] name = "sphinx-packet" version = "0.1.0" @@ -5818,7 +5722,7 @@ dependencies = [ "hashlink 0.7.0", "hex", "indexmap", - "itoa 1.0.5", + "itoa", "libc", "libsqlite3-sys", "log", @@ -5865,7 +5769,7 @@ dependencies = [ "hashlink 0.8.1", "hex", "indexmap", - "itoa 1.0.5", + "itoa", "libc", "libsqlite3-sys", "log", @@ -6217,15 +6121,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "textwrap" version = "0.16.0" @@ -6278,7 +6173,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa 1.0.5", + "itoa", "js-sys", "serde", "time-core", @@ -6851,9 +6746,6 @@ dependencies = [ "async-trait", "base64 0.13.1", "bip39", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", - "coconut-interface", "colored", "cosmrs", "cosmwasm-std", @@ -6861,15 +6753,18 @@ dependencies = [ "cw4", "flate2", "futures", - "group-contract-common", "itertools", "log", - "multisig-contract-common", "nym-api-requests", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", "nym-config", "nym-contracts-common", "nym-execute", + "nym-group-contract-common", "nym-mixnet-contract-common", + "nym-multisig-contract-common", "nym-network-defaults", "nym-vesting-contract", "nym-vesting-contract-common", @@ -6882,6 +6777,7 @@ dependencies = [ "tokio", "ts-rs", "url", + "zeroize", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 19a77963c4..f1e6937055 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,17 @@ license = "Apache-2.0" [workspace.dependencies] async-trait = "0.1.64" cfg-if = "1.0.0" -dotenv = "0.15.0" +cosmwasm-derive = "=1.0.0" +cosmwasm-schema = "=1.0.0" +cosmwasm-std = "=1.0.0" +cosmwasm-storage = "=1.0.0" +cw-utils = "=0.13.4" +cw-storage-plus = "=0.13.4" +cw2 = { version = "=0.13.4" } +cw3 = { version = "=0.13.4" } +cw3-fixed-multisig = { version = "=0.13.4" } +cw4 = { version = "=0.13.4" } +dotenvy = "0.15.6" lazy_static = "1.4.0" log = "0.4" once_cell = "1.7.2" diff --git a/Makefile b/Makefile index a3a51f4e6b..7a012f7ba2 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,22 @@ test: clippy-all cargo-test wasm fmt +test-no-mobile: clippy-all-no-mobile cargo-test-no-mobile wasm fmt-no-mobile test-all: test cargo-test-expensive +test-all-no-mobile: test-no-mobile cargo-test-expensive no-clippy: build cargo-test wasm fmt +no-clippy-no-mobile: build-no-mobile cargo-test-no-mobile wasm fmt-no-mobile happy: fmt clippy-happy test -clippy-all: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-connect-mobile clippy-all-wasm-client -clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect clippy-happy-connect-mobile -cargo-test: test-main test-contracts test-wallet test-connect test-connect-mobile +happy-no-mobile: fmt-no-mobile clippy-happy-no-mobile test-no-mobile +clippy-all: clippy-all-no-mobile clippy-all-connect-mobile +clippy-all-no-mobile: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client +clippy-happy: clippy-happy-no-mobile clippy-happy-connect-mobile +clippy-happy-no-mobile: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect +cargo-test: cargo-test-no-mobile test-connect-mobile +cargo-test-no-mobile: test-main test-contracts test-wallet test-connect cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive -build: build-contracts build-wallet build-main build-main-examples build-connect build-connect-mobile build-wasm-client -fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-connect-mobile fmt-wasm-client +build: build-no-mobile build-connect-mobile +build-no-mobile: build-contracts build-wallet build-main build-main-examples build-connect build-wasm-client +fmt: fmt-no-mobile fmt-connect-mobile +fmt-no-mobile: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client clippy-happy-main: cargo clippy diff --git a/README.md b/README.md index 96409f6e0e..3a7f37ca33 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr * nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework. [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) -[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop) +[![Build Status](https://img.shields.io/github/actions/workflow/status/nymtech/nym/build.yml?branch=develop&style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop) ### Building diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index 4cfea3a095..36fc8b52eb 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "client-core" -version = "1.1.12" +version = "1.1.13" authors = ["Dave Hrycyszyn "] edition = "2021" rust-version = "1.66" @@ -37,6 +37,10 @@ nym-topology = { path = "../../common/topology" } validator-client = { path = "../../common/client-libs/validator-client", default-features = false } nym-task = { path = "../../common/task" } +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.validator-client] +path = "../../common/client-libs/validator-client" +features = ["nyxd-client"] + [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream] version = "0.1.11" features = ["time"] diff --git a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/mod.rs b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/mod.rs index fd8a618542..8830a33211 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/mod.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/mod.rs @@ -155,18 +155,21 @@ impl Backend { // (assuming no key rotation has happened) // but the way it's currently coded, everyone will purge old data let since_last_flush = OffsetDateTime::now_utc() - last_flush; - if since_last_flush.whole_days() > 0 { - info!("it's been over {} days and {} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours()); + let days = since_last_flush.whole_days(); + let hours = since_last_flush.whole_hours() % 24; + + if days > 0 { + info!("it's been over {days} days and {hours} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now."); manager.delete_all_reply_surb_data().await?; } - if since_last_flush.whole_days() > 1 { - info!("it's been over {} days and {} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours()); + if days > 1 { + info!("it's been over {days} days and {hours} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now."); manager.delete_all_reply_keys().await?; } - if since_last_flush.whole_days() > 2 { - info!("it's been over {} days and {} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours()); + if days > 2 { + info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now."); manager.delete_all_tags().await?; } diff --git a/clients/credential/Cargo.toml b/clients/credential/Cargo.toml index 84e5bf68c6..ca836f3858 100644 --- a/clients/credential/Cargo.toml +++ b/clients/credential/Cargo.toml @@ -15,10 +15,10 @@ thiserror = "1.0" url = "2.2" tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime -coconut-interface = { path = "../../common/coconut-interface" } +nym-coconut-interface = { path = "../../common/coconut-interface" } nym-config = { path = "../../common/config" } -credentials = { path = "../../common/credentials" } -credential-storage = { path = "../../common/credential-storage" } +nym-credentials = { path = "../../common/credentials" } +nym-credential-storage = { path = "../../common/credential-storage" } nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] } nym-bin-common = { path = "../../common/bin-common"} nym-network-defaults = { path = "../../common/network-defaults" } diff --git a/clients/credential/src/commands.rs b/clients/credential/src/commands.rs index 4dbc1e30f2..fe58078f54 100644 --- a/clients/credential/src/commands.rs +++ b/clients/credential/src/commands.rs @@ -7,11 +7,11 @@ use nym_bin_common::completions::ArgShell; use rand::rngs::OsRng; use std::str::FromStr; -use coconut_interface::{Base58, Parameters}; -use credential_storage::storage::Storage; -use credential_storage::PersistentStorage; -use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES}; -use credentials::coconut::utils::obtain_aggregate_signature; +use nym_coconut_interface::{Base58, Parameters}; +use nym_credential_storage::storage::Storage; +use nym_credential_storage::PersistentStorage; +use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES}; +use nym_credentials::coconut::utils::obtain_aggregate_signature; use nym_crypto::asymmetric::{encryption, identity}; use nym_network_defaults::VOUCHER_INFO; use validator_client::nyxd::traits::DkgQueryClient; diff --git a/clients/credential/src/error.rs b/clients/credential/src/error.rs index b6d1c3c1d4..aa32d845c1 100644 --- a/clients/credential/src/error.rs +++ b/clients/credential/src/error.rs @@ -4,8 +4,8 @@ use std::time::SystemTimeError; use thiserror::Error; -use credential_storage::error::StorageError; -use credentials::error::Error as CredentialError; +use nym_credential_storage::error::StorageError; +use nym_credentials::error::Error as CredentialError; use nym_crypto::asymmetric::encryption::KeyRecoveryError; use nym_crypto::asymmetric::identity::Ed25519RecoveryError; use validator_client::nyxd::error::NyxdError; diff --git a/clients/credential/src/main.rs b/clients/credential/src/main.rs index 90e7a27f32..3965eba15f 100644 --- a/clients/credential/src/main.rs +++ b/clients/credential/src/main.rs @@ -77,7 +77,7 @@ async fn main() -> Result<()> { .client_home_directory .join(DATA_DIR) .join(CRED_DB_FILE_NAME); - let shared_storage = credential_storage::initialise_storage(db_path).await; + let shared_storage = nym_credential_storage::initialise_storage(db_path).await; let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?; let network_details = NymNetworkDetails::new_from_env(); diff --git a/clients/credential/src/recovery_storage.rs b/clients/credential/src/recovery_storage.rs index 7d48740398..db81d2ff6a 100644 --- a/clients/credential/src/recovery_storage.rs +++ b/clients/credential/src/recovery_storage.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; use std::fs::{create_dir_all, read_dir, File}; use std::io::{Read, Write}; use std::path::PathBuf; diff --git a/clients/credential/src/state.rs b/clients/credential/src/state.rs index 6a0ec4253f..a4894a8ced 100644 --- a/clients/credential/src/state.rs +++ b/clients/credential/src/state.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_interface::Parameters; -use credentials::coconut::bandwidth::BandwidthVoucher; +use nym_coconut_interface::Parameters; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; use nym_crypto::asymmetric::{encryption, identity}; diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index ec7b7942b3..c7190f1b18 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.12" +version = "1.1.13" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" @@ -36,10 +36,10 @@ tokio-tungstenite = "0.14" # websocket ## internal nym-bin-common = { path = "../../common/bin-common" } client-core = { path = "../client-core", features = ["fs-surb-storage"] } -coconut-interface = { path = "../../common/coconut-interface" } +nym-coconut-interface = { path = "../../common/coconut-interface" } nym-config = { path = "../../common/config" } -credential-storage = { path = "../../common/credential-storage" } -credentials = { path = "../../common/credentials" } +nym-credential-storage = { path = "../../common/credential-storage" } +nym-credentials = { path = "../../common/credentials" } nym-crypto = { path = "../../common/crypto" } gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-requests = { path = "../../gateway/gateway-requests" } diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index 796435fb5f..aedfc0494d 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -74,7 +74,7 @@ impl SocketClient { let client = validator_client::Client::new_query(client_config) .expect("Could not construct query client"); BandwidthController::new( - credential_storage::initialise_storage(config.get_base().get_database_path()).await, + nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await, client, ) } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 2dbe43dac6..a9e131f778 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.12" +version = "1.1.13" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" @@ -29,26 +29,26 @@ url = "2.2" # internal nym-bin-common = { path = "../../common/bin-common" } client-core = { path = "../client-core", features = ["fs-surb-storage"] } -coconut-interface = { path = "../../common/coconut-interface" } +nym-coconut-interface = { path = "../../common/coconut-interface" } nym-config = { path = "../../common/config" } -credential-storage = { path = "../../common/credential-storage", optional = true } +nym-credential-storage = { path = "../../common/credential-storage", optional = true } mobile-storage = { path = "../../common/mobile-storage", optional = true } -credentials = { path = "../../common/credentials" } +nym-credentials = { path = "../../common/credentials" } nym-crypto = { path = "../../common/crypto" } gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-requests = { path = "../../gateway/gateway-requests" } nym-network-defaults = { path = "../../common/network-defaults" } nym-sphinx = { path = "../../common/nymsphinx" } -ordered-buffer = { path = "../../common/socks5/ordered-buffer" } +nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } nym-pemstore = { path = "../../common/pemstore" } -proxy-helpers = { path = "../../common/socks5/proxy-helpers" } -service-providers-common = { path = "../../service-providers/common" } -socks5-requests = { path = "../../common/socks5/requests" } +nym-socks5-proxy-helpers = { path = "../../common/socks5/proxy-helpers" } +nym-service-providers-common = { path = "../../service-providers/common" } +nym-socks5-requests = { path = "../../common/socks5/requests" } nym-task = { path = "../../common/task" } nym-topology = { path = "../../common/topology" } validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] } [features] -default = ["credential-storage"] +default = ["nym-credential-storage"] eth = [] mobile = ["mobile-storage", "gateway-client/mobile"] diff --git a/clients/socks5/src/client/config/mod.rs b/clients/socks5/src/client/config/mod.rs index 15791ba9fa..cf974199a3 100644 --- a/clients/socks5/src/client/config/mod.rs +++ b/clients/socks5/src/client/config/mod.rs @@ -7,10 +7,10 @@ pub use client_core::config::MISSING_VALUE; use client_core::config::{ClientCoreConfigTrait, DebugConfig}; use nym_config::defaults::DEFAULT_SOCKS5_LISTENING_PORT; use nym_config::{NymConfig, OptionalSet}; +use nym_service_providers_common::interface::ProviderInterfaceVersion; +use nym_socks5_requests::Socks5ProtocolVersion; use nym_sphinx::addressing::clients::Recipient; use serde::{Deserialize, Serialize}; -use service_providers_common::interface::ProviderInterfaceVersion; -use socks5_requests::Socks5ProtocolVersion; use std::fmt::Debug; use std::path::PathBuf; use std::str::FromStr; diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 5a6454d361..50d0718ca7 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -92,7 +92,7 @@ impl NymClient { #[cfg(not(feature = "mobile"))] let storage = - credential_storage::initialise_storage(config.get_base().get_database_path()).await; + nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await; #[cfg(feature = "mobile")] let storage = mobile_storage::PersistentStorage {}; diff --git a/clients/socks5/src/error.rs b/clients/socks5/src/error.rs index 5d9092867f..4504511ec2 100644 --- a/clients/socks5/src/error.rs +++ b/clients/socks5/src/error.rs @@ -1,6 +1,6 @@ use crate::socks::types::SocksProxyError; use client_core::error::ClientCoreError; -use socks5_requests::{ConnectionError, ConnectionId}; +use nym_socks5_requests::{ConnectionError, ConnectionId}; #[derive(thiserror::Error, Debug)] pub enum Socks5ClientError { diff --git a/clients/socks5/src/socks/client.rs b/clients/socks5/src/socks/client.rs index 83e5c68530..25f4e2d568 100644 --- a/clients/socks5/src/socks/client.rs +++ b/clients/socks5/src/socks/client.rs @@ -8,19 +8,19 @@ use client_core::client::inbound_messages::{InputMessage, InputMessageSender}; use futures::channel::mpsc; use futures::task::{Context, Poll}; use log::*; +use nym_service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion}; +use nym_socks5_proxy_helpers::connection_controller::{ + ConnectionReceiver, ControllerCommand, ControllerSender, +}; +use nym_socks5_proxy_helpers::proxy_runner::ProxyRunner; +use nym_socks5_requests::{ + ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request, +}; use nym_sphinx::addressing::clients::Recipient; use nym_task::connections::{LaneQueueLengths, TransmissionLane}; use nym_task::TaskClient; use pin_project::pin_project; -use proxy_helpers::connection_controller::{ - ConnectionReceiver, ControllerCommand, ControllerSender, -}; -use proxy_helpers::proxy_runner::ProxyRunner; use rand::RngCore; -use service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion}; -use socks5_requests::{ - ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request, -}; use std::io; use std::net::SocketAddr; use std::pin::Pin; diff --git a/clients/socks5/src/socks/mixnet_responses.rs b/clients/socks5/src/socks/mixnet_responses.rs index e905cd2d84..ed45958381 100644 --- a/clients/socks5/src/socks/mixnet_responses.rs +++ b/clients/socks5/src/socks/mixnet_responses.rs @@ -4,11 +4,11 @@ use log::*; use client_core::client::received_buffer::ReconstructedMessagesReceiver; use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender}; +use nym_service_providers_common::interface::{ControlResponse, ResponseContent}; +use nym_socks5_proxy_helpers::connection_controller::ControllerSender; +use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent}; use nym_sphinx::receiver::ReconstructedMessage; use nym_task::TaskClient; -use proxy_helpers::connection_controller::ControllerSender; -use service_providers_common::interface::{ControlResponse, ResponseContent}; -use socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent}; use crate::error::Socks5ClientError; diff --git a/clients/socks5/src/socks/server.rs b/clients/socks5/src/socks/server.rs index f986f90777..57bc8fe25e 100644 --- a/clients/socks5/src/socks/server.rs +++ b/clients/socks5/src/socks/server.rs @@ -8,10 +8,10 @@ use client_core::client::{ inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender, }; use log::*; +use nym_socks5_proxy_helpers::connection_controller::Controller; use nym_sphinx::addressing::clients::Recipient; use nym_task::connections::{ConnectionCommandSender, LaneQueueLengths}; use nym_task::TaskClient; -use proxy_helpers::connection_controller::Controller; use std::net::SocketAddr; use tap::TapFallible; use tokio::net::TcpListener; diff --git a/clients/socks5/src/socks/types.rs b/clients/socks5/src/socks/types.rs index b7f7023115..964377bdc6 100644 --- a/clients/socks5/src/socks/types.rs +++ b/clients/socks5/src/socks/types.rs @@ -1,4 +1,4 @@ -use socks5_requests::Socks5RequestError; +use nym_socks5_requests::Socks5RequestError; use std::string::FromUtf8Error; use thiserror::Error; diff --git a/clients/validator/.eslintrc.json b/clients/validator/.eslintrc.json index 985f191ff5..1069570078 100644 --- a/clients/validator/.eslintrc.json +++ b/clients/validator/.eslintrc.json @@ -1,83 +1,71 @@ { - "root": true, - "env": { - "browser": true, - "es6": true, - "node": true - }, - "parserOptions": { - "ecmaVersion": 2019, - "sourceType": "module" - }, - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "plugins": ["prettier", "mocha"], - "extends": [ - "airbnb-base", - "airbnb-typescript/base", - "prettier"], - "rules": { - "prettier/prettier": "error", - "import/prefer-default-export": "off", - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.test.[jt]s", - "**/*.spec.[jt]s" - ] - } - ], - "import/extensions": [ - "error", - "ignorePackages", - { - "ts": "never", - "js": "never" - } - ] - }, - "overrides": [ - { - "files": "**/*.ts", - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json" - }, - "plugins": ["@typescript-eslint/eslint-plugin"], - "extends": [ - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], - "rules": { - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-var-requires": "off", - "no-use-before-define": [0], - "@typescript-eslint/no-use-before-define": [1], - "import/no-unresolved": 0, - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.test.ts", - "**/*.spec.ts" - ] - } - ], - "quotes": "off", - "@typescript-eslint/quotes": [ - 2, - "single", - { - "avoidEscape": true - } - ], - "@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }] - } - } + "root": true, + "env": { + "browser": true, + "es6": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 2019, + "sourceType": "module" + }, + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "plugins": ["prettier", "mocha"], + "extends": ["airbnb-base", "airbnb-typescript/base", "prettier"], + "rules": { + "prettier/prettier": "error", + "import/prefer-default-export": "off", + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": ["**/*.test.[jt]s", "**/*.spec.[jt]s"] + } + ], + "import/extensions": [ + "error", + "ignorePackages", + { + "ts": "never", + "js": "never" + } ] + }, + "overrides": [ + { + "files": "**/*.ts", + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json" + }, + "plugins": ["@typescript-eslint/eslint-plugin"], + "extends": ["plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "prettier"], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-var-requires": "off", + "no-use-before-define": [0], + "@typescript-eslint/no-use-before-define": [1], + "import/no-unresolved": 0, + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": ["**/*.test.ts", "**/*.spec.ts"] + } + ], + "quotes": "off", + "@typescript-eslint/quotes": [ + 2, + "single", + { + "avoidEscape": true + } + ], + "@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }] + } + } + ], + "ignorePatterns": ["tsconfig.json", "*.d.ts", "dist/**/*", "dist", "node_modules"] } diff --git a/clients/validator/.gitignore b/clients/validator/.gitignore index 5a4946114d..ee82866c85 100644 --- a/clients/validator/.gitignore +++ b/clients/validator/.gitignore @@ -4,4 +4,5 @@ coverage dist docs examples/accounts -node_modules \ No newline at end of file +node_modules +.env \ No newline at end of file diff --git a/clients/validator/.npmignore b/clients/validator/.npmignore index 62ab11e57c..298aabc724 100644 --- a/clients/validator/.npmignore +++ b/clients/validator/.npmignore @@ -1,3 +1,5 @@ coverage node_modules -tests \ No newline at end of file +tests +src +type diff --git a/clients/validator/README.md b/clients/validator/README.md index d54ebe7c77..41c1ae73af 100644 --- a/clients/validator/README.md +++ b/clients/validator/README.md @@ -1,40 +1,39 @@ -Nym Validator Client -==================== +# Nym Validator Client (Typescript) -A TypeScript client for interacting with CosmWasm smart contracts in Nym validators. +A TypeScript client for interacting with CosmWasm smart contracts in Nym validators. -Running examples ------------------ - -With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client. - -Running tests -------------- +Include the Nym Validator in your project: ``` -npm test +yarn add @nymproject/nym-validator-client ``` -You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is: - +Connect to validator and make queries ``` -cargo watch -s "cd clients/validator && npm test" +import Validator from '@nymproject/nym-validator-client' + +const main = async () => { + + const client = await Validator.connectForQuery(rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom) + + client.getBalance(address) + +} + ``` -It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here. +Connect to validator for performing actions -Generating Documentation ------------------------- +``` +import Validator from '@nymproject/nym-validator-client' -You can generate docs by running `npm run docs`. Generated output will appear in the `docs` directory. +const main = async () => { -Packaging ------------------------- + const client = await Validator.connect(mnemonic, rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom) -If you're a Nym platform developer who's made changes to the client and wants to re-publish the package to NPM, here's how you do it: + const res = await client.send(address, [{ amount: '10000000', denom: 'unym' }]); -1. Bump the version number (use SemVer) -1. `npm run build` -1. `npm login` (if you haven't already) -1. `npm publish` \ No newline at end of file +} + +``` diff --git a/clients/validator/package.json b/clients/validator/package.json index 6179e11b19..47766a842b 100644 --- a/clients/validator/package.json +++ b/clients/validator/package.json @@ -4,15 +4,23 @@ "description": "A TypeScript client for interacting with smart contracts in Nym validators", "repository": "https://github.com/nymtech/nym", "main": "./dist/index.js", - "types": "dist/index.d.ts", + "types": "./dist/index.d.ts", "scripts": { - "build": "tsc", - "test": "ts-mocha tests/**/*.test.ts", + "build": "rollup -c ./rollup.config.mjs", + "build:types": "rollup-type-bundler --dist ./dist/nym-validator-client", + "build:prod": "sh ./scripts/build-prod.sh", + "test": "ts-mocha -p ./tsconfig.test.json ./src/tests/**/*.test.ts", + "testmock": "ts-mocha -p ./tsconfig.test.json ./src/tests/mock/*.test.ts", "coverage": "nyc npm test", - "lint": "eslint src", - "lint:fix": "eslint src --fix", + "clean": "rm -rf ./dist", + "lint": "eslint", + "lint:fix": "eslint --fix", + "lint:tsc": "tsc --noEmit", "docs": "typedoc --out docs src/index.ts" }, + "files": [ + "./dist/*" + ], "keywords": [], "author": "Nym Technologies SA (https://nymtech.net)", "contributors": [ @@ -21,6 +29,14 @@ ], "license": "Apache-2.0", "devDependencies": { + "@favware/rollup-type-bundler": "^2.0.0", + "@nymproject/types": "^1.0.0", + "@rollup/plugin-commonjs": "^24.0.1", + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-typescript": "^11.0.0", + "@rollup/plugin-node-resolve": "^15.0.1", + "rollup": "^3.17.2", + "rollup-plugin-dts": "^5.2.0", "@typescript-eslint/eslint-plugin": "^5.7.0", "@typescript-eslint/parser": "^5.7.0", "eslint": "^7.18.0", @@ -31,21 +47,21 @@ "eslint-plugin-import": "^2.25.4", "eslint-plugin-mocha": "^10.0.3", "eslint-plugin-prettier": "^4.0.0", - "expect": "^28.1.3", "mocha": "^10.0.0", "prettier": "^2.5.1", - "typedoc": "^0.22.13", "ts-mocha": "^10.0.0", - "typescript": "^4.6.2" - }, - "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.28.0", - "@cosmjs/crypto": "^0.28.0", - "@cosmjs/math": "^0.28.0", - "@cosmjs/proto-signing": "^0.28.0", - "@cosmjs/stargate": "^0.28.0", - "@cosmjs/tendermint-rpc": "^0.28.0", - "axios": "^0.26.1", - "cosmjs-types": "^0.4.1" + "typedoc": "^0.22.13", + "typescript": "^4.6.2", + "cosmjs-types": "^0.4.1", + "dotenv": "^16.0.3", + "expect": "^28.1.3", + "moq.ts": "^7.3.4", + "@cosmjs/cosmwasm-stargate": "^0.29.5", + "@cosmjs/crypto": "^0.29.5", + "@cosmjs/math": "^0.29.5", + "@cosmjs/proto-signing": "^0.29.5", + "@cosmjs/stargate": "^0.29.5", + "@cosmjs/tendermint-rpc": "^0.29.5", + "axios": "^1.3.3" } } diff --git a/clients/validator/rollup.config.mjs b/clients/validator/rollup.config.mjs new file mode 100644 index 0000000000..f78cf449cf --- /dev/null +++ b/clients/validator/rollup.config.mjs @@ -0,0 +1,15 @@ +import typescript from '@rollup/plugin-typescript'; +import resolve from '@rollup/plugin-node-resolve'; +import json from '@rollup/plugin-json'; +import commonjs from '@rollup/plugin-commonjs'; + +export default [ + { + input: './src/index.ts', + output: { + dir: 'dist/nym-validator-client', + format: 'cjs', + }, + plugins: [resolve(), typescript(), commonjs(), json()], + }, +]; diff --git a/clients/validator/scripts/build-prod.sh b/clients/validator/scripts/build-prod.sh new file mode 100644 index 0000000000..06188652b0 --- /dev/null +++ b/clients/validator/scripts/build-prod.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset + +rm -rf ./dist || true +rm -rf ../../dist || true + + +# Bundle application + +yarn build + +# Bundle types + +yarn build:types + +# Build package.json for bundle + +node ./scripts/buildPackageJson.mjs + +# Copy README + +cp README.md dist/nym-validator-client + +# move the output outside of the yarn/npm workspaces + +mv ./dist ../../ + +echo "Output can be found in:" +realpath ../../dist \ No newline at end of file diff --git a/clients/validator/scripts/buildPackageJson.mjs b/clients/validator/scripts/buildPackageJson.mjs new file mode 100644 index 0000000000..da95f10e9d --- /dev/null +++ b/clients/validator/scripts/buildPackageJson.mjs @@ -0,0 +1,20 @@ +import * as fs from 'fs'; + +// parse the package.json from the SDK, so we can keep fields like the name and version +const json = JSON.parse(fs.readFileSync('./package.json').toString()); + +// defaults (NB: these are in the output file locations) +const main = 'index.js'; +const types = 'index.d.ts'; + +// make a package.json for the bundle +const packageJson = { + name: json.name, + version: json.version, + license: json.license, + author: json.author, + main, + types, +}; + +fs.writeFileSync('./dist/nym-validator-client/package.json', JSON.stringify(packageJson, null, 2)); diff --git a/clients/validator/src/currency.ts b/clients/validator/src/currency.ts index 483d13b408..216905f8b1 100644 --- a/clients/validator/src/currency.ts +++ b/clients/validator/src/currency.ts @@ -1,5 +1,6 @@ import { Decimal } from '@cosmjs/math'; import { Coin } from '@cosmjs/stargate'; +import { CoinMap } from './types/shared'; // NARROW NO-BREAK SPACE (U+202F) const thinSpace = '\u202F'; @@ -34,15 +35,6 @@ export function nativeToPrintable(nativeValue: string): string { return Decimal.fromAtomics(nativeValue, 6).toString(); } -export interface MappedCoin { - readonly denom: string; - readonly fractionalDigits: number; -} - -export interface CoinMap { - readonly [key: string]: MappedCoin; -} - export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin { if (!coinMap) return coin; diff --git a/clients/validator/src/index.ts b/clients/validator/src/index.ts index dac29371c3..e11d24525f 100644 --- a/clients/validator/src/index.ts +++ b/clients/validator/src/index.ts @@ -1,6 +1,3 @@ -import { Bip39, Random } from '@cosmjs/crypto'; -import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing'; -import { coin as cosmosCoin, Coin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate'; import { ExecuteResult, InstantiateOptions, @@ -8,45 +5,37 @@ import { MigrateResult, UploadResult, } from '@cosmjs/cosmwasm-stargate'; -import SigningClient, { ISigningClient } from './signing-client'; +import { Bip39, Random } from '@cosmjs/crypto'; +import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing'; +import { Coin, coin as cosmosCoin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate'; import { ContractStateParams, Delegation, Gateway, GatewayBond, + GatewayOwnershipResponse, + LayerDistribution, MixnetContractVersion, MixNode, MixNodeBond, + MixNodeCostParams, + MixNodeDetails, + MixNodeRewarding, + MixOwnershipResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, -} from './types'; -import { - CoinMap, - displayAmountToNative, - MappedCoin, - nativeCoinToDisplay, - nativeToPrintable, - printableBalance, - printableCoin, -} from './currency'; + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + PagedUnbondedMixnodesResponse, + RewardingParams, + StakeSaturationResponse, + UnbondedMixnodeResponse, +} from '@nymproject/types'; import QueryClient from './query-client'; -import { nymGasPrice } from './stargate-helper'; - -export { coins, coin } from '@cosmjs/stargate'; -export { Coin }; -export { - displayAmountToNative, - nativeCoinToDisplay, - printableCoin, - printableBalance, - nativeToPrintable, - MappedCoin, - CoinMap, -}; -export { nymGasPrice }; +import SigningClient, { ISigningClient } from './signing-client'; +import { ContractState } from './types/shared'; export interface INymClient { readonly mixnetContract: string; @@ -147,7 +136,7 @@ export default class ValidatorClient implements INymClient { return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions); } - getBalance(address: string): Promise { + async getBalance(address: string): Promise { return this.client.getBalance(address, this.denom); } @@ -159,15 +148,39 @@ export default class ValidatorClient implements INymClient { return this.client.getCachedMixnodes(); } - async getActiveMixnodes(): Promise { + async getStakeSaturation(mixId: number): Promise { + return this.client.getStakeSaturation(this.mixnetContract, mixId); + } + + async getActiveMixnodes(): Promise { return this.client.getActiveMixnodes(); } + async getUnbondedMixNodeInformation(mixId: number): Promise { + return this.client.getUnbondedMixNodeInformation(this.mixnetContract, mixId); + } + async getRewardedMixnodes(): Promise { return this.client.getRewardedMixnodes(); } - public async getMixnetContractSettings(): Promise { + async getMixnodeRewardingDetails(mixId: number): Promise { + return this.client.getMixnodeRewardingDetails(this.mixnetContract, mixId); + } + + async getOwnedMixnode(address: string): Promise { + return this.client.getOwnedMixnode(this.mixnetContract, address); + } + + async ownsGateway(address: string): Promise { + return this.client.ownsGateway(this.mixnetContract, address); + } + + async getLayerDistribution(): Promise { + return this.client.getLayerDistribution(this.mixnetContract); + } + + public async getMixnetContractSettings(): Promise { return this.client.getStateParams(this.mixnetContract); } @@ -175,29 +188,74 @@ export default class ValidatorClient implements INymClient { return this.client.getContractVersion(this.mixnetContract); } - public async getRewardPool(): Promise { - return this.client.getRewardPool(this.mixnetContract); + public async getVestingContractVersion(): Promise { + return this.client.getContractVersion(this.vestingContract); } - public async getCirculatingSupply(): Promise { - return this.client.getCirculatingSupply(this.mixnetContract); + public async getSpendableCoins(vestingAccountAddress: string): Promise { + return this.client.getSpendableCoins(this.vestingContract, vestingAccountAddress); } - public async getSybilResistancePercent(): Promise { - return this.client.getSybilResistancePercent(this.mixnetContract); + public async getRewardParams(): Promise { + return this.client.getRewardParams(this.mixnetContract); } - public async getIntervalRewardPercent(): Promise { - return this.client.getIntervalRewardPercent(this.mixnetContract); + async getUnbondedMixNodes(): Promise { + let mixNodes: UnbondedMixnodeResponse[] = []; + const limit = 50; + let startAfter; + for (;;) { + // eslint-disable-next-line no-await-in-loop + const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes( + this.mixnetContract, + limit, + startAfter, + ); + + mixNodes = mixNodes.concat(pagedResponse.nodes); + startAfter = pagedResponse.start_next_after; + // if `start_next_after` is not set, we're done + if (!startAfter) { + break; + } + } + + return mixNodes; } - public async getAllNyxdMixnodes(): Promise { + public async getMixNodeBonds(): Promise { let mixNodes: MixNodeBond[] = []; const limit = 50; let startAfter; - for (; ;) { + for (;;) { // eslint-disable-next-line no-await-in-loop - const pagedResponse: PagedMixnodeResponse = await this.client.getMixNodesPaged(this.mixnetContract, limit); + const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds( + this.mixnetContract, + limit, + startAfter, + ); + mixNodes = mixNodes.concat(pagedResponse.nodes); + startAfter = pagedResponse.start_next_after; + // if `start_next_after` is not set, we're done + if (!startAfter) { + break; + } + } + + return mixNodes; + } + + public async getMixNodesDetailed(): Promise { + let mixNodes: MixNodeDetails[] = []; + const limit = 50; + let startAfter; + for (;;) { + // eslint-disable-next-line no-await-in-loop + const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed( + this.mixnetContract, + limit, + startAfter, + ); mixNodes = mixNodes.concat(pagedResponse.nodes); startAfter = pagedResponse.start_next_after; // if `start_next_after` is not set, we're done @@ -210,37 +268,24 @@ export default class ValidatorClient implements INymClient { } public async getAllNyxdGateways(): Promise { - let gateways: GatewayBond[] = []; - const limit = 50; - let startAfter; - for (; ;) { - // eslint-disable-next-line no-await-in-loop - const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract, limit); - gateways = gateways.concat(pagedResponse.nodes); - startAfter = pagedResponse.start_next_after; - // if `start_next_after` is not set, we're done - if (!startAfter) { - break; - } - } - - return gateways; + const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract); + return pagedResponse.nodes; } /** * Gets list of all delegations towards particular mixnode. * - * @param mixIdentity identity of the node to which the delegation was sent + * @param mix_id identity of the node to which the delegation was sent */ - public async getAllNyxdSingleMixnodeDelegations(mixIdentity: string): Promise { + public async getAllNyxdSingleMixnodeDelegations(mix_id: number): Promise { let delegations: Delegation[] = []; const limit = 250; let startAfter; - for (; ;) { + for (;;) { // eslint-disable-next-line no-await-in-loop const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged( this.mixnetContract, - mixIdentity, + mix_id, limit, startAfter, ); @@ -259,7 +304,7 @@ export default class ValidatorClient implements INymClient { let delegations: Delegation[] = []; const limit = 250; let startAfter; - for (; ;) { + for (;;) { // eslint-disable-next-line no-await-in-loop const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged( this.mixnetContract, @@ -278,13 +323,13 @@ export default class ValidatorClient implements INymClient { return delegations; } - public async getAllNyxdNetworkDelegations(): Promise { + public async getAllNyxdDelegations(): Promise { let delegations: Delegation[] = []; const limit = 250; let startAfter; - for (; ;) { + for (;;) { // eslint-disable-next-line no-await-in-loop - const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllNetworkDelegationsPaged( + const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged( this.mixnetContract, limit, startAfter, @@ -300,6 +345,10 @@ export default class ValidatorClient implements INymClient { return delegations; } + public async getDelegationDetails(mix_id: number, delegator: string): Promise { + return this.client.getDelegationDetails(this.mixnetContract, mix_id, delegator); + } + /** * Generate a minimum gateway bond required to create a fresh mixnode. * @@ -308,7 +357,7 @@ export default class ValidatorClient implements INymClient { public async minimumMixnodePledge(): Promise { const stateParams = await this.getMixnetContractSettings(); // we trust the contract to return a valid number - return cosmosCoin(stateParams.minimum_mixnode_pledge, this.prefix); + return cosmosCoin(stateParams.params.minimum_mixnode_pledge, this.prefix); } /** @@ -319,7 +368,7 @@ export default class ValidatorClient implements INymClient { public async minimumGatewayPledge(): Promise { const stateParams = await this.getMixnetContractSettings(); // we trust the contract to return a valid number - return cosmosCoin(stateParams.minimum_gateway_pledge, this.prefix); + return cosmosCoin(stateParams.params.minimum_gateway_pledge, this.prefix); } public async send( @@ -393,12 +442,21 @@ export default class ValidatorClient implements INymClient { public async bondMixNode( mixNode: MixNode, ownerSignature: string, + costParams: MixNodeCostParams, pledge: Coin, fee?: StdFee | 'auto' | number, memo?: string, ): Promise { this.assertSigning(); - return (this.client as ISigningClient).bondMixNode(this.mixnetContract, mixNode, ownerSignature, pledge, fee, memo); + return (this.client as ISigningClient).bondMixNode( + this.mixnetContract, + mixNode, + costParams, + ownerSignature, + pledge, + fee, + memo, + ); } public async unbondMixNode(fee?: StdFee | 'auto' | number, memo?: string): Promise { @@ -423,29 +481,29 @@ export default class ValidatorClient implements INymClient { } public async delegateToMixNode( - mixIdentity: string, + mixId: number, amount: Coin, fee?: StdFee | 'auto' | number, memo?: string, ): Promise { this.assertSigning(); - return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixIdentity, amount, fee, memo); + return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixId, amount, fee, memo); } public async undelegateFromMixNode( - mixIdentity: string, + mixId: number, fee?: StdFee | 'auto' | number, memo?: string, ): Promise { - return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixIdentity, fee, memo); + return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixId, fee, memo); } public async updateMixnodeConfig( - mixIdentity: string, + mixId: number, fee: StdFee | 'auto' | number, profitPercentage: number, ): Promise { - return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixIdentity, profitPercentage, fee); + return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixId, profitPercentage, fee); } public async updateContractStateParams( diff --git a/clients/validator/src/nym-api-querier.ts b/clients/validator/src/nym-api-querier.ts index 3fa0471804..e66c93c41f 100644 --- a/clients/validator/src/nym-api-querier.ts +++ b/clients/validator/src/nym-api-querier.ts @@ -2,9 +2,8 @@ * Copyright 2021 - Nym Technologies SA * SPDX-License-Identifier: Apache-2.0 */ - import axios from 'axios'; -import { GatewayBond, MixNodeBond } from './types'; +import { GatewayBond, MixNodeBond, MixNodeDetails } from '@nymproject/types'; export const NYM_API_VERSION = '/v1'; export const NYM_API_GATEWAYS_PATH = `${NYM_API_VERSION}/gateways`; @@ -17,7 +16,7 @@ export interface INymApiQuery { getCachedGateways(): Promise; - getActiveMixnodes(): Promise; + getActiveMixnodes(): Promise; getRewardedMixnodes(): Promise; } @@ -51,7 +50,7 @@ export default class NymApiQuerier implements INymApiQuery { throw new Error('None of the provided validator APIs seem to be alive'); } - async getActiveMixnodes(): Promise { + async getActiveMixnodes(): Promise { const url = new URL(this.nymApiUrl); url.pathname += NYM_API_ACTIVE_MIXNODES_PATH; diff --git a/clients/validator/src/nyxd-querier.ts b/clients/validator/src/nyxd-querier.ts index edd791a8fe..0e34493c2e 100644 --- a/clients/validator/src/nyxd-querier.ts +++ b/clients/validator/src/nyxd-querier.ts @@ -2,28 +2,24 @@ * Copyright 2021 - Nym Technologies SA * SPDX-License-Identifier: Apache-2.0 */ - -import { JsonObject } from '@cosmjs/cosmwasm-stargate'; // eslint-disable-next-line import/no-cycle import { INyxdQuery } from './query-client'; +import { Delegation, RewardingParams, StakeSaturationResponse } from '@nymproject/types'; import { - ContractStateParams, - Delegation, + UnbondedMixnodeResponse, GatewayOwnershipResponse, - LayerDistribution, MixnetContractVersion, MixOwnershipResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, - RewardingStatus, -} from './types'; - -interface SmartContractQuery { - queryContractSmart(address: string, queryMsg: Record): Promise; -} + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + PagedUnbondedMixnodesResponse, + LayerDistribution, +} from '@nymproject/types'; +import { ContractState, SmartContractQuery } from './types/shared'; export default class NyxdQuerier implements INyxdQuery { client: SmartContractQuery; @@ -38,15 +34,44 @@ export default class NyxdQuerier implements INyxdQuery { }); } - getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - get_mix_nodes: { + get_mix_node_bonds: { limit, start_after: startAfter, }, }); } + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_mix_nodes_detailed: { + limit, + start_after: startAfter, + }, + }); + } + + getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_stake_saturation: { mix_id: mixId }, + }); + } + + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_mixnode_rewarding_details: { mix_id: mixId }, + }); + } + getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { get_gateways: { @@ -56,35 +81,51 @@ export default class NyxdQuerier implements INyxdQuery { }); } - ownsMixNode(mixnetContractAddress: string, address: string): Promise { + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - owns_mixnode: { + get_owned_mixnode: { address, }, }); } + getUnbondedMixNodes( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_unbonded_mix_nodes: { limit, start_after: startAfter }, + }); + } + + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_unbonded_mix_node_information: { mix_id: mixId }, + }); + } + ownsGateway(mixnetContractAddress: string, address: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - owns_gateway: { + get_owned_gateway: { address, }, }); } - getStateParams(mixnetContractAddress: string): Promise { + getStateParams(mixnetContractAddress: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - state_params: {}, + get_state: {}, }); } - getAllNetworkDelegationsPaged( + getAllDelegationsPaged( mixnetContractAddress: string, limit?: number, startAfter?: [string, string], ): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - get_all_network_delegations: { + get_all_delegations: { start_after: startAfter, limit, }, @@ -93,13 +134,13 @@ export default class NyxdQuerier implements INyxdQuery { getMixNodeDelegationsPaged( mixnetContractAddress: string, - mixIdentity: string, + mix_id: number, limit?: number, startAfter?: string, ): Promise { return this.client.queryContractSmart(mixnetContractAddress, { get_mixnode_delegations: { - mix_identity: mixIdentity, + mix_id: mix_id, start_after: startAfter, limit, }, @@ -121,10 +162,10 @@ export default class NyxdQuerier implements INyxdQuery { }); } - getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise { + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { get_delegation_details: { - mix_identity: mixIdentity, + mix_id: mix_id, delegator, }, }); @@ -132,44 +173,19 @@ export default class NyxdQuerier implements INyxdQuery { getLayerDistribution(mixnetContractAddress: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - layer_distribution: {}, + get_layer_distribution: {}, }); } - getRewardPool(mixnetContractAddress: string): Promise { + getRewardParams(mixnetContractAddress: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - get_reward_pool: {}, + get_rewarding_params: {}, }); } - getCirculatingSupply(mixnetContractAddress: string): Promise { - return this.client.queryContractSmart(mixnetContractAddress, { - get_circulating_supply: {}, - }); - } - - getIntervalRewardPercent(mixnetContractAddress: string): Promise { - return this.client.queryContractSmart(mixnetContractAddress, { - get_interval_reward_percent: {}, - }); - } - - getSybilResistancePercent(mixnetContractAddress: string): Promise { - return this.client.queryContractSmart(mixnetContractAddress, { - get_sybil_resistance_percent: {}, - }); - } - - getRewardingStatus( - mixnetContractAddress: string, - mixIdentity: string, - rewardingIntervalNonce: number, - ): Promise { - return this.client.queryContractSmart(mixnetContractAddress, { - get_rewarding_status: { - mix_identity: mixIdentity, - rewarding_interval_nonce: rewardingIntervalNonce, - }, + getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise { + return this.client.queryContractSmart(vestingContractAddress, { + vesting_account_address: vestingAccountAddress, }); } } diff --git a/clients/validator/src/query-client.ts b/clients/validator/src/query-client.ts index c78237cdb4..319c46f80e 100644 --- a/clients/validator/src/query-client.ts +++ b/clients/validator/src/query-client.ts @@ -1,75 +1,56 @@ -import { CosmWasmClient, JsonObject } from '@cosmjs/cosmwasm-stargate'; -import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; -import { - Account, - Block, - Coin, - DeliverTxResponse, - IndexedTx, - SearchTxFilter, - SearchTxQuery, - SequenceResponse, -} from '@cosmjs/stargate'; -import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient'; +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; // eslint-disable-next-line import/no-cycle import NyxdQuerier from './nyxd-querier'; import { - ContractStateParams, Delegation, GatewayBond, GatewayOwnershipResponse, LayerDistribution, MixnetContractVersion, - MixNodeBond, + MixNodeDetails, MixOwnershipResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, - RewardingStatus, -} from './types'; -import NymApiQuerier, { INymApiQuery as INymApiQuery } from './nym-api-querier'; - -export interface ICosmWasmQuery { - // methods exposed by `CosmWasmClient` - getChainId(): Promise; - getHeight(): Promise; - getAccount(searchAddress: string): Promise; - getSequence(address: string): Promise; - getBlock(height?: number): Promise; - getBalance(address: string, searchDenom: string): Promise; - getTx(id: string): Promise; - searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; - disconnect(): void; - broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise; - getCodes(): Promise; - getCodeDetails(codeId: number): Promise; - getContracts(codeId: number): Promise; - getContract(address: string): Promise; - getContractCodeHistory(address: string): Promise; - queryContractRaw(address: string, key: Uint8Array): Promise; - queryContractSmart(address: string, queryMsg: Record): Promise; -} + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + PagedUnbondedMixnodesResponse, + StakeSaturationResponse, + UnbondedMixnodeResponse, + MixNodeBond, + MixNodeRewarding, +} from '@nymproject/types'; +import NymApiQuerier, { INymApiQuery } from './nym-api-querier'; +import { ContractState, ICosmWasmQuery } from './types/shared'; +import { RewardingParams } from '@nymproject/types'; +import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; export interface INyxdQuery { // nym-specific implemented inside NymQuerier getContractVersion(mixnetContractAddress: string): Promise; - - getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise; + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise; + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise; getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise; - ownsMixNode(mixnetContractAddress: string, address: string): Promise; + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise; ownsGateway(mixnetContractAddress: string, address: string): Promise; - getStateParams(mixnetContractAddress: string): Promise; - - getAllNetworkDelegationsPaged( + getStateParams(mixnetContractAddress: string): Promise; + getAllDelegationsPaged( mixnetContractAddress: string, limit?: number, startAfter?: [string, string], ): Promise; getMixNodeDelegationsPaged( mixnetContractAddress: string, - mixIdentity: string, + mix_id: number, limit?: number, startAfter?: string, ): Promise; @@ -79,21 +60,15 @@ export interface INyxdQuery { limit?: number, startAfter?: string, ): Promise; - getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise; - + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise; getLayerDistribution(mixnetContractAddress: string): Promise; - getRewardPool(mixnetContractAddress: string): Promise; - getCirculatingSupply(mixnetContractAddress: string): Promise; - getIntervalRewardPercent(mixnetContractAddress: string): Promise; - getSybilResistancePercent(mixnetContractAddress: string): Promise; - getRewardingStatus( - mixnetContractAddress: string, - mixIdentity: string, - rewardingIntervalNonce: number, - ): Promise; + getRewardParams(mixnetContractAddress: string): Promise; + getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise; + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise; + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise; } -export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery { } +export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery {} export default class QueryClient extends CosmWasmClient implements IQueryClient { private nyxdQuerier: NyxdQuerier; @@ -115,41 +90,73 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient return this.nyxdQuerier.getContractVersion(mixnetContractAddress); } - getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { - return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter); + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter); + } + + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter); + } + + getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId); + } + + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId); } getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter); } - ownsMixNode(mixnetContractAddress: string, address: string): Promise { - return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address); + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise { + return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address); } ownsGateway(mixnetContractAddress: string, address: string): Promise { return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address); } - getStateParams(mixnetContractAddress: string): Promise { + getUnbondedMixNodes( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter); + } + + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId); + } + + getStateParams(mixnetContractAddress: string): Promise { return this.nyxdQuerier.getStateParams(mixnetContractAddress); } - getAllNetworkDelegationsPaged( + getAllDelegationsPaged( mixnetContractAddress: string, limit?: number, startAfter?: [string, string], ): Promise { - return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter); + return this.nyxdQuerier.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter); } getMixNodeDelegationsPaged( mixnetContractAddress: string, - mixIdentity: string, + mix_id: number, limit?: number, startAfter?: string, ): Promise { - return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter); + return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter); } getDelegatorDelegationsPaged( @@ -161,36 +168,16 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter); } - getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise { - return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator); + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise { + return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator); } getLayerDistribution(mixnetContractAddress: string): Promise { return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress); } - getRewardPool(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getRewardPool(mixnetContractAddress); - } - - getCirculatingSupply(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress); - } - - getIntervalRewardPercent(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress); - } - - getSybilResistancePercent(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress); - } - - getRewardingStatus( - mixnetContractAddress: string, - mixIdentity: string, - rewardingIntervalNonce: number, - ): Promise { - return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce); + getRewardParams(mixnetContractAddress: string): Promise { + return this.nyxdQuerier.getRewardParams(mixnetContractAddress); } getCachedGateways(): Promise { @@ -201,11 +188,15 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient return this.nymApiQuerier.getCachedMixnodes(); } - getActiveMixnodes(): Promise { + getActiveMixnodes(): Promise { return this.nymApiQuerier.getActiveMixnodes(); } getRewardedMixnodes(): Promise { return this.nymApiQuerier.getRewardedMixnodes(); } + + getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise { + return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress); + } } diff --git a/clients/validator/src/signing-client.ts b/clients/validator/src/signing-client.ts index 8cb589cd0e..d750eefcd2 100644 --- a/clients/validator/src/signing-client.ts +++ b/clients/validator/src/signing-client.ts @@ -25,15 +25,22 @@ import { MixnetContractVersion, MixNode, MixNodeBond, + MixNodeCostParams, + MixNodeDetails, + MixNodeRewarding, MixOwnershipResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, - RewardingStatus, -} from './types'; + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + PagedUnbondedMixnodesResponse, + RewardingParams, + UnbondedMixnodeResponse, +} from '@nymproject/types'; import NymApiQuerier from './nym-api-querier'; +import { ContractState } from './types/shared'; // methods exposed by `SigningCosmWasmClient` export interface ICosmWasmSigning { @@ -143,6 +150,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign bondMixNode( mixnetContractAddress: string, mixNode: MixNode, + costParams: MixNodeCostParams, ownerSignature: string, pledge: Coin, fee?: StdFee | 'auto' | number, @@ -164,7 +172,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign delegateToMixNode( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, amount: Coin, fee?: StdFee | 'auto' | number, memo?: string, @@ -172,14 +180,14 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign undelegateFromMixNode( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, fee?: StdFee | 'auto' | number, memo?: string, ): Promise; updateMixnodeConfig( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, profitMarginPercent: number, fee: StdFee | 'auto' | number, ): Promise; @@ -235,44 +243,76 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig // query related: getContractVersion(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getContractVersion(mixnetContractAddress); + return this.getContractVersion(mixnetContractAddress); } - getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { - return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter); + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter); + } + + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter); + } + + getStakeSaturation(mixnetContractAddress: string, mixId: number) { + return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId); + } + + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId); + } + + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId); } getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter); } - ownsMixNode(mixnetContractAddress: string, address: string): Promise { - return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address); + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise { + return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address); } ownsGateway(mixnetContractAddress: string, address: string): Promise { return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address); } - getStateParams(mixnetContractAddress: string): Promise { + getStateParams(mixnetContractAddress: string): Promise { return this.nyxdQuerier.getStateParams(mixnetContractAddress); } - getAllNetworkDelegationsPaged( + getAllDelegationsPaged( mixnetContractAddress: string, limit?: number, startAfter?: [string, string], ): Promise { - return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter); + return this.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter); + } + + getUnbondedMixNodes( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter); } getMixNodeDelegationsPaged( mixnetContractAddress: string, - mixIdentity: string, + mix_id: number, limit?: number, startAfter?: string, ): Promise { - return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter); + return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter); } getDelegatorDelegationsPaged( @@ -284,36 +324,16 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter); } - getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise { - return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator); + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise { + return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator); } getLayerDistribution(mixnetContractAddress: string): Promise { return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress); } - getRewardPool(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getRewardPool(mixnetContractAddress); - } - - getCirculatingSupply(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress); - } - - getIntervalRewardPercent(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress); - } - - getSybilResistancePercent(mixnetContractAddress: string): Promise { - return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress); - } - - getRewardingStatus( - mixnetContractAddress: string, - mixIdentity: string, - rewardingIntervalNonce: number, - ): Promise { - return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce); + getRewardParams(mixnetContractAddress: string): Promise { + return this.nyxdQuerier.getRewardParams(mixnetContractAddress); } getCachedGateways(): Promise { @@ -324,7 +344,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig return this.nymApiQuerier.getCachedMixnodes(); } - getActiveMixnodes(): Promise { + getActiveMixnodes(): Promise { return this.nymApiQuerier.getActiveMixnodes(); } @@ -332,11 +352,16 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig return this.nymApiQuerier.getRewardedMixnodes(); } + getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise { + return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress); + } + // signing related: bondMixNode( mixnetContractAddress: string, mixNode: MixNode, + costParams: MixNodeCostParams, ownerSignature: string, pledge: Coin, fee: StdFee | 'auto' | number = 'auto', @@ -348,6 +373,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig { bond_mixnode: { mix_node: mixNode, + cost_params: costParams, owner_signature: ownerSignature, }, }, @@ -414,7 +440,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig delegateToMixNode( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, amount: Coin, fee: StdFee | 'auto' | number = 'auto', memo = 'Default MixNode Delegation from Typescript', @@ -424,7 +450,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig mixnetContractAddress, { delegate_to_mixnode: { - mix_identity: mixIdentity, + mix_id: mixId, }, }, fee, @@ -435,7 +461,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig undelegateFromMixNode( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, fee: StdFee | 'auto' | number = 'auto', memo = 'Default MixNode Undelegation from Typescript', ): Promise { @@ -444,7 +470,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig mixnetContractAddress, { undelegate_from_mixnode: { - mix_identity: mixIdentity, + mix_id: mixId, }, }, fee, @@ -454,14 +480,14 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig updateMixnodeConfig( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, profitMarginPercent: number, fee: StdFee | 'auto' | number, ): Promise { return this.execute( this.clientAddress, mixnetContractAddress, - { update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_identity: mixIdentity } }, + { update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_id: mixId } }, fee, ); } diff --git a/clients/validator/src/tests/expectedResponses.ts b/clients/validator/src/tests/expectedResponses.ts new file mode 100644 index 0000000000..876f61e866 --- /dev/null +++ b/clients/validator/src/tests/expectedResponses.ts @@ -0,0 +1,178 @@ +import expect from 'expect'; + +export const amountDemon = { + amount: expect.any(String), + denom: expect.any(String) +} + +export const delegation = { + owner: expect.any(String), + mix_id: expect.any(Number), + cumulative_reward_ratio: expect.any(String), + amount: amountDemon, + height: expect.any(Number || BigInt), + proxy: expect.any(String || null) +} + +export const detailedDelegation = { + delegation: delegation, + mixnode_still_bonded: expect.any(Boolean) +} + +export const gateway = { + pledge_amount: amountDemon, + owner: expect.any(String), + block_height: expect.any(Number || BigInt), + gateway: { + host: expect.any(String), + mix_port: expect.any(Number), + clients_port: expect.any(Number), + location: expect.any(String), + sphinx_key: expect.any(String), + identity_key: expect.any(String), + version: expect.any(String), + }, + proxy: expect.any(String || null) +} + +export const pagedGateway = { + nodes: gateway, + per_page: expect.any(Number), + start_next_after: expect.any(Number) +} + +export const ownGateway = { + address: expect.any(String), + gateway: gateway +} + +export const rewardingdetails = { + cost_params: { + profit_margin_percent: expect.any(String), + interval_operating_cost: { + denom: expect.any(String), + amount: expect.any(String) + } + }, + operator: expect.any(String), + delegates: expect.any(String), + total_unit_reward: expect.any(String), + unit_delegation: expect.any(String), + last_rewarded_epoch: expect.any(Number), + unique_delegations: expect.any(Number) +} + +export const mix_node = { + host: expect.any(String), + mix_port: expect.any(Number), + verloc_port: expect.any(Number), + http_api_port: expect.any(Number), + sphinx_key: expect.any(String), + identity_key: expect.any(String), + version: expect.any(String) +} + +export const mixnodebond = { + mix_id: expect.any(Number), + owner: expect.any(String), + original_pledge: { + denom: expect.any(String), + amount: expect.any(String) + }, + layer: expect.any(String), + mix_node: mix_node, + proxy: expect.any(String) || null, + bonding_height: expect.any(Number || BigInt), + is_unbonding: expect.any(Boolean) +} + +export const mixnode = { + bond_information: mixnodebond, + rewarding_details: rewardingdetails +} + +export const ownedNode = { + address: expect.any(String), + mixnode_details: { + bond_information: mixnodebond, + rewarding_details: rewardingdetails + } +} + +export const saturation = { + mix_id: expect.any(Number), + current_saturation: expect.any(String), + uncapped_saturation: expect.any(String) +} + +export const contractVersion = { + build_timestamp: expect.any(String), + build_version: expect.any(String), + commit_sha: expect.any(String), + commit_timestamp: expect.any(String), + commit_branch: expect.any(String), + rustc_version: expect.any(String) +}; + +export const stateParams = { + minimum_gateway_pledge: amountDemon, + minimum_mixnode_pledge: expect.any(String), + mixnode_rewarded_set_size: expect.any(Number), + mixnode_active_set_size: expect.any(Number) +} + +export const contract = { + owner: expect.any(Number), + rewarding_validator_address: expect.any(Number), + vesting_contract_address: expect.any(Number), + rewarding_denom: expect.any(String), + params: stateParams +} + +export const rewardingnode = { + mix_id: expect.any(Number), + rewarding_details: rewardingdetails +} + +export const unbondednode = { + mix_id: expect.any(Number), + unbonded_info: { + identity_key: expect.any(String), + owner: expect.any(String), + proxy: expect.any(String) || null, + unbonding_height: expect.any(Number) + } +} + +export const allunbondednodes = [ + expect.any(Number), { + identity_key: expect.any(String), + owner: expect.any(String), + proxy: expect.any(String) || null, + unbonding_height: expect.any(Number) + } +] + +export const layerDistribution = { + layer1: expect.any(Number), + layer2: expect.any(Number), + layer3: expect.any(Number) +} + + +export const intervalRewardParams = { + reward_pool: expect.any(Number), + staking_supply: expect.any(Number), + staking_supply_scale_factor: expect.any(Number), + epoch_reward_budget: expect.any(Number), + stake_saturation_point: expect.any(Number), + sybil_resistance: expect.any(Number), + active_set_work_factor: expect.any(Number), + interval_pool_emission: expect.any(Number) +} + +export const rewardingParams = { + interval: intervalRewardParams, + rewarded_set_size: expect.any(Number), + active_set_size: expect.any(Number) +} diff --git a/clients/validator/src/tests/mock/client.ts b/clients/validator/src/tests/mock/client.ts new file mode 100644 index 0000000000..c5633d2ed0 --- /dev/null +++ b/clients/validator/src/tests/mock/client.ts @@ -0,0 +1,19 @@ +import { Mock, Times } from 'moq.ts'; +import expect from 'expect'; +import { INyxdQuery } from '../../query-client'; + +export class TestHelper { + buildMethod = async (methodName: string, args: any[], expectedResult: any): Promise => { + const client = new Mock() + .setup((nym) => nym[methodName](...args)) + .returns(Promise.resolve(expectedResult)); + const obj = client.object(); + const actualDetails = await obj[methodName](...args); + + client.verify((nym) => nym[methodName](...args), Times.Exactly(1)); + expect(Object.keys([actualDetails])).toEqual(Object.keys(expectedResult)); + expect(actualDetails).toBeDefined(); + + return actualDetails; + }; +} diff --git a/clients/validator/src/tests/mock/delegation.mock.test.ts b/clients/validator/src/tests/mock/delegation.mock.test.ts new file mode 100644 index 0000000000..69fffcc53b --- /dev/null +++ b/clients/validator/src/tests/mock/delegation.mock.test.ts @@ -0,0 +1,40 @@ +import expect from 'expect'; +import { Delegation } from '@nymproject/types'; +import { PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse } from '../../types/shared-types'; +import { TestHelper } from './client'; +import { mixnet, mixnodeowneraddress, mix_id } from './testData'; + +describe('Delegation mock tests', () => { + const testHelper = new TestHelper(); + + it('get Delegation Details', () => { + const execute = testHelper.buildMethod('getDelegationDetails', [mixnet, mix_id, mixnodeowneraddress], { + owner: mixnodeowneraddress, + mix_id: 0, + cumulative_reward_ratio: '', + amount: { + denom: 'nym', + amount: '10', + }, + height: 1314134144132n, + proxy: 'null', + }); + expect(execute).toBeTruthy(); + }); + + it('get All Delegations Paged', () => { + const execute = testHelper.buildMethod('getAllDelegationsPaged', [mixnet], { + delegations: [], + }); + expect(execute).toBeTruthy(); + }); + + it('get Delegator Delegations Paged', () => { + const execute = testHelper.buildMethod('getDelegatorDelegationsPaged', [mixnet, mixnodeowneraddress], < + PagedDelegatorDelegationsResponse + >{ + delegations: [], + }); + expect(execute).toBeTruthy(); + }); +}); diff --git a/clients/validator/src/tests/mock/gateway.mock.test.ts b/clients/validator/src/tests/mock/gateway.mock.test.ts new file mode 100644 index 0000000000..5c7f277a41 --- /dev/null +++ b/clients/validator/src/tests/mock/gateway.mock.test.ts @@ -0,0 +1,24 @@ +import expect from 'expect'; +import { GatewayOwnershipResponse, PagedGatewayResponse } from '../../types/shared-types'; +import { TestHelper } from './client'; +import { gatewayowneraddress, mixnet } from './testData'; + +describe('Gateway mock tests', () => { + const testHelper = new TestHelper(); + + it('get Gateways Paged', () => { + const execute = testHelper.buildMethod('getGatewaysPaged', [mixnet], { + nodes: [], + per_page: 25, + }); + expect(execute).toBeTruthy(); + }); + + it('owns Gateway', () => { + const execute = testHelper.buildMethod('ownsGateway', [mixnet, gatewayowneraddress], { + address: gatewayowneraddress, + gateway: {}, + }); + expect(execute).toBeTruthy(); + }); +}); diff --git a/clients/validator/src/tests/mock/mixnet.mock.test.ts b/clients/validator/src/tests/mock/mixnet.mock.test.ts new file mode 100644 index 0000000000..8d84d4eecc --- /dev/null +++ b/clients/validator/src/tests/mock/mixnet.mock.test.ts @@ -0,0 +1,65 @@ +import expect from 'expect'; +import { LayerDistribution, MixnetContractVersion, StakeSaturationResponse } from '@nymproject/types'; +import { TestHelper } from './client'; +import { mixnet, mix_id } from './testData'; +import { RewardingParams } from '@nymproject/types'; +import { ContractState } from '../../types/shared'; + +describe('Mixnet mock tests', () => { + const testHelper = new TestHelper(); + + it('get Layer Distribution', () => { + const execute = testHelper.buildMethod('getLayerDistribution', [mixnet], { + layer1: 2, + layer2: 2, + layer3: 5, + }); + expect(execute).toBeTruthy(); + }); + + it('get Reward Params', () => { + const execute = testHelper.buildMethod('getRewardParams', [mixnet], { + interval: {}, + rewarded_set_size: 0, + active_set_size: 0, + }); + expect(execute).toBeTruthy(); + }); + + it('get Stake Saturation', () => { + const execute = testHelper.buildMethod('getStakeSaturation', [mixnet, mix_id], { + mix_id: 0, + current_saturation: '', + uncapped_saturation: '', + }); + expect(execute).toBeTruthy(); + }); + + it('get State Params', () => { + const execute = testHelper.buildMethod('getStateParams', [mixnet], { + owner: '', + rewarding_validator_address: '', + vesting_contract_address: '', + rewarding_denom: 'unym', + params: { + minimum_mixnode_pledge: '', + minimum_gateway_pledge: '', + mixnode_rewarded_set_size: 240, + mixnode_active_set_size: 240, + }, + }); + expect(execute).toBeTruthy(); + }); + + it('get Contract Version', () => { + const execute = testHelper.buildMethod('getContractVersion', [mixnet], { + build_timestamp: 'test', + commit_branch: 'test', + build_version: 'test', + rustc_version: 'test', + commit_sha: 'test', + commit_timestamp: 'test', + }); + expect(execute).toBeTruthy(); + }); +}); diff --git a/clients/validator/src/tests/mock/mixnode.mock.test.ts b/clients/validator/src/tests/mock/mixnode.mock.test.ts new file mode 100644 index 0000000000..c4d9e4519b --- /dev/null +++ b/clients/validator/src/tests/mock/mixnode.mock.test.ts @@ -0,0 +1,71 @@ +import expect from 'expect'; +import { + MixNodeRewarding, + MixOwnershipResponse, + PagedMixDelegationsResponse, + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + UnbondedMixnodeResponse, +} from '@nymproject/types'; +import { TestHelper } from './client'; +import { mixnet, mixnodeowneraddress, mix_id } from './testData'; + +describe('Mixnode mock tests', () => { + const testHelper = new TestHelper(); + + it('get Mixnode Bonds', () => { + const execute = testHelper.buildMethod('getMixNodeBonds', [mixnet], { + nodes: [], + per_page: 25, + }); + expect(execute).toBeTruthy(); + }); + + it('get Mixnode Delegations Paged', () => { + const execute = testHelper.buildMethod('getMixNodeDelegationsPaged', [mixnet, mix_id], < + PagedMixDelegationsResponse + >{ + delegations: [], + per_page: 25, + }); + expect(execute).toBeTruthy(); + }); + + it('get Mixnodes Detailed', () => { + const execute = testHelper.buildMethod('getMixNodesDetailed', [mixnet], { + nodes: [], + per_page: 25, + }); + expect(execute).toBeTruthy(); + }); + + it('get Mixnode Rewarding Details', () => { + const execute = testHelper.buildMethod('getMixnodeRewardingDetails', [mixnet, mix_id], { + cost_params: {}, + operator: '', + delegates: '', + total_unit_reward: '', + unit_delegation: '', + last_rewarded_epoch: 1, + unique_delegations: 1, + }); + expect(execute).toBeTruthy(); + }); + + it('get Owned Mixnode', () => { + const execute = testHelper.buildMethod('getOwnedMixnode', [mixnet, mixnodeowneraddress], { + address: '', + mixnode: {}, + }); + expect(execute).toBeTruthy(); + }); + + it('get Unbonded Mixnode Information', () => { + const execute = testHelper.buildMethod( + 'getUnbondedMixNodeInformation', + [mixnet, mix_id], + {}, + ); + expect(execute).toBeTruthy(); + }); +}); diff --git a/clients/validator/src/tests/mock/testData.ts b/clients/validator/src/tests/mock/testData.ts new file mode 100644 index 0000000000..28df1557f7 --- /dev/null +++ b/clients/validator/src/tests/mock/testData.ts @@ -0,0 +1,8 @@ +export const mixnet = 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g'; +export const gatewayowneraddress = 'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts'; +// export const mixId = 436207616; +export const mix_id = 26; +export const nodeIdentityKey = 'ATmVJknZarDF6Yj53M7h8NS9LLCUvWuToXpk3pDvYUH1'; +export const mixnodeowneraddress = 'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47'; +export const delegatorAddress = 'n1lemst75va9700tsrxq58adzujrh6h9s5x60h9h'; +export const rewardingIntervalNonce = 1; diff --git a/clients/validator/src/tests/query/mixnetQueries.test.ts b/clients/validator/src/tests/query/mixnetQueries.test.ts new file mode 100644 index 0000000000..391f8aaf0d --- /dev/null +++ b/clients/validator/src/tests/query/mixnetQueries.test.ts @@ -0,0 +1,180 @@ +import expect from 'expect'; +import ValidatorClient from '../../index'; + +import { + allunbondednodes, + contract, + delegation, + gateway, + layerDistribution, + mixnode, + mixnodebond, + ownedNode, + ownGateway, + rewardingnode, + saturation, + unbondednode, +} from '../expectedResponses'; +import { delegatorAddress, gatewayowneraddress } from '../mock/testData'; + +const dotenv = require('dotenv'); + +dotenv.config(); + +describe('Mixnet queries', () => { + let client: ValidatorClient; + + beforeEach(async () => { + client = await ValidatorClient.connectForQuery( + process.env.rpcAddress || '', + process.env.validatorAddress || '', + process.env.prefix || '', + process.env.mixnetContractAddress || '', + process.env.vestingContractAddress || '', + process.env.denom || '', + ); + }); + + // + // CONTRACT + // + + it('can query for an account balance', async () => { + const balance = await client.getBalance('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77'); + expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0); + }); + + it('can query for stake saturation', async () => { + const stakeSaturation = await client.getStakeSaturation(7); + expect(Object.keys(stakeSaturation)).toEqual(Object.keys(saturation)); + expect(stakeSaturation).toBeTruthy(); + expect(stakeSaturation?.current_saturation).toBeTruthy(); + }); + + it('can query for contract version', async () => { + const contractV = await client.getMixnetContractVersion(); + expect(contractV).toBeTruthy(); + }); + + it('can query for mixnet contract settings', async () => { + const settings = await client.getMixnetContractSettings(); + expect(Object.keys(settings)).toEqual(Object.keys(contract)); + expect(settings).toBeTruthy(); + }); + + it('can query for reward pool', async () => { + const rewardPool = await client.getRewardParams(); + // TODO add velidation here + expect(rewardPool).toBeTruthy(); + }); + + it('can query for layer distribution', async () => { + const layer = await client.getLayerDistribution(); + expect(Object.keys(layer)).toEqual(Object.keys(layerDistribution)); + expect(layer).toBeTruthy(); + }); + + // + // MIXNODES + // + + it('can query for unbonded mixnodes', async () => { + const unbondedNodes = await client.getUnbondedMixNodes(); + for (let i = 0; i < unbondedNodes.length; i++) { + expect(Object.keys(unbondedNodes[0])).toEqual(Object.keys(allunbondednodes)); + expect(unbondedNodes).toBeTruthy(); + } + }); + + it('can query for unbonded mixnode information', async () => { + const unbondedMixnodeInfo = await client.getUnbondedMixNodeInformation(1); + expect(Object.keys(unbondedMixnodeInfo)).toEqual(Object.keys(unbondednode)); + expect(unbondedMixnodeInfo).toBeTruthy(); + }); + + it('can query for mixnode rewarding details', async () => { + const rewardingDetails = await client.getMixnodeRewardingDetails(1); + expect(Object.keys(rewardingDetails)).toEqual(Object.keys(rewardingnode)); + expect(rewardingDetails).toBeTruthy(); + }); + + it('can query for owned mixnode', async () => { + const ownedMixnode = await client.getOwnedMixnode('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77'); + expect(Object.keys(ownedMixnode)).toEqual(Object.keys(ownedNode)); + expect(ownedMixnode).toBeTruthy(); + }); + + it('can query for all mixnode bonds', async () => { + const mixnodeBonds = await client.getMixNodeBonds(); + expect(Object.keys(mixnodeBonds[0])).toEqual(Object.keys(mixnodebond)); + expect(mixnodeBonds).toBeTruthy(); + expect(Array.isArray(mixnodeBonds)).toBeTruthy(); + }); + + it('can query for all mixnode details', async () => { + const mixnodeDetails = await client.getMixNodesDetailed(); + expect(Object.keys(mixnodeDetails[0])).toEqual(Object.keys(mixnode)); + expect(mixnodeDetails).toBeTruthy(); + expect(Array.isArray(mixnodeDetails)).toBeTruthy(); + }); + + it('can query for all active mixnodes', async () => { + const activeNodes = await client.getActiveMixnodes(); + expect(Object.keys(activeNodes[0])).toEqual(Object.keys(mixnode)); + expect(activeNodes).toBeTruthy(); + expect(Array.isArray(activeNodes)).toBeTruthy(); + }); + + it('can query for rewarded mixnodes', async () => { + const rewardNodes = await client.getRewardedMixnodes(); + expect(Object.keys(rewardNodes[0])).toEqual(Object.keys(mixnode)); + expect(rewardNodes).toBeTruthy(); + }); + + // + // DELEGATIONS + // + + it('can query for account delegations', async () => { + const delegations = await client.getAllNyxdDelegatorDelegations('n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47'); + expect(Object.keys(delegations[0])).toEqual(Object.keys(delegation)); + expect(delegations).toBeTruthy(); + expect(Array.isArray(delegations)).toBeTruthy(); + }); + + it('can query for all delegations', async () => { + const allDelegations = await client.getAllNyxdDelegations(); + expect(Object.keys(allDelegations[0])).toEqual(Object.keys(delegation)); + expect(allDelegations).toBeTruthy(); + expect(Array.isArray(allDelegations)).toBeTruthy(); + }); + + it('can query for all delegations on a node', async () => { + const mixnodeDelegations = await client.getAllNyxdSingleMixnodeDelegations(1); + expect(Object.keys(mixnodeDelegations[0])).toEqual(Object.keys(delegation)); + expect(mixnodeDelegations).toBeTruthy(); + }); + + it('can query for detailed delegations', async () => { + const detailedDelegation = await client.getDelegationDetails(7, delegatorAddress); + expect(Object.keys(detailedDelegation)).toEqual(Object.keys(detailedDelegation)); + expect(detailedDelegation).toBeTruthy(); + }); + + // + // GATEWAYS + // + + it('can query for all gateways', async () => { + const gateways = await client.getAllNyxdGateways(); + expect(Object.keys(gateways[0])).toEqual(Object.keys(gateway)); + expect(gateways).toBeTruthy(); + expect(Array.isArray(gateways)).toBeTruthy(); + }).timeout(10000); + + it('can query for owned gateway', async () => { + const gateway = await client.ownsGateway(gatewayowneraddress); + expect(Object.keys(gateway)).toEqual(Object.keys(ownGateway)); + expect(gateway).toBeTruthy(); + }).timeout(10000); +}); diff --git a/clients/validator/src/tests/query/vestingQueries.test.ts b/clients/validator/src/tests/query/vestingQueries.test.ts new file mode 100644 index 0000000000..2cbe4f5088 --- /dev/null +++ b/clients/validator/src/tests/query/vestingQueries.test.ts @@ -0,0 +1,28 @@ +import expect from 'expect'; +import ValidatorClient from '../../index'; + +const dotenv = require('dotenv'); + +dotenv.config(); + +describe('Vesting queries', () => { + let client: ValidatorClient; + + beforeEach(async () => { + client = await ValidatorClient.connectForQuery( + process.env.rpcAddress || '', + process.env.validatorAddress || '', + process.env.prefix || '', + process.env.mixnetContractAddress || '', + process.env.vestingContractAddress || '', + process.env.denom || '', + ); + }); + + it('can query for contract version', async () => { + const contract = await client.getVestingContractVersion(); + expect(contract).toBeTruthy(); + }); + + it('can get the balance on the account', () => {}); +}); diff --git a/clients/validator/src/tests/sign/mixnetActions.test.ts b/clients/validator/src/tests/sign/mixnetActions.test.ts new file mode 100644 index 0000000000..1b535f6f2d --- /dev/null +++ b/clients/validator/src/tests/sign/mixnetActions.test.ts @@ -0,0 +1,107 @@ +import expect from 'expect'; +import ValidatorClient from '../../'; + +const dotenv = require('dotenv'); + +dotenv.config(); + +// TODO: implement for QA with .env for mnemonics +describe('Mixnet actions', () => { + let client: ValidatorClient; + + beforeEach(async () => { + client = await ValidatorClient.connect( + process.env.mnemonic || '', + process.env.rpcAddress || '', + process.env.validatorAddress || '', + process.env.prefix || '', + process.env.mixnetContractAddress || '', + process.env.vestingContractAddress || '', + process.env.denom || '', + ); + }); + + it('can send tokens', async () => { + const res = await client.send(client.address, [{ amount: '10000000', denom: 'unym' }]); + expect(res.transactionHash).toBeDefined(); + }).timeout(10000); + + it.skip('can delegate tokens', async () => { + const [_, secondMixnode] = await client.getActiveMixnodes(); + + if (secondMixnode) { + const res = await client.delegateToMixNode( + secondMixnode.bond_information.mix_id, + { + amount: '15000000', + denom: 'unym', + }, + { gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] }, + ); + expect(res.transactionHash).toBeDefined(); + } + }).timeout(10000); + + // Need to provide a mix id that can be undelegated from + it.skip('can undelegate from a mixnode', async () => { + const mixId = 8; + const res = await client.undelegateFromMixNode(mixId); + expect(res.transactionHash).toBeDefined(); + }); + + it.skip('Can unbond a mixnode', async () => { + const res = await client.unbondMixNode(); + expect(res.transactionHash).toBeDefined(); + }).timeout(10000); + + it.skip('Can bond a mixnode', async () => { + const res = await client.bondMixNode( + { + identity_key: '3P6pAcF2p3pYMqWdpHqhbavu3ifyaBs5Qw5UmmCGwimx', + sphinx_key: 'GQMQKwUThaggatA6oZteSWTsCQoUfmLtamJ7o9YkP9aE', + host: '109.74.195.67', + mix_port: 1789, + verloc_port: 1790, + http_api_port: 8000, + version: '1.1.4', + }, + '3rXWCQBUj5JQB3UBUkZcXhCk9Zh3cjduMF8aFHPTG7KTkkhZzDJTNmE2p2Ph1g6iQW5vRGTpQzjXF33WDwvhzHk6', + { profit_margin_percent: '0.1', interval_operating_cost: { amount: '40', denom: 'nym' } }, + { amount: '100_000_000', denom: 'unym' }, + { gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] }, + ); + expect(res.transactionHash).toBeDefined(); + }).timeout(10000); + + it.skip('can unbond a gateway', async () => { + const res = await client.unbondGateway(); + expect(res.transactionHash).toBeDefined(); + }); + + it.skip('can bond a gateway', async () => { + const res = await client.bondGateway( + { + identity_key: '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL', + sphinx_key: 'G65Fwc2JNAotuHQFqmDKhQNQL5rn3r9pupUdmxMygNUZ', + host: '151.236.220.82', + version: '1.1.4', + mix_port: 1789, + clients_port: 9000, + location: 'Cuba', + }, + '3ipSJksWHehZm1YfuH5Ahtg7b22NnrP9hEs6iEDXfUS5uiUhpWmCjGR3b3NDHuxeGjpZYJNYJ52D8WCPK5ZR7Szj', + { amount: '100_000_000', denom: 'unym' }, + ); + expect(res.transactionHash).toBeDefined(); + }); + + it.skip('can update contract state params', async () => { + const res = await client.updateContractStateParams({ + minimum_mixnode_pledge: '', + minimum_gateway_pledge: '', + mixnode_rewarded_set_size: 2, + mixnode_active_set_size: 2, + }); + expect(res.transactionHash).toBeDefined(); + }); +}); diff --git a/clients/validator/src/tsconfig.json b/clients/validator/src/tsconfig.json deleted file mode 100644 index 7990d8ef90..0000000000 --- a/clients/validator/src/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "esModuleInterop": true, - "strict": true, - "declaration": true, - "outDir": "./dist" - }, - "exclude": [ - "tests", - "dist", - "node_modules" - ] -} \ No newline at end of file diff --git a/clients/validator/src/types.ts b/clients/validator/src/types.ts deleted file mode 100644 index 575702ec45..0000000000 --- a/clients/validator/src/types.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Coin } from '@cosmjs/stargate'; - -// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust - -export type MixnetContractVersion = { - build_timestamp: string; - build_version: string; - commit_sha: string; - commit_timestamp: string; - commit_branch: string; - rustc_version: string; -}; - -export type PagedMixnodeResponse = { - nodes: MixNodeBond[]; - per_page: number; - start_next_after?: string; -}; - -export type PagedGatewayResponse = { - nodes: GatewayBond[]; - per_page: number; - start_next_after?: string; -}; - -export type MixOwnershipResponse = { - address: string; - mixnode?: MixNodeBond; -}; - -export type GatewayOwnershipResponse = { - address: string; - gateway?: GatewayBond; -}; - -export type ContractStateParams = { - // ideally I'd want to define those as `number` rather than `string`, but - // rust-side they are defined as Uint128 and that don't have - // native javascript representations and therefore are interpreted as strings after deserialization - minimum_mixnode_pledge: string; - minimum_gateway_pledge: string; - mixnode_rewarded_set_size: number; - mixnode_active_set_size: number; -}; - -export type LayerDistribution = { - gateways: number; - layer1: number; - layer2: number; - layer3: number; -}; - -export type Delegation = { - owner: string; - node_identity: string; - amount: Coin; - block_height: number; - proxy?: string; -}; - -export type PagedMixDelegationsResponse = { - delegations: Delegation[]; - start_next_after?: string; -}; - -export type PagedDelegatorDelegationsResponse = { - delegations: Delegation[]; - start_next_after?: string; -}; - -export type PagedAllDelegationsResponse = { - delegations: Delegation[]; - start_next_after?: [string, string]; -}; - -export type RewardingResult = { - operator_reward: string; - total_delegator_reward: string; -}; - -export type NodeRewardParams = { - period_reward_pool: string; - k: string; - reward_blockstamp: number; - circulating_supply: string; - uptime: string; - sybil_resistance_percent: number; -}; - -export type DelegatorRewardParams = { - node_reward_params: NodeRewardParams; - sigma: number; - profit_margin: number; - node_profit: number; -}; - -export type PendingDelegatorRewarding = { - running_results: RewardingResult; - next_start: string; - rewarding_params: DelegatorRewardParams; -}; - -export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding }; - -export type MixnodeRewardingStatusResponse = { - status?: RewardingStatus; -}; - -export enum Layer { - Gateway, - One, - Two, - Three, -} - -export type MixNodeBond = { - owner: string; - mix_node: MixNode; - layer: Layer; - bond_amount: Coin; - total_delegation: Coin; -}; - -export type MixNode = { - host: string; - mix_port: number; - verloc_port: number; - http_api_port: number; - sphinx_key: string; - identity_key: string; - version: string; - profit_margin_percent: number; -}; - -export type GatewayBond = { - owner: string; - gateway: Gateway; - - bond_amount: Coin; - total_delegation: Coin; -}; - -export type Gateway = { - host: string; - mix_port: number; - clients_port: number; - location: string; - sphinx_key: string; - identity_key: string; - version: string; -}; - -export type SendRequest = { - senderAddress: string; - recipientAddress: string; - transferAmount: readonly Coin[]; -}; diff --git a/clients/validator/src/types/global.d.ts b/clients/validator/src/types/global.d.ts new file mode 100644 index 0000000000..a6eb7c3521 --- /dev/null +++ b/clients/validator/src/types/global.d.ts @@ -0,0 +1,11 @@ +declare namespace NodeJS { + interface ProcessEnv { + rpcAddress: string; + validatorAddress: string; + prefix: string; + mixnetContractAddress: string; + vestingContractAddress: string; + denom: string; + mnemonic: string; + } +} diff --git a/clients/validator/src/types/shared.ts b/clients/validator/src/types/shared.ts new file mode 100644 index 0000000000..d8db5ad0e5 --- /dev/null +++ b/clients/validator/src/types/shared.ts @@ -0,0 +1,259 @@ +import { JsonObject } from '@cosmjs/cosmwasm-stargate'; +import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient'; +import { + Account, + Block, + Coin, + DeliverTxResponse, + IndexedTx, + SearchTxFilter, + SearchTxQuery, + SequenceResponse, +} from '@cosmjs/stargate'; +import { + MixNodeRewarding, + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + StakeSaturationResponse, + UnbondedMixnodeResponse, +} from '@nymproject/types'; + +export type MixnetContractVersion = { + build_timestamp: string; + build_version: string; + commit_sha: string; + commit_timestamp: string; + commit_branch: string; + rustc_version: string; +}; + +export type PagedMixnodeResponse = { + nodes: MixNodeBond[]; + per_page: number; + start_next_after?: string; +}; + +export type PagedGatewayResponse = { + nodes: GatewayBond[]; + per_page: number; + start_next_after?: string; +}; + +export type MixOwnershipResponse = { + address: string; + mixnode?: MixNodeBond; +}; + +export type GatewayOwnershipResponse = { + address: string; + gateway?: GatewayBond; +}; + +export type ContractStateParams = { + // ideally I'd want to define those as `number` rather than `string`, but + // rust-side they are defined as Uint128 and that don't have + // native javascript representations and therefore are interpreted as strings after deserialization + minimum_mixnode_pledge: string; + minimum_gateway_pledge: string; + mixnode_rewarded_set_size: number; + mixnode_active_set_size: number; +}; + +export type LayerDistribution = { + gateways: number; + layer1: number; + layer2: number; + layer3: number; +}; + +export type Delegation = { + owner: string; + node_identity: string; + amount: Coin; + block_height: number; + proxy?: string; +}; + +export type PagedMixDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: string; +}; + +export type PagedDelegatorDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: string; +}; + +export type PagedAllDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: [string, string]; +}; + +export type RewardingResult = { + operator_reward: string; + total_delegator_reward: string; +}; + +export type NodeRewardParams = { + period_reward_pool: string; + k: string; + reward_blockstamp: number; + circulating_supply: string; + uptime: string; + sybil_resistance_percent: number; +}; + +export type DelegatorRewardParams = { + node_reward_params: NodeRewardParams; + sigma: number; + profit_margin: number; + node_profit: number; +}; + +export type PendingDelegatorRewarding = { + running_results: RewardingResult; + next_start: string; + rewarding_params: DelegatorRewardParams; +}; + +export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding }; + +export type MixnodeRewardingStatusResponse = { + status?: RewardingStatus; +}; + +export enum Layer { + Gateway, + One, + Two, + Three, +} + +export type MixNodeBond = { + owner: string; + mix_node: MixNode; + layer: Layer; + bond_amount: Coin; + total_delegation: Coin; +}; + +export type MixNode = { + host: string; + mix_port: number; + verloc_port: number; + http_api_port: number; + sphinx_key: string; + identity_key: string; + version: string; + profit_margin_percent: number; +}; + +export type GatewayBond = { + owner: string; + gateway: Gateway; + + bond_amount: Coin; + total_delegation: Coin; +}; + +export type Gateway = { + host: string; + mix_port: number; + clients_port: number; + location: string; + sphinx_key: string; + identity_key: string; + version: string; +}; + +export type SendRequest = { + senderAddress: string; + recipientAddress: string; + transferAmount: readonly Coin[]; +}; + +export interface SmartContractQuery { + queryContractSmart(address: string, queryMsg: Record): Promise; +} +export interface ICosmWasmQuery { + // methods exposed by `CosmWasmClient` + getChainId(): Promise; + getHeight(): Promise; + getAccount(searchAddress: string): Promise; + getSequence(address: string): Promise; + getBlock(height?: number): Promise; + getBalance(address: string, searchDenom: string): Promise; + getTx(id: string): Promise; + searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; + disconnect(): void; + broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise; + getCodes(): Promise; + getCodeDetails(codeId: number): Promise; + getContracts(codeId: number): Promise; + getContract(address: string): Promise; + getContractCodeHistory(address: string): Promise; + queryContractRaw(address: string, key: Uint8Array): Promise; + queryContractSmart(address: string, queryMsg: Record): Promise; +} + +export interface INymdQuery { + // nym-specific implemented inside NymQuerier + getContractVersion(mixnetContractAddress: string): Promise; + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise; + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise; + getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise; + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise; + ownsGateway(mixnetContractAddress: string, address: string): Promise; + getStateParams(mixnetContractAddress: string): Promise; + getAllNetworkDelegationsPaged( + mixnetContractAddress: string, + limit?: number, + startAfter?: [string, string], + ): Promise; + getMixNodeDelegationsPaged( + mixnetContractAddress: string, + mix_id: number, + limit?: number, + startAfter?: string, + ): Promise; + getDelegatorDelegationsPaged( + mixnetContractAddress: string, + delegator: string, + limit?: number, + startAfter?: string, + ): Promise; + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise; + getLayerDistribution(mixnetContractAddress: string): Promise; + getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise; + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise; + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise; +} + +export interface IVestingQuerier { + getVestingContractVersion(mixnetContractAddress: string): Promise; +} + +export interface MappedCoin { + readonly denom: string; + readonly fractionalDigits: number; +} + +export interface CoinMap { + readonly [key: string]: MappedCoin; +} + +export interface ContractState { + owner: string; + rewarding_validator_address: string; + vesting_contract_address: string; + rewarding_denom: string; + params: ContractStateParams; +} diff --git a/clients/validator/tests/query/balance.test.ts b/clients/validator/tests/query/balance.test.ts deleted file mode 100644 index 97be47c2ce..0000000000 --- a/clients/validator/tests/query/balance.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import ValidatorClient from '../../validator/index'; -import expect from 'expect'; - -describe('Query: balances', () => { - it('can query for an account balance', async () => { - const client = await ValidatorClient.connectForQuery( - 'https://rpc.nymtech.net/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym'); - const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy'); - expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0); - }).timeout(5000); -}) diff --git a/clients/validator/tests/sign/send.test.ts b/clients/validator/tests/sign/send.test.ts deleted file mode 100644 index 197d5bd4ef..0000000000 --- a/clients/validator/tests/sign/send.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import ValidatorClient from '../../dist'; -import expect from 'expect'; - -// TODO: implement for QA with .env for mnemonics -// describe('Sign: send', () => { -// it('can send tokens', async () => { -// const client = await ValidatorClient.connect( -// '', -// 'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym'); -// await client.send('') -// const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy'); -// expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0); -// }).timeout(5000); -// }) \ No newline at end of file diff --git a/clients/validator/tsconfig.json b/clients/validator/tsconfig.json index ec8e7c1fb0..0a2ce0f074 100644 --- a/clients/validator/tsconfig.json +++ b/clients/validator/tsconfig.json @@ -1,23 +1,22 @@ { - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "esModuleInterop": true, - "strict": true, - "declaration": true, - "outDir": "./dist", - "skipLibCheck": true - }, - "typedocOptions": { - "entryPoints": [ - "src/index.ts" - ], - "out": "docs" - }, - "exclude": [ - "dist", - "examples", - "tests", - "node_modules" - ] -} \ No newline at end of file + "compilerOptions": { + "outDir": "./dist/nym-validator-client", + "module": "ES2020", + "target": "es2021", + "allowJs": false, + "strict": true, + "lib": ["es2021", "dom", "dom.iterable", "esnext"], + "moduleResolution": "node", + "skipDefaultLibCheck": true, + "esModuleInterop": true, + "declaration": true, + "skipLibCheck": true, + "noImplicitAny": true, + "typeRoots": ["./src/types/global.d.ts"] + }, + "typedocOptions": { + "entryPoints": ["./src/index.ts"], + "out": "docs" + }, + "exclude": ["dist", "./src/tests/**/*", "node_module"] +} diff --git a/clients/validator/tsconfig.test.json b/clients/validator/tsconfig.test.json new file mode 100644 index 0000000000..7048c297c8 --- /dev/null +++ b/clients/validator/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS" + } +} diff --git a/clients/webassembly/Cargo.toml b/clients/webassembly/Cargo.toml index 87bcd3ce91..190cc8e8d9 100644 --- a/clients/webassembly/Cargo.toml +++ b/clients/webassembly/Cargo.toml @@ -31,8 +31,8 @@ wasm-bindgen-futures = "0.4" # internal client-core = { path = "../client-core", default-features = false, features = ["wasm"] } -coconut-interface = { path = "../../common/coconut-interface" } -credentials = { path = "../../common/credentials" } +nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-credentials = { path = "../../common/credentials" } nym-crypto = { path = "../../common/crypto" } nym-sphinx = { path = "../../common/nymsphinx" } gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] } diff --git a/common/bandwidth-claim-contract/Cargo.toml b/common/bandwidth-claim-contract/Cargo.toml index e63e100d7a..bb9d6a85b3 100644 --- a/common/bandwidth-claim-contract/Cargo.toml +++ b/common/bandwidth-claim-contract/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bandwidth-claim-contract" +name = "nym-bandwidth-claim-contract" version = "0.1.0" edition = "2021" diff --git a/common/bin-common/Cargo.toml b/common/bin-common/Cargo.toml index 0b3c1a5af5..21f90d27f9 100644 --- a/common/bin-common/Cargo.toml +++ b/common/bin-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-bin-common" -version = "0.2.0" +version = "0.3.0" description = "Common code for nym binaries" edition = { workspace = true } authors = { workspace = true } @@ -17,7 +17,7 @@ semver = "0.11" serde = { workspace = true, features = ["derive"], optional = true } [build-dependencies] -vergen = { version = "7", default-features = false, features = ["build", "git", "rustc", "cargo"] } +vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc", "cargo"] } [features] default = [] diff --git a/common/bin-common/build.rs b/common/bin-common/build.rs index b909ce42c4..bd26c886d2 100644 --- a/common/bin-common/build.rs +++ b/common/bin-common/build.rs @@ -4,5 +4,10 @@ use vergen::{vergen, Config}; fn main() { - vergen(Config::default()).expect("failed to extract build metadata") + let mut config = Config::default(); + if std::env::var("DOCS_RS").is_ok() { + // If we don't have access to git information, such as in a docs.rs build, don't error + *config.git_mut().skip_if_error_mut() = true; + } + vergen(config).expect("failed to extract build metadata"); } diff --git a/common/build-information/Cargo.toml b/common/build-information/Cargo.toml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index a9867755d8..f630a24154 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -18,8 +18,8 @@ async-trait = { workspace = true } tokio = { version = "1.24.1", features = ["macros"] } # internal -coconut-interface = { path = "../../coconut-interface" } -credentials = { path = "../../credentials" } +nym-coconut-interface = { path = "../../coconut-interface" } +nym-credentials = { path = "../../credentials" } nym-crypto = { path = "../../crypto" } gateway-requests = { path = "../../../gateway/gateway-requests" } nym-network-defaults = { path = "../../network-defaults" } @@ -47,7 +47,7 @@ features = ["net", "sync", "time"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite] version = "0.14" -[target."cfg(not(target_arch = \"wasm32\"))".dependencies.credential-storage] +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-credential-storage] path = "../../credential-storage" # wasm-only dependencies diff --git a/common/client-libs/gateway-client/src/bandwidth.rs b/common/client-libs/gateway-client/src/bandwidth.rs index d2b5406836..5d1d0c50bf 100644 --- a/common/client-libs/gateway-client/src/bandwidth.rs +++ b/common/client-libs/gateway-client/src/bandwidth.rs @@ -7,7 +7,7 @@ use crate::error::GatewayClientError; use crate::wasm_mockups::Storage; #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "mobile"))] -use credential_storage::storage::Storage; +use nym_credential_storage::storage::Storage; #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "mobile")] @@ -22,7 +22,7 @@ use crate::wasm_mockups::StorageError; #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "mobile"))] -use credential_storage::error::StorageError; +use nym_credential_storage::error::StorageError; #[cfg(target_arch = "wasm32")] use crate::wasm_mockups::{Client, CosmWasmClient}; @@ -30,8 +30,8 @@ use std::str::FromStr; #[cfg(not(target_arch = "wasm32"))] use validator_client::{nyxd::CosmWasmClient, Client}; use { - coconut_interface::Base58, - credentials::coconut::{ + nym_coconut_interface::Base58, + nym_credentials::coconut::{ bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key, }, }; @@ -42,7 +42,7 @@ use crate::wasm_mockups::PersistentStorage; #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "mobile"))] -use credential_storage::PersistentStorage; +use nym_credential_storage::PersistentStorage; #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "mobile")] @@ -69,17 +69,17 @@ where pub async fn prepare_coconut_credential( &self, - ) -> Result<(coconut_interface::Credential, i64), GatewayClientError> { + ) -> Result<(nym_coconut_interface::Credential, i64), GatewayClientError> { let bandwidth_credential = self.storage.get_next_coconut_credential().await?; let voucher_value = u64::from_str(&bandwidth_credential.voucher_value) .map_err(|_| StorageError::InconsistentData)?; let voucher_info = bandwidth_credential.voucher_info.clone(); let serial_number = - coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?; + nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?; let binding_number = - coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?; + nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?; let signature = - coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?; + nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?; let epoch_id = u64::from_str(&bandwidth_credential.epoch_id) .map_err(|_| StorageError::InconsistentData)?; diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index a1d1662fd6..0919a81e85 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -9,13 +9,13 @@ pub use crate::packet_router::{ }; use crate::socket_state::{PartiallyDelegated, SocketState}; use crate::{cleanup_socket_message, try_decrypt_binary_message}; -use coconut_interface::Credential; use futures::{SinkExt, StreamExt}; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use gateway_requests::iv::IV; use gateway_requests::registration::handshake::{client_handshake, SharedKeys}; use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION}; use log::*; +use nym_coconut_interface::Credential; use nym_crypto::asymmetric::identity; use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; use nym_sphinx::forwarding::packet::MixPacket; @@ -33,7 +33,7 @@ use validator_client::nyxd::CosmWasmClient; #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "mobile"))] -use credential_storage::PersistentStorage; +use nym_credential_storage::PersistentStorage; #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "mobile")] diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs index 7080ce7723..c83ee33e42 100644 --- a/common/client-libs/gateway-client/src/error.rs +++ b/common/client-libs/gateway-client/src/error.rs @@ -3,13 +3,13 @@ #[cfg(target_arch = "wasm32")] use crate::wasm_mockups::StorageError; -#[cfg(not(feature = "mobile"))] -#[cfg(not(target_arch = "wasm32"))] -use credential_storage::error::StorageError; use gateway_requests::registration::handshake::error::HandshakeError; #[cfg(feature = "mobile")] #[cfg(not(target_arch = "wasm32"))] use mobile_storage::StorageError; +#[cfg(not(feature = "mobile"))] +#[cfg(not(target_arch = "wasm32"))] +use nym_credential_storage::error::StorageError; use std::io; use thiserror::Error; use tungstenite::Error as WsError; @@ -31,7 +31,7 @@ pub enum GatewayClientError { CredentialStorageError(#[from] StorageError), #[error("Coconut error - {0}")] - CoconutError(#[from] coconut_interface::CoconutError), + CoconutError(#[from] nym_coconut_interface::CoconutError), // TODO: see if `JsValue` is a reasonable type for this #[cfg(target_arch = "wasm32")] @@ -48,7 +48,7 @@ pub enum GatewayClientError { NoBandwidthControllerAvailable, #[error("Credential error - {0}")] - CredentialError(#[from] credentials::error::Error), + CredentialError(#[from] nym_credentials::error::Error), #[error("Connection was abruptly closed")] ConnectionAbruptlyClosed, diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index a5f95b9508..7673c3ab46 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -11,13 +11,13 @@ rust-version = "1.56" base64 = "0.13" colored = "2.0" -coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" } +nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" } nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" } nym-mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" } -coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } -multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } -group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } +nym-coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } +nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } nym-vesting-contract = { path = "../../../contracts/vesting" } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -28,9 +28,10 @@ url = { version = "2.2", features = ["serde"] } tokio = { version = "1.24.1", features = ["sync", "time"] } futures = "0.3" -coconut-interface = { path = "../../coconut-interface" } +nym-coconut-interface = { path = "../../coconut-interface" } nym-network-defaults = { path = "../../network-defaults" } nym-api-requests = { path = "../../../nym-api/nym-api-requests" } +nym-execute = { path = "../../execute" } # required for nyxd-client # at some point it might be possible to make it wasm-compatible @@ -39,14 +40,14 @@ async-trait = { workspace = true, optional = true } bip39 = { version = "1", features = ["rand"], optional = true } nym-config = { path = "../../config", optional = true } cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true} -cw3 = { version = "0.13.4", optional = true } -cw4 = { version = "0.13.4", optional = true } +cw3 = { workspace = true, optional = true } +cw4 = { workspace = true, optional = true } prost = { version = "0.10", default-features = false, optional = true } flate2 = { version = "1.0.20", optional = true } sha2 = { version = "0.9.5", optional = true } itertools = { version = "0.10", optional = true } -cosmwasm-std = { version = "1.0.0", optional = true } -nym-execute = { path = "../../execute" } +cosmwasm-std = { workspace = true, optional = true } +zeroize = { version = "1.5.7", optional = true } [dev-dependencies] ts-rs = "6.1.2" @@ -64,6 +65,7 @@ nyxd-client = [ "sha2", "itertools", "cosmwasm-std", + "zeroize" ] generate-ts = [] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 68e3358576..1aaa514b87 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{nym_api, ValidatorClientError}; -use coconut_dkg_common::types::NodeIndex; -use coconut_interface::VerificationKey; use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; @@ -11,6 +9,8 @@ use nym_api_requests::models::{ GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, }; +use nym_coconut_dkg_common::types::NodeIndex; +use nym_coconut_interface::VerificationKey; pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId}; #[cfg(feature = "nyxd-client")] @@ -18,17 +18,17 @@ use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient #[cfg(feature = "nyxd-client")] use crate::nyxd::{self, CosmWasmClient, NyxdClient, QueryNyxdClient, SigningNyxdClient}; #[cfg(feature = "nyxd-client")] -use coconut_dkg_common::{ +use cw3::ProposalResponse; +#[cfg(feature = "nyxd-client")] +use nym_api_requests::models::MixNodeBondAnnotated; +#[cfg(feature = "nyxd-client")] +use nym_coconut_dkg_common::{ dealer::ContractDealing, types::{DealerDetails, EpochId}, verification_key::ContractVKShare, }; #[cfg(feature = "nyxd-client")] -use coconut_interface::Base58; -#[cfg(feature = "nyxd-client")] -use cw3::ProposalResponse; -#[cfg(feature = "nyxd-client")] -use nym_api_requests::models::MixNodeBondAnnotated; +use nym_coconut_interface::Base58; #[cfg(feature = "nyxd-client")] use nym_mixnet_contract_common::{ families::{Family, FamilyHead}, @@ -130,10 +130,9 @@ impl Config { #[cfg(feature = "nyxd-client")] #[derive(Clone)] pub struct Client { - // TODO: we really shouldn't be storing a mnemonic here, but removing it would be - // non-trivial amount of work and it's out of scope of the current branch - mnemonic: Option, - + // // TODO: we really shouldn't be storing a mnemonic here, but removing it would be + // // non-trivial amount of work and it's out of scope of the current branch + // mnemonic: Option, mixnode_page_limit: Option, gateway_page_limit: Option, mixnode_delegations_page_limit: Option, @@ -157,12 +156,11 @@ impl Client { let nyxd_client = NyxdClient::connect_with_mnemonic( config.nyxd_config.clone(), config.nyxd_url.as_str(), - mnemonic.clone(), + mnemonic, None, )?; Ok(Client { - mnemonic: Some(mnemonic), mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, @@ -176,12 +174,7 @@ impl Client { } pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { - self.nyxd = NyxdClient::connect_with_mnemonic( - self.nyxd.current_config().clone(), - new_endpoint.as_ref(), - self.mnemonic.clone().unwrap(), - None, - )?; + self.nyxd.change_endpoint(new_endpoint.as_ref())?; Ok(()) } @@ -198,7 +191,6 @@ impl Client { NyxdClient::connect(config.nyxd_config.clone(), config.nyxd_url.as_str())?; Ok(Client { - mnemonic: None, mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, @@ -757,6 +749,12 @@ where Ok(self.nym_api.get_mixnodes_detailed().await?) } + pub async fn get_cached_mixnodes_detailed_unfiltered( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?) + } + pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index fa453d3c83..10bea35417 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -144,6 +144,21 @@ impl Client { .await } + pub async fn get_mixnodes_detailed_unfiltered( + &self, + ) -> Result, NymAPIError> { + self.query_nym_api( + &[ + routes::API_VERSION, + routes::STATUS, + routes::MIXNODES, + routes::DETAILED_UNFILTERED, + ], + NO_PARAMS, + ) + .await + } + pub async fn get_gateways(&self) -> Result, NymAPIError> { self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS) .await diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index 1c715ff1b8..8b6bdeca2f 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -8,6 +8,7 @@ pub const MIXNODES: &str = "mixnodes"; pub const GATEWAYS: &str = "gateways"; pub const DETAILED: &str = "detailed"; +pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered"; pub const ACTIVE: &str = "active"; pub const REWARDED: &str = "rewarded"; pub const COCONUT_ROUTES: &str = "coconut"; diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/logs.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/logs.rs index fd196cf6e7..3912500f47 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/logs.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/logs.rs @@ -6,8 +6,8 @@ use cosmrs::tendermint::abci; use itertools::Itertools; use serde::{Deserialize, Serialize}; -pub use coconut_bandwidth_contract_common::event_attributes::*; -pub use coconut_dkg_common::event_attributes::*; +pub use nym_coconut_bandwidth_contract_common::event_attributes::*; +pub use nym_coconut_dkg_common::event_attributes::*; // it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response) // as theirs logs diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/signing_client.rs index 6fbe2cec44..58ec47bbcb 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/signing_client.rs @@ -756,6 +756,19 @@ impl Client { }) } + pub fn change_endpoint(&mut self, new_endpoint: U) -> Result<(), NyxdError> + where + U: TryInto, + { + let new_rpc_client = HttpClient::new(new_endpoint)?; + self.rpc_client = new_rpc_client; + Ok(()) + } + + pub fn into_signer(self) -> DirectSecp256k1HdWallet { + self.signer + } + pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) { self.broadcast_polling_rate = broadcast_polling_rate } diff --git a/common/client-libs/validator-client/src/nyxd/error.rs b/common/client-libs/validator-client/src/nyxd/error.rs index fab3965217..2330edbdf7 100644 --- a/common/client-libs/validator-client/src/nyxd/error.rs +++ b/common/client-libs/validator-client/src/nyxd/error.rs @@ -140,7 +140,7 @@ pub enum NyxdError { CosmwasmStdError(#[from] cosmwasm_std::StdError), #[error("Coconut interface error: {0}")] - CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), + CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), #[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")] UnexpectedBech32Prefix { got: String, expected: String }, diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index e02672682b..4990f1b030 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -197,7 +197,7 @@ impl NyxdClient { { let prefix = &config.chain_details.bech32_account_prefix; let denom = &config.chain_details.mix_denom.base; - let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?; + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); let client_address = wallet .try_derive_accounts()? .into_iter() @@ -212,6 +212,17 @@ impl NyxdClient { simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, }) } + + pub fn change_endpoint(&mut self, new_endpoint: U) -> Result<(), NyxdError> + where + U: TryInto, + { + self.client.change_endpoint(new_endpoint) + } + + pub fn into_signer(self) -> DirectSecp256k1HdWallet { + self.client.into_signer() + } } impl NyxdClient diff --git a/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_query_client.rs index 7cacd57801..71758a124b 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_query_client.rs @@ -4,8 +4,8 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::{CosmWasmClient, NyxdClient}; -use coconut_bandwidth_contract_common::msg::QueryMsg; -use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; +use nym_coconut_bandwidth_contract_common::msg::QueryMsg; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; use async_trait::async_trait; diff --git a/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_signing_client.rs index a7d03c9625..b21ce7543e 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_signing_client.rs @@ -5,8 +5,8 @@ pub use crate::nyxd::cosmwasm_client::signing_client::SigningCosmWasmClient; use crate::nyxd::cosmwasm_client::types::ExecuteResult; use crate::nyxd::error::NyxdError; use crate::nyxd::{Coin, Fee, NyxdClient}; -use coconut_bandwidth_contract_common::spend_credential::SpendCredentialData; -use coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg}; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialData; +use nym_coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg}; use async_trait::async_trait; diff --git a/common/client-libs/validator-client/src/nyxd/traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/dkg_query_client.rs index 296f8913eb..20e032dff0 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/dkg_query_client.rs @@ -4,13 +4,13 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::{CosmWasmClient, NyxdClient}; use async_trait::async_trait; -use coconut_dkg_common::dealer::{ +use cosmrs::AccountId; +use nym_coconut_dkg_common::dealer::{ DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse, }; -use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; -use coconut_dkg_common::types::{Epoch, EpochId, InitialReplacementData}; -use coconut_dkg_common::verification_key::PagedVKSharesResponse; -use cosmrs::AccountId; +use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; +use nym_coconut_dkg_common::types::{Epoch, EpochId, InitialReplacementData}; +use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse; #[async_trait] pub trait DkgQueryClient { diff --git a/common/client-libs/validator-client/src/nyxd/traits/dkg_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/dkg_signing_client.rs index 45fdd2cbbc..b99260e120 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/dkg_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/dkg_signing_client.rs @@ -5,9 +5,9 @@ use crate::nyxd::cosmwasm_client::types::ExecuteResult; use crate::nyxd::error::NyxdError; use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient}; use async_trait::async_trait; -use coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg; -use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; -use coconut_dkg_common::verification_key::VerificationKeyShare; +use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg; +use nym_coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; +use nym_coconut_dkg_common::verification_key::VerificationKeyShare; use nym_contracts_common::dealings::ContractSafeBytes; #[async_trait] diff --git a/common/client-libs/validator-client/src/nyxd/traits/group_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/group_query_client.rs index 57a6f6a98f..f41fb2e261 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/group_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/group_query_client.rs @@ -4,7 +4,7 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::{CosmWasmClient, NyxdClient}; -use group_contract_common::msg::QueryMsg; +use nym_group_contract_common::msg::QueryMsg; use async_trait::async_trait; use cw4::MemberResponse; diff --git a/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs index 640969fe2b..2ff48d1113 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs @@ -18,12 +18,13 @@ use nym_mixnet_contract_common::rewarding::{ }; use nym_mixnet_contract_common::{ delegation, ContractBuildInformation, ContractState, ContractStateParams, - CurrentIntervalResponse, EpochEventId, GatewayBondResponse, GatewayOwnershipResponse, - IdentityKey, IntervalEventId, LayerDistribution, MixId, MixOwnershipResponse, - MixnodeDetailsResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, - PagedFamiliesResponse, PagedGatewayResponse, PagedMembersResponse, - PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse, - PendingEpochEventsResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg, + CurrentIntervalResponse, EpochEventId, EpochStatus, GatewayBondResponse, + GatewayOwnershipResponse, IdentityKey, IntervalEventId, LayerDistribution, MixId, + MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse, + PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse, + PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse, + PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse, + PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg, }; use serde::Deserialize; @@ -60,6 +61,11 @@ pub trait MixnetQueryClient { .await } + async fn get_current_epoch_status(&self) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetEpochStatus {}) + .await + } + async fn get_current_interval_details(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetCurrentIntervalDetails {}) .await @@ -377,6 +383,13 @@ pub trait MixnetQueryClient { .await } + async fn get_number_of_pending_events( + &self, + ) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetNumberOfPendingEvents {}) + .await + } + async fn get_node_family_by_label(&self, label: &str) -> Result, NyxdError> { self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByLabel { label: label.to_string(), diff --git a/common/client-libs/validator-client/src/nyxd/traits/mixnet_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/mixnet_signing_client.rs index b780acb9ef..dafad09536 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/mixnet_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/mixnet_signing_client.rs @@ -106,6 +106,11 @@ pub trait MixnetSigningClient { .await } + async fn begin_epoch_transition(&self, fee: Option) -> Result { + self.execute_mixnet_contract(fee, MixnetExecuteMsg::BeginEpochTransition {}, vec![]) + .await + } + async fn advance_current_epoch( &self, new_rewarded_set: Vec, @@ -193,7 +198,8 @@ pub trait MixnetSigningClient { async fn join_family_on_behalf( &self, member_address: String, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: String, fee: Option, ) -> Result { @@ -201,7 +207,8 @@ pub trait MixnetSigningClient { fee, MixnetExecuteMsg::JoinFamilyOnBehalf { member_address, - signature, + node_identity_signature, + family_signature, family_head, }, vec![], @@ -229,7 +236,7 @@ pub trait MixnetSigningClient { async fn leave_family_on_behalf( &self, member_address: String, - signature: String, + node_identity_signature: String, family_head: String, fee: Option, ) -> Result { @@ -237,7 +244,7 @@ pub trait MixnetSigningClient { fee, MixnetExecuteMsg::LeaveFamilyOnBehalf { member_address, - signature, + node_identity_signature, family_head, }, vec![], diff --git a/common/client-libs/validator-client/src/nyxd/traits/multisig_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/multisig_query_client.rs index c396cecec4..392a9a9cd0 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/multisig_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/multisig_query_client.rs @@ -5,7 +5,7 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::{CosmWasmClient, NyxdClient}; use cw3::{ProposalListResponse, ProposalResponse}; -use multisig_contract_common::msg::QueryMsg; +use nym_multisig_contract_common::msg::QueryMsg; use async_trait::async_trait; diff --git a/common/client-libs/validator-client/src/nyxd/traits/multisig_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/multisig_signing_client.rs index 6e4069c4a2..6d716ece8a 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/multisig_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/multisig_signing_client.rs @@ -6,9 +6,9 @@ use crate::nyxd::cosmwasm_client::types::ExecuteResult; use crate::nyxd::error::NyxdError; use crate::nyxd::{Fee, NyxdClient}; -use coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg; use cw3::Vote; -use multisig_contract_common::msg::ExecuteMsg; +use nym_coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg; +use nym_multisig_contract_common::msg::ExecuteMsg; use async_trait::async_trait; use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg}; diff --git a/common/client-libs/validator-client/src/nyxd/wallet/mod.rs b/common/client-libs/validator-client/src/nyxd/wallet/mod.rs index f58d059c6d..c859b6fecc 100644 --- a/common/client-libs/validator-client/src/nyxd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/wallet/mod.rs @@ -8,6 +8,7 @@ use cosmrs::crypto::PublicKey; use cosmrs::tx::SignDoc; use cosmrs::{tx, AccountId}; use nym_config::defaults; +use zeroize::Zeroize; /// Derivation information required to derive a keypair and an address from a mnemonic. #[derive(Debug, Clone)] @@ -56,19 +57,40 @@ pub struct DirectSecp256k1HdWallet { accounts: Vec, } +impl Zeroize for DirectSecp256k1HdWallet { + fn zeroize(&mut self) { + // in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it) + // and the memory would have been filled with zeroes. + // + // we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing + // of overwriting it with a fresh mnemonic that was never used before + // + // note: this function can only fail on an invalid word count, which clearly is not the case here + self.secret = bip39::Mnemonic::generate(self.secret.word_count()).unwrap(); + self.seed.zeroize(); + // there's nothing secret about derivation paths + } +} + +impl Drop for DirectSecp256k1HdWallet { + fn drop(&mut self) { + self.zeroize() + } +} + impl DirectSecp256k1HdWallet { pub fn builder(prefix: &str) -> DirectSecp256k1HdWalletBuilder { DirectSecp256k1HdWalletBuilder::new(prefix) } /// Restores a wallet from the given BIP39 mnemonic using default options. - pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Result { + pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Self { DirectSecp256k1HdWalletBuilder::new(prefix).build(mnemonic) } pub fn generate(prefix: &str, word_count: usize) -> Result { let mneomonic = bip39::Mnemonic::generate(word_count)?; - Self::from_mnemonic(prefix, mneomonic) + Ok(Self::from_mnemonic(prefix, mneomonic)) } fn derive_keypair(&self, hd_path: &DerivationPath) -> Result { @@ -145,11 +167,14 @@ impl DirectSecp256k1HdWallet { } #[must_use] +#[derive(Zeroize)] +#[zeroize(drop)] pub struct DirectSecp256k1HdWalletBuilder { /// The password to use when deriving a BIP39 seed from a mnemonic. bip39_password: String, /// The BIP-32/SLIP-10 derivation paths + #[zeroize(skip)] hd_paths: Vec, /// The bech32 address prefix (human readable part) @@ -185,23 +210,23 @@ impl DirectSecp256k1HdWalletBuilder { self } - pub fn build(self, mnemonic: bip39::Mnemonic) -> Result { + pub fn build(self, mnemonic: bip39::Mnemonic) -> DirectSecp256k1HdWallet { let seed = mnemonic.to_seed(&self.bip39_password); - let prefix = self.prefix; + let prefix = self.prefix.clone(); let accounts = self .hd_paths - .into_iter() + .iter() .map(|hd_path| Secp256k1Derivation { - hd_path, + hd_path: hd_path.clone(), prefix: prefix.clone(), }) .collect(); - Ok(DirectSecp256k1HdWallet { + DirectSecp256k1HdWallet { accounts, seed, secret: mnemonic, - }) + } } } @@ -229,8 +254,7 @@ mod tests { "n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf", ]; for (idx, mnemonic) in mnemonics.iter().enumerate() { - let wallet = - DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap()).unwrap(); + let wallet = DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap()); assert_eq!( wallet.try_derive_accounts().unwrap()[0].address, addrs[idx].parse().unwrap() diff --git a/common/coconut-interface/Cargo.toml b/common/coconut-interface/Cargo.toml index 285750cb3a..18f9115052 100644 --- a/common/coconut-interface/Cargo.toml +++ b/common/coconut-interface/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "coconut-interface" +name = "nym-coconut-interface" version = "0.1.0" edition = "2021" description = "Crutch library until there is proper SerDe support for coconut structs" diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index cf941a1961..01d9f3f5fa 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -11,7 +11,7 @@ bs58 = "0.4" comfy-table = "6.0.0" cfg-if = "1.0.0" clap = { version = "4.0", features = ["derive"] } -cw-utils = { version = "0.13.4" } +cw-utils = { workspace = true } handlebars = "3.0.1" humantime-serde = "1.0" k256 = { version = "0.10", features = ["ecdsa", "sha256"] } @@ -26,12 +26,12 @@ url = "2.2" tap = "1" cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } -cosmwasm-std = { version = "1.0.0" } +cosmwasm-std = { workspace = true } validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] } nym-network-defaults = { path = "../network-defaults" } nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" } -coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" } -coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } -multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" } +nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } +nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" } diff --git a/common/commands/src/validator/account/create.rs b/common/commands/src/validator/account/create.rs index e321d5b41d..e82e97ffde 100644 --- a/common/commands/src/validator/account/create.rs +++ b/common/commands/src/validator/account/create.rs @@ -15,8 +15,7 @@ pub fn create_account(args: Args, prefix: &str) { let word_count = args.word_count.unwrap_or(24); let mnemonic = bip39::Mnemonic::generate(word_count).expect("failed to generate mnemonic!"); - let wallet = - DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic).expect("failed to build wallet!"); + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); // Output address and mnemonics into separate lines for easier parsing println!("{}", wallet.mnemonic()); diff --git a/common/commands/src/validator/account/pubkey.rs b/common/commands/src/validator/account/pubkey.rs index 46c5186fff..8f340229f9 100644 --- a/common/commands/src/validator/account/pubkey.rs +++ b/common/commands/src/validator/account/pubkey.rs @@ -51,21 +51,19 @@ pub async fn get_pubkey( } pub fn get_pubkey_from_mnemonic(address: AccountId, prefix: &str, mnemonic: bip39::Mnemonic) { - match DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic) { - Ok(wallet) => match wallet.try_derive_accounts() { - Ok(accounts) => match accounts.iter().find(|a| *a.address() == address) { - Some(account) => { - println!("{}", account.public_key().to_string()); - } - None => { - error!("Could not derive key that matches {}", address) - } - }, - Err(e) => { - error!("Failed to derive accounts. {}", e); + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); + match wallet.try_derive_accounts() { + Ok(accounts) => match accounts.iter().find(|a| *a.address() == address) { + Some(account) => { + println!("{}", account.public_key().to_string()); + } + None => { + error!("Could not derive key that matches {}", address) } }, - Err(e) => show_error(e), + Err(e) => { + error!("Failed to derive accounts. {}", e); + } } } diff --git a/common/commands/src/validator/cosmwasm/generators/coconut_bandwidth.rs b/common/commands/src/validator/cosmwasm/generators/coconut_bandwidth.rs index 936ec5b383..2640a5e89b 100644 --- a/common/commands/src/validator/cosmwasm/generators/coconut_bandwidth.rs +++ b/common/commands/src/validator/cosmwasm/generators/coconut_bandwidth.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use clap::Parser; use log::{debug, info}; -use coconut_bandwidth_contract_common::msg::InstantiateMsg; +use nym_coconut_bandwidth_contract_common::msg::InstantiateMsg; use validator_client::nyxd::AccountId; #[derive(Debug, Parser)] diff --git a/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs b/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs index 6386d9b600..6a1294fe16 100644 --- a/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs +++ b/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs @@ -5,8 +5,8 @@ use clap::Parser; use log::{debug, info}; use std::str::FromStr; -use coconut_dkg_common::msg::InstantiateMsg; -use coconut_dkg_common::types::TimeConfiguration; +use nym_coconut_dkg_common::msg::InstantiateMsg; +use nym_coconut_dkg_common::types::TimeConfiguration; use validator_client::nyxd::AccountId; #[derive(Debug, Parser)] diff --git a/common/commands/src/validator/cosmwasm/generators/multisig.rs b/common/commands/src/validator/cosmwasm/generators/multisig.rs index 0d0be11477..3db5af2022 100644 --- a/common/commands/src/validator/cosmwasm/generators/multisig.rs +++ b/common/commands/src/validator/cosmwasm/generators/multisig.rs @@ -7,7 +7,7 @@ use clap::Parser; use cosmwasm_std::Decimal; use cw_utils::{Duration, Threshold}; use log::{debug, info}; -use multisig_contract_common::msg::InstantiateMsg; +use nym_multisig_contract_common::msg::InstantiateMsg; use validator_client::nyxd::AccountId; #[derive(Debug, Parser)] diff --git a/common/commands/src/validator/signature/sign.rs b/common/commands/src/validator/signature/sign.rs index 87dfc0303e..3dd93b22d5 100644 --- a/common/commands/src/validator/signature/sign.rs +++ b/common/commands/src/validator/signature/sign.rs @@ -1,7 +1,6 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::utils::show_error; use clap::Parser; use cosmrs::crypto::PublicKey; use log::error; @@ -36,33 +35,32 @@ pub fn sign(args: Args, prefix: &str, mnemonic: Option) { return; } - match DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.expect("mnemonic not set")) { - Ok(wallet) => match wallet.try_derive_accounts() { - Ok(accounts) => match accounts.first() { - Some(account) => { - let msg = args.message.into_bytes(); - match wallet.sign_raw_with_account(account, &msg) { - Ok(signature) => { - let output = SignatureOutputJson { - account_id: account.address().to_string(), - public_key: account.public_key(), - signature_as_hex: signature.to_string(), - }; - println!("{}", json!(output)); - } - Err(e) => { - error!("Failed to sign message. {}", e); - } + let wallet = + DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.expect("mnemonic not set")); + match wallet.try_derive_accounts() { + Ok(accounts) => match accounts.first() { + Some(account) => { + let msg = args.message.into_bytes(); + match wallet.sign_raw_with_account(account, &msg) { + Ok(signature) => { + let output = SignatureOutputJson { + account_id: account.address().to_string(), + public_key: account.public_key(), + signature_as_hex: signature.to_string(), + }; + println!("{}", json!(output)); + } + Err(e) => { + error!("Failed to sign message. {}", e); } } - None => { - error!("Could not derive an account key from the mnemonic",) - } - }, - Err(e) => { - error!("Failed to derive accounts. {}", e); + } + None => { + error!("Could not derive an account key from the mnemonic",) } }, - Err(e) => show_error(e), + Err(e) => { + error!("Failed to derive accounts. {}", e); + } } } diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml index 0d4cf1ef86..fc5e1b43d7 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "coconut-bandwidth-contract-common" +name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cosmwasm-std = "1.0.0" +cosmwasm-std = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -multisig-contract-common = { path = "../multisig-contract" } +nym-multisig-contract-common = { path = "../multisig-contract" } diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs index 28842576c6..ffece73fd4 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::{from_binary, to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg}; -use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; +use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml index 59eb21e5c0..1f6d6c3f3f 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml @@ -1,15 +1,15 @@ [package] -name = "coconut-dkg-common" +name = "nym-coconut-dkg-common" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cosmwasm-std = "1.0.0" -cw-utils = "0.13.4" +cosmwasm-std = { workspace = true } +cw-utils = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } contracts-common = { path = "../contracts-common", package = "nym-contracts-common" } -multisig-contract-common = { path = "../multisig-contract" } +nym-multisig-contract-common = { path = "../multisig-contract" } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs index 90b66acde7..9b8338b6f1 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs @@ -5,7 +5,7 @@ use crate::msg::ExecuteMsg; use crate::types::{EpochId, NodeIndex}; use cosmwasm_std::{from_binary, to_binary, Addr, CosmosMsg, StdResult, Timestamp, WasmMsg}; use cw_utils::Expiration; -use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; +use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; use serde::{Deserialize, Serialize}; pub type VerificationKeyShare = String; diff --git a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml index e5b6dd66eb..dbed7f9cf6 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-contracts-common" -version = "0.2.0" +version = "0.3.0" description = "Common library for Nym cosmwasm contracts" edition = { workspace = true } authors = { workspace = true } @@ -9,7 +9,7 @@ repository = { workspace = true } [dependencies] bs58 = "0.4.0" -cosmwasm-std = "1.0.0" +cosmwasm-std = { workspace = true } schemars = "0.8" serde = { version = "1.0", features = ["derive"] } thiserror = "1" diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs index cfc22e8727..87251d38e7 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(feature = "dkg")] -use dkg::{error::DkgError, Dealing}; +use nym_dkg::{error::DkgError, Dealing}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Display, Formatter}; diff --git a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml index 6389f8fb3c..9f57c84154 100644 --- a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "group-contract-common" +name = "nym-group-contract-common" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cw4 = { version = "0.13.4" } +cw4 = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 670c993554..ac6c2c2061 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-mixnet-contract-common" -version = "0.2.0" +version = "0.3.0" description = "Common library for the Nym mixnet contract" rust-version = "1.62" edition = { workspace = true } @@ -10,12 +10,12 @@ repository = { workspace = true } [dependencies] bs58 = "0.4.0" -cosmwasm-std = "1.0.0" +cosmwasm-std = { workspace = true } serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1" schemars = "0.8" thiserror = "1.0" -contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.2.0" } +contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.3.0" } serde_json = "1.0.0" humantime-serde = "1.1.1" diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs index 3c696a83ca..acce6bca1a 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs @@ -16,12 +16,19 @@ use serde::{Deserialize, Serialize}; pub type OwnerProxySubKey = String; pub type StorageKey = (MixId, OwnerProxySubKey); -pub fn generate_owner_storage_subkey(address: &Addr, proxy: Option<&Addr>) -> String { - if let Some(proxy) = &proxy { +// throughout the contract we ensure that our proxy can ONLY ever be the vesting contract +// thus this method is equivalent to either using the existing address (for when there's no proxy) +// or to XORing with a constant (vesting contract address) since the vesting contract address never changes. +// Thus there should be no collisions in here. +pub fn generate_owner_storage_subkey( + address: &Addr, + maybe_vesting_contract: Option<&Addr>, +) -> String { + if let Some(vesting_contract) = &maybe_vesting_contract { let key_bytes = address .as_bytes() .iter() - .zip(proxy.as_bytes()) + .zip(vesting_contract.as_bytes()) .map(|(x, y)| x ^ y) .collect::>(); bs58::encode(key_bytes).into_string() diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index 6eff27c315..7a68f81d90 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{IdentityKey, MixId}; +use crate::{EpochState, IdentityKey, MixId}; use cosmwasm_std::{Addr, Coin, Decimal}; use thiserror::Error; @@ -64,6 +64,19 @@ pub enum MixnetContractError { #[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), @@ -169,4 +182,40 @@ pub enum MixnetContractError { #[error("Feature is not yet implemented")] NotImplemented, + + #[error("epochs_in_interval must be > 0")] + EpochsInIntervalZero, + + #[error("epoch duration must be > 0")] + EpochDurationZero, + + #[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")] + RewardingValidatorMismatch { + current_validator: Addr, + chosen_validator: Addr, + }, + + #[error("the epoch is currently in the process of being advanced. (the state is {current_state}) Please try sending your transaction again once this has finished")] + EpochAdvancementInProgress { current_state: EpochState }, + + #[error("the epoch is in an unexpected state. expected 'mix rewarding' state, but we're in {current_state} instead.")] + UnexpectedNonRewardingEpochState { current_state: EpochState }, + + #[error("attempted to reward mixnode out of order. Attempted to reward {attempted_to_reward} while last rewarded was {last_rewarded}.")] + RewardingOutOfOrder { + last_rewarded: MixId, + attempted_to_reward: MixId, + }, + + #[error("the epoch is currently not in the 'event reconciliation' state. (the state is {current_state})")] + EpochNotInEventReconciliationState { current_state: EpochState }, + + #[error("the epoch is currently not in the 'epoch advancement' state. (the state is {current_state})")] + EpochNotInAdvancementState { current_state: EpochState }, + + #[error("failed to parse {value} into a valid SemVer version: {error_message}")] + SemVerFailure { + value: String, + error_message: String, + }, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs index 7a6ab1ffe6..98ecb8a1b7 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs @@ -35,6 +35,7 @@ pub enum MixnetEventType { Undelegation, ContractSettingsUpdate, RewardingValidatorUpdate, + BeginEpochTransition, AdvanceEpoch, ExecutePendingEpochEvents, ExecutePendingIntervalEvents, @@ -77,6 +78,7 @@ impl ToString for MixnetEventType { MixnetEventType::Undelegation => "undelegation", MixnetEventType::ContractSettingsUpdate => "settings_update", MixnetEventType::RewardingValidatorUpdate => "rewarding_validator_address_update", + MixnetEventType::BeginEpochTransition => "beginning_epoch_transition", MixnetEventType::AdvanceEpoch => "advance_epoch", MixnetEventType::ExecutePendingEpochEvents => "execute_pending_epoch_events", MixnetEventType::ExecutePendingIntervalEvents => "execute_pending_interval_events", @@ -139,6 +141,7 @@ pub const ZERO_PERFORMANCE_VALUE: &str = "zero_performance"; // rewarded set update pub const ACTIVE_SET_SIZE_KEY: &str = "active_set_size"; +pub const CURRENT_EPOCH_KEY: &str = "current_epoch"; pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch"; // interval @@ -500,6 +503,13 @@ pub fn new_mix_rewarding_event( ) } +pub fn new_epoch_transition_start_event(current_interval: Interval) -> Event { + Event::new(MixnetEventType::BeginEpochTransition).add_attribute( + CURRENT_EPOCH_KEY, + current_interval.current_epoch_absolute_id().to_string(), + ) +} + pub fn new_advance_epoch_event(interval: Interval, rewarded_nodes: u32) -> Event { Event::new(MixnetEventType::AdvanceEpoch) .add_attribute( diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index f09a6cfe22..a7bb6caba1 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -1,9 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::error::MixnetContractError; use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent}; -use crate::{EpochId, IntervalId}; -use cosmwasm_std::Env; +use crate::{EpochId, IntervalId, MixId}; +use cosmwasm_std::{Addr, Env}; use schemars::gen::SchemaGenerator; use schemars::schema::{InstanceType, Schema, SchemaObject}; use schemars::JsonSchema; @@ -60,6 +61,133 @@ pub(crate) mod string_rfc3339_offset_date_time { } } +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct EpochStatus { + // TODO: introduce mechanism to allow another validator to take over if no progress has been made in X blocks / Y seconds + /// Specifies either, which validator is currently performing progression into the following epoch (if the epoch is currently being progressed), + /// or which validator was responsible for progressing into the current epoch (if the epoch is currently in progress) + pub being_advanced_by: Addr, + pub state: EpochState, +} + +impl EpochStatus { + pub fn new(being_advanced_by: Addr) -> Self { + EpochStatus { + being_advanced_by, + state: EpochState::InProgress, + } + } + + pub fn update_last_rewarded( + &mut self, + new_last_rewarded: MixId, + ) -> Result { + match &mut self.state { + EpochState::Rewarding { + ref mut last_rewarded, + final_node_id, + } => { + if new_last_rewarded <= *last_rewarded { + return Err(MixnetContractError::RewardingOutOfOrder { + last_rewarded: *last_rewarded, + attempted_to_reward: new_last_rewarded, + }); + } + + *last_rewarded = new_last_rewarded; + Ok(new_last_rewarded == *final_node_id) + } + state => Err(MixnetContractError::UnexpectedNonRewardingEpochState { + current_state: *state, + }), + } + } + + pub fn last_rewarded(&self) -> Result { + match self.state { + EpochState::Rewarding { last_rewarded, .. } => Ok(last_rewarded), + state => Err(MixnetContractError::UnexpectedNonRewardingEpochState { + current_state: state, + }), + } + } + + pub fn ensure_is_in_event_reconciliation_state(&self) -> Result<(), MixnetContractError> { + if !matches!(self.state, EpochState::ReconcilingEvents) { + return Err(MixnetContractError::EpochNotInEventReconciliationState { + current_state: self.state, + }); + } + Ok(()) + } + + pub fn ensure_is_in_advancement_state(&self) -> Result<(), MixnetContractError> { + if !matches!(self.state, EpochState::AdvancingEpoch) { + return Err(MixnetContractError::EpochNotInAdvancementState { + current_state: self.state, + }); + } + Ok(()) + } + + pub fn is_in_progress(&self) -> bool { + matches!(self.state, EpochState::InProgress) + } + + pub fn is_rewarding(&self) -> bool { + matches!(self.state, EpochState::Rewarding { .. }) + } + + pub fn is_reconciling(&self) -> bool { + matches!(self.state, EpochState::ReconcilingEvents) + } + + pub fn is_advancing(&self) -> bool { + matches!(self.state, EpochState::AdvancingEpoch) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] +pub enum EpochState { + /// Represents the state of an epoch that's in progress (well, duh.) + /// All actions are allowed to be issued. + InProgress, + + /// Represents the state of an epoch when the rewarding entity has been decided on, + /// and the mixnodes are in the process of being rewarded for their work in this epoch. + Rewarding { + last_rewarded: MixId, + + final_node_id: MixId, + // total_rewarded: u32, + }, + + /// Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch + /// and all issued actions should now get resolved before being allowed to advance into the next epoch. + ReconcilingEvents, + + /// Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch, + /// all issued actions got resolved and the epoch should now be advanced whilst assigning new rewarded set. + AdvancingEpoch, +} + +impl Display for EpochState { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EpochState::InProgress => write!(f, "in progress"), + EpochState::Rewarding { + last_rewarded, + final_node_id, + } => write!( + f, + "mix rewarding (last rewarded: {last_rewarded}, final node: {final_node_id})" + ), + EpochState::ReconcilingEvents => write!(f, "event reconciliation"), + EpochState::AdvancingEpoch => write!(f, "advancing epoch"), + } + } +} + #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", @@ -400,6 +528,21 @@ impl PendingIntervalEventsResponse { } } +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct NumberOfPendingEventsResponse { + pub epoch_events: u32, + pub interval_events: u32, +} + +impl NumberOfPendingEventsResponse { + pub fn new(epoch_events: u32, interval_events: u32) -> Self { + Self { + epoch_events, + interval_events, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index adabf7de30..4adc351471 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 #![warn(clippy::expect_used)] @@ -29,7 +29,8 @@ pub use gateway::{ Gateway, GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse, }; pub use interval::{ - CurrentIntervalResponse, Interval, PendingEpochEventsResponse, PendingIntervalEventsResponse, + CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse, + PendingEpochEventsResponse, PendingIntervalEventsResponse, }; pub use mixnode::{ Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index d1b9fd31e6..1af718afde 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -101,15 +101,16 @@ pub enum ExecuteMsg { owner_signature: String, label: String, }, - /// Family head needs to sign the joining node IdentityKey + /// Family head needs to sign the joining node IdentityKey, MixNode needs to provide its signature proving that it wants to join the family JoinFamilyOnBehalf { member_address: String, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: IdentityKey, }, LeaveFamilyOnBehalf { member_address: String, - signature: String, + node_identity_signature: String, family_head: IdentityKey, }, KickFamilyMemberOnBehalf { @@ -138,6 +139,7 @@ pub enum ExecuteMsg { epoch_duration_secs: u64, force_immediately: bool, }, + BeginEpochTransition {}, AdvanceCurrentEpoch { new_rewarded_set: Vec, // families_in_layer: HashMap, @@ -281,6 +283,7 @@ impl ExecuteMsg { ExecuteMsg::UpdateIntervalConfig { force_immediately, .. } => format!("updating mixnet interval configuration. forced: {force_immediately}"), + ExecuteMsg::BeginEpochTransition {} => "beginning epoch transition".into(), ExecuteMsg::AdvanceCurrentEpoch { .. } => "advancing current epoch".into(), ExecuteMsg::ReconcileEpochEvents { .. } => "reconciling epoch events".into(), ExecuteMsg::BondMixnode { mix_node, .. } => { @@ -367,10 +370,13 @@ pub enum QueryMsg { }, // state/sys-params-related GetContractVersion {}, + #[serde(rename = "get_cw2_contract_version")] + GetCW2ContractVersion {}, GetRewardingValidatorAddress {}, GetStateParams {}, GetState {}, GetRewardingParams {}, + GetEpochStatus {}, GetCurrentIntervalDetails {}, GetRewardedSet { limit: Option, @@ -493,6 +499,7 @@ pub enum QueryMsg { limit: Option, start_after: Option, }, + GetNumberOfPendingEvents {}, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml index 096e4affe0..14ef586c93 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -1,16 +1,16 @@ [package] -name = "multisig-contract-common" +name = "nym-multisig-contract-common" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cw-utils = { version = "0.13.4" } -cw3 = { version = "0.13.4" } -cw3-fixed-multisig = { version = "0.13.4", features = ["library"] } -cw4 = { version = "0.13.4" } -cosmwasm-std = "1.0.0" +cw-utils = { workspace = true } +cw3 = { workspace = true } +cw3-fixed-multisig = { workspace = true, features = ["library"] } +cw4 = { workspace= true } +cosmwasm-std = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } diff --git a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml index 7742b7c7a8..88a67765d9 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-vesting-contract-common" -version = "0.2.0" +version = "0.3.0" description = "Common library for the Nym vesting contract" edition = { workspace = true } authors = { workspace = true } @@ -8,9 +8,9 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -cosmwasm-std = "1.0.0" -mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" } -contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.2.0" } +cosmwasm-std = { workspace = true } +mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" } +contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.3.0" } serde = { version = "1.0", features = ["derive"] } schemars = "0.8" ts-rs = {version = "6.1.2", optional = true} diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index e69a2eef55..8df999f2a6 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -61,13 +61,14 @@ pub enum ExecuteMsg { owner_signature: String, label: String, }, - /// Family head needs to sign the joining node IdentityKey + /// Family head needs to sign the joining node IdentityKey, the Node provides its signature signaling consent to join the family JoinFamily { - signature: String, + node_identity_signature: String, + family_signature: String, family_head: IdentityKey, }, LeaveFamily { - signature: String, + node_identity_signature: String, family_head: IdentityKey, }, KickFamilyMember { @@ -188,6 +189,8 @@ impl ExecuteMsg { #[serde(rename_all = "snake_case")] pub enum QueryMsg { GetContractVersion {}, + #[serde(rename = "get_cw2_contract_version")] + GetCW2ContractVersion {}, GetAccountsPaged { start_next_after: Option, limit: Option, diff --git a/common/credential-storage/Cargo.toml b/common/credential-storage/Cargo.toml index ec828bce97..c488eab99b 100644 --- a/common/credential-storage/Cargo.toml +++ b/common/credential-storage/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "credential-storage" +name = "nym-credential-storage" version = "0.1.0" edition = "2021" diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index b69c5f12f5..c7c64c4301 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "credentials" +name = "nym-credentials" version = "0.1.0" edition = "2021" @@ -11,7 +11,7 @@ cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegr thiserror = "1.0" # I guess temporarily until we get serde support in coconut up and running -coconut-interface = { path = "../coconut-interface" } +nym-coconut-interface = { path = "../coconut-interface" } nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] } nym-api-requests = { path = "../../nym-api/nym-api-requests" } validator-client = { path = "../client-libs/validator-client" } diff --git a/common/credentials/src/coconut/bandwidth.rs b/common/credentials/src/coconut/bandwidth.rs index 78563211a2..9c87e75341 100644 --- a/common/credentials/src/coconut/bandwidth.rs +++ b/common/credentials/src/coconut/bandwidth.rs @@ -6,7 +6,7 @@ // right now this has no double-spending protection, spender binding, etc // it's the simplest possible case -use coconut_interface::{ +use nym_coconut_interface::{ hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey, }; @@ -282,7 +282,7 @@ pub fn prepare_for_spending( #[cfg(test)] mod test { use super::*; - use coconut_interface::Base58; + use nym_coconut_interface::Base58; use rand::rngs::OsRng; fn voucher_fixture() -> BandwidthVoucher { diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 4de5b8f8ea..5a912705ce 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -1,11 +1,11 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_interface::{ +use nym_api_requests::coconut::BlindSignRequestBody; +use nym_coconut_interface::{ aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute, BlindedSignature, Credential, Parameters, Signature, SignatureShare, VerificationKey, }; -use nym_api_requests::coconut::BlindSignRequestBody; use nym_crypto::asymmetric::encryption::PublicKey; use nym_crypto::shared_key::recompute_shared_key; use nym_crypto::symmetric::stream_cipher; diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 4c146b0af8..e35cca27e4 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_interface::CoconutError; +use nym_coconut_interface::CoconutError; use nym_crypto::asymmetric::encryption::KeyRecoveryError; use validator_client::ValidatorClientError; diff --git a/common/dkg/Cargo.toml b/common/dkg/Cargo.toml index 525001f42e..dc1f4c29e9 100644 --- a/common/dkg/Cargo.toml +++ b/common/dkg/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dkg" +name = "nym-dkg" version = "0.1.0" edition = "2021" resolver = "2" @@ -37,7 +37,7 @@ version = "0.11" default-features = false [dev-dependencies] -criterion = "0.3" +criterion = "0.4" [[bench]] name = "benchmarks" @@ -45,4 +45,4 @@ harness = false [features] default = [] -cw-types = ["nym-contracts-common"] \ No newline at end of file +cw-types = ["nym-contracts-common"] diff --git a/common/dkg/benches/benchmarks.rs b/common/dkg/benches/benchmarks.rs index 7b85d70356..0aed2e4192 100644 --- a/common/dkg/benches/benchmarks.rs +++ b/common/dkg/benches/benchmarks.rs @@ -3,17 +3,17 @@ use bls12_381::{G1Projective, G2Affine, G2Prepared, Scalar}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use dkg::bte::encryption::BabyStepGiantStepLookup; -use dkg::bte::proof_chunking::ProofOfChunking; -use dkg::bte::proof_discrete_log::ProofOfDiscreteLog; -use dkg::bte::proof_sharing::ProofOfSecretSharing; -use dkg::bte::{ +use ff::Field; +use nym_dkg::bte::encryption::BabyStepGiantStepLookup; +use nym_dkg::bte::proof_chunking::ProofOfChunking; +use nym_dkg::bte::proof_discrete_log::ProofOfDiscreteLog; +use nym_dkg::bte::proof_sharing::ProofOfSecretSharing; +use nym_dkg::bte::{ decrypt_share, encrypt_shares, keygen, proof_chunking, proof_sharing, setup, DecryptionKey, PublicKey, }; -use dkg::interpolation::polynomial::Polynomial; -use dkg::{Dealing, NodeIndex, Share}; -use ff::Field; +use nym_dkg::interpolation::polynomial::Polynomial; +use nym_dkg::{Dealing, NodeIndex, Share}; use rand_core::{RngCore, SeedableRng}; use std::collections::BTreeMap; diff --git a/common/dkg/tests/integration.rs b/common/dkg/tests/integration.rs index 4ac55df1c7..2e7ffcda07 100644 --- a/common/dkg/tests/integration.rs +++ b/common/dkg/tests/integration.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use bls12_381::{G2Projective, Scalar}; -use dkg::bte::{decrypt_share, keygen, setup}; -use dkg::dealing::RecoveredVerificationKeys; -use dkg::interpolation::perform_lagrangian_interpolation_at_origin; -use dkg::{combine_shares, try_recover_verification_keys, Dealing}; +use nym_dkg::bte::{decrypt_share, keygen, setup}; +use nym_dkg::dealing::RecoveredVerificationKeys; +use nym_dkg::interpolation::perform_lagrangian_interpolation_at_origin; +use nym_dkg::{combine_shares, try_recover_verification_keys, Dealing}; use rand_core::SeedableRng; use std::collections::BTreeMap; diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index a8db7465a3..f2878ad16a 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace = true [dependencies] cfg-if = { workspace = true } -dotenv = { workspace = true } +dotenvy = { workspace = true } hex-literal = "0.3.3" once_cell = { workspace = true } serde = { workspace = true, features = ["derive"]} diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index f0d6ccce16..37e3b8d956 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -320,7 +320,7 @@ pub fn setup_env(config_env_file: Option<&PathBuf>) { // if the configuration is not already set in the env vars Err(std::env::VarError::NotPresent) => { if let Some(config_env_file) = config_env_file { - dotenv::from_path(config_env_file) + dotenvy::from_path(config_env_file) .expect("Invalid path to environment configuration file"); fix_deprecated_environmental_variables(); } else { diff --git a/common/nymcoconut/Cargo.toml b/common/nymcoconut/Cargo.toml index 2d262a60a6..ed9cf51b7a 100644 --- a/common/nymcoconut/Cargo.toml +++ b/common/nymcoconut/Cargo.toml @@ -17,7 +17,7 @@ serde_derive = "1.0" bs58 = "0.4.0" sha2 = "0.9" -dkg = { path = "../dkg" } +nym-dkg = { path = "../dkg" } nym-pemstore = { path = "../pemstore" } [dependencies.ff] @@ -29,7 +29,7 @@ version = "0.11" default-features = false [dev-dependencies] -criterion = { version="0.3", features=["html_reports"] } +criterion = { version="0.4", features=["html_reports"] } doc-comment = "0.3" rand_chacha = "0.3" diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs index bae9efa36d..a432cecd60 100644 --- a/common/nymcoconut/src/tests/helpers.rs +++ b/common/nymcoconut/src/tests/helpers.rs @@ -109,13 +109,13 @@ pub mod tests { use super::*; use crate::{KeyPair, Parameters, SecretKey}; use bls12_381::Scalar; - use dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex}; + use nym_dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex}; use rand_chacha::rand_core::SeedableRng; pub fn generate_dkg_secrets(node_indices: &[NodeIndex]) -> Vec { let dummy_seed = [42u8; 32]; let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); - let params = dkg::bte::setup(); + let params = nym_dkg::bte::setup(); // the simplest possible case let threshold = 2; @@ -123,7 +123,7 @@ pub mod tests { let mut receivers = std::collections::BTreeMap::new(); let mut full_keys = Vec::new(); for index in node_indices { - let (dk, pk) = dkg::bte::keygen(¶ms, &mut rng); + let (dk, pk) = nym_dkg::bte::keygen(¶ms, &mut rng); receivers.insert(*index, *pk.public_key()); full_keys.push((dk, pk)) } diff --git a/common/nymsphinx/chunking/src/set.rs b/common/nymsphinx/chunking/src/set.rs index 82af761b73..be5ae739da 100644 --- a/common/nymsphinx/chunking/src/set.rs +++ b/common/nymsphinx/chunking/src/set.rs @@ -66,13 +66,15 @@ pub(crate) type FragmentSet = Vec; /// `Fragment`s thus allowing for some additional optimizations by letting it skip /// certain procedures when reconstructing. pub(crate) fn generate_set_id(rng: &mut R) -> i32 { - let potential_id = rng.gen::().abs(); + let potential_id = rng.gen::(); // make sure id is always non-zero, as we do not want to accidentally have weird // reconstruction cases where unfragmented payload overwrites some part of set with id0 - if potential_id == 0 { + // furthermore, make sure it's not i32::MIN (-2147483648) as due to 2-complement encoding, + // attempting to calculate the absolutely value is going to panic + if potential_id == 0 || potential_id == i32::MIN { generate_set_id(rng) } else { - potential_id + potential_id.abs() } } diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index cc0f91306e..0c49d798a6 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -219,3 +219,16 @@ impl PacketSize { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::AckEncryptionAlgorithm; + use nym_crypto::symmetric::stream_cipher::IvSizeUser; + + #[test] + fn ack_iv_size_assertion() { + let iv_size = AckEncryptionAlgorithm::iv_size(); + assert_eq!(iv_size, ACK_IV_SIZE); + } +} diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index 93e370d55e..147a14788c 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -59,6 +59,9 @@ impl From for ReconstructedMessage { #[derive(Debug, Error)] pub enum MessageRecoveryError { + #[error("The received message did not contain enough bytes to recover the ephemeral public key. Got {provided}. required: {required}")] + NotEnoughBytesForEphemeralKey { provided: usize, required: usize }, + #[error("Recovered remote x25519 public key is invalid - {0}")] InvalidRemoteEphemeralKey(#[from] encryption::KeyRecoveryError), @@ -123,6 +126,13 @@ impl MessageReceiver { local_key: &encryption::PrivateKey, raw_enc_frag: &'a mut [u8], ) -> Result<&'a mut [u8], MessageRecoveryError> { + if raw_enc_frag.len() < encryption::PUBLIC_KEY_SIZE { + return Err(MessageRecoveryError::NotEnoughBytesForEphemeralKey { + provided: raw_enc_frag.len(), + required: encryption::PUBLIC_KEY_SIZE, + }); + } + // 1. recover remote encryption key let remote_key_bytes = &raw_enc_frag[..encryption::PUBLIC_KEY_SIZE]; let remote_ephemeral_key = encryption::PublicKey::from_bytes(remote_key_bytes)?; diff --git a/common/socks5/ordered-buffer/Cargo.toml b/common/socks5/ordered-buffer/Cargo.toml index d7207eaf9c..faa16769db 100644 --- a/common/socks5/ordered-buffer/Cargo.toml +++ b/common/socks5/ordered-buffer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ordered-buffer" +name = "nym-ordered-buffer" version = "0.1.0" authors = ["Dave Hrycyszyn "] edition = "2021" @@ -8,4 +8,4 @@ edition = "2021" [dependencies] log = { workspace = true } -thiserror = "1.0.37" \ No newline at end of file +thiserror = "1.0.37" diff --git a/common/socks5/proxy-helpers/Cargo.toml b/common/socks5/proxy-helpers/Cargo.toml index 3f1403fd9b..88157a22c4 100644 --- a/common/socks5/proxy-helpers/Cargo.toml +++ b/common/socks5/proxy-helpers/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "proxy-helpers" +name = "nym-socks5-proxy-helpers" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" @@ -16,8 +16,8 @@ futures = "0.3" log = { workspace = true } # internal -ordered-buffer = { path = "../ordered-buffer" } -socks5-requests = { path = "../requests" } +nym-ordered-buffer = { path = "../ordered-buffer" } +nym-socks5-requests = { path = "../requests" } nym-task = { path = "../../task" } [dev-dependencies] diff --git a/common/socks5/proxy-helpers/src/connection_controller.rs b/common/socks5/proxy-helpers/src/connection_controller.rs index b50e0fb7a3..a466316bbb 100644 --- a/common/socks5/proxy-helpers/src/connection_controller.rs +++ b/common/socks5/proxy-helpers/src/connection_controller.rs @@ -4,10 +4,10 @@ use futures::channel::mpsc; use futures::StreamExt; use log::*; +use nym_ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData}; +use nym_socks5_requests::{ConnectionId, NetworkData, SendRequest}; use nym_task::connections::{ConnectionCommand, ConnectionCommandSender}; use nym_task::TaskClient; -use ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData}; -use socks5_requests::{ConnectionId, NetworkData, SendRequest}; use std::collections::{HashMap, HashSet}; /// A generic message produced after reading from a socket/connection. It includes data that was diff --git a/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs b/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs index 959de166de..548d1a0971 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs @@ -8,11 +8,11 @@ use bytes::Bytes; use futures::FutureExt; use futures::StreamExt; use log::*; +use nym_ordered_buffer::OrderedMessageSender; +use nym_socks5_requests::ConnectionId; use nym_task::connections::LaneQueueLengths; use nym_task::connections::TransmissionLane; use nym_task::TaskClient; -use ordered_buffer::OrderedMessageSender; -use socks5_requests::ConnectionId; use std::fmt::Debug; use std::time::Duration; use std::{io, sync::Arc}; diff --git a/common/socks5/proxy-helpers/src/proxy_runner/mod.rs b/common/socks5/proxy-helpers/src/proxy_runner/mod.rs index d0e427c2da..c9d925ad4f 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/mod.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/mod.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::connection_controller::ConnectionReceiver; +use nym_socks5_requests::ConnectionId; use nym_task::connections::LaneQueueLengths; use nym_task::TaskClient; -use socks5_requests::ConnectionId; use std::fmt::Debug; use std::{sync::Arc, time::Duration}; use tokio::{net::TcpStream, sync::Notify}; diff --git a/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs b/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs index f97b1b4056..72d8824d2b 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs @@ -6,8 +6,8 @@ use crate::connection_controller::{ConnectionMessage, ConnectionReceiver}; use futures::FutureExt; use futures::StreamExt; use log::*; +use nym_socks5_requests::ConnectionId; use nym_task::TaskClient; -use socks5_requests::ConnectionId; use std::{sync::Arc, time::Duration}; use tokio::io::AsyncWriteExt; use tokio::select; diff --git a/common/socks5/requests/Cargo.toml b/common/socks5/requests/Cargo.toml index f879221078..d100503c6a 100644 --- a/common/socks5/requests/Cargo.toml +++ b/common/socks5/requests/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "socks5-requests" +name = "nym-socks5-requests" version = "0.1.0" authors = ["Dave Hrycyszyn "] edition = "2021" @@ -12,4 +12,4 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" } -service-providers-common = { path = "../../../service-providers/common" } +nym-service-providers-common = { path = "../../../service-providers/common" } diff --git a/common/socks5/requests/src/lib.rs b/common/socks5/requests/src/lib.rs index f2bf2124e4..ba81b231a4 100644 --- a/common/socks5/requests/src/lib.rs +++ b/common/socks5/requests/src/lib.rs @@ -1,8 +1,8 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use service_providers_common::interface; -use service_providers_common::interface::ServiceProviderMessagingError; +use nym_service_providers_common::interface; +use nym_service_providers_common::interface::ServiceProviderMessagingError; use thiserror::Error; pub use request::*; @@ -37,12 +37,12 @@ pub enum Socks5RequestError { #[cfg(test)] mod tests { use super::*; - use service_providers_common::interface::RequestContent; + use nym_service_providers_common::interface::RequestContent; #[cfg(test)] mod interface_backwards_compatibility { use super::*; - use service_providers_common::interface::ProviderInterfaceVersion; + use nym_service_providers_common::interface::ProviderInterfaceVersion; #[test] fn old_client_vs_new_service_provider() { diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index 867cbfa786..f296407c0b 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{Socks5ProtocolVersion, Socks5RequestError, Socks5Response}; +use nym_service_providers_common::interface::{Serializable, ServiceProviderRequest}; use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError}; -use service_providers_common::interface::{Serializable, ServiceProviderRequest}; use std::convert::TryFrom; use thiserror::Error; diff --git a/common/socks5/requests/src/response.rs b/common/socks5/requests/src/response.rs index fd7a31d942..6e698a2b37 100644 --- a/common/socks5/requests/src/response.rs +++ b/common/socks5/requests/src/response.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ConnectionId, Socks5ProtocolVersion, Socks5RequestError}; -use service_providers_common::interface::{Serializable, ServiceProviderResponse}; +use nym_service_providers_common::interface::{Serializable, ServiceProviderResponse}; use thiserror::Error; // don't start tags from 0 for easier backwards compatibility since `NetworkData` diff --git a/common/socks5/requests/src/version.rs b/common/socks5/requests/src/version.rs index 2fd70a42d5..7bace529de 100644 --- a/common/socks5/requests/src/version.rs +++ b/common/socks5/requests/src/version.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use service_providers_common::{define_simple_version, interface::Version}; +use nym_service_providers_common::{define_simple_version, interface::Version}; /// Defines initial version of the communication interface between socks5 clients and /// network requesters (socks5). diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index 6824f6c0d7..4eab444605 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -7,7 +7,7 @@ use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::GatewayBond; use nym_sphinx_addressing::nodes::NodeIdentity; use nym_sphinx_types::Node as SphinxNode; -use rand::Rng; +use rand::{CryptoRng, Rng}; use std::collections::HashMap; use std::convert::TryInto; use std::fmt::{self, Display, Formatter}; @@ -194,8 +194,7 @@ impl NymTopology { gateway_identity: &NodeIdentity, ) -> Result, NymTopologyError> where - // I don't think there's a need for this RNG to be crypto-secure - R: Rng + ?Sized, + R: Rng + CryptoRng + ?Sized, { let gateway = self.get_gateway(gateway_identity).ok_or( NymTopologyError::NonExistentGatewayError { diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index 1bdf334f42..5f822c4987 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -19,7 +19,7 @@ thiserror = "1.0" url = "2.2" ts-rs = "6.1.2" -cosmwasm-std = "1.0.0" +cosmwasm-std = { workspace = true } cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } validator-client = { path = "../../common/client-libs/validator-client", features = [ @@ -28,7 +28,7 @@ validator-client = { path = "../../common/client-libs/validator-client", feature nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } nym-config = { path = "../../common/config" } -coconut-interface = { path = "../../common/coconut-interface" } +nym-coconut-interface = { path = "../../common/coconut-interface" } # Used for Type conversion, can be extracted but its a lot of work nym-vesting-contract = { path = "../../contracts/vesting" } diff --git a/contracts/.gitignore b/contracts/.gitignore deleted file mode 100644 index ffa3bbd20e..0000000000 --- a/contracts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Cargo.lock \ No newline at end of file diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock new file mode 100644 index 0000000000..b1c927514c --- /dev/null +++ b/contracts/Cargo.lock @@ -0,0 +1,2037 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "ctr", + "opaque-debug 0.3.0", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.8", + "once_cell", + "version_check", +] + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "coconut-test" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-multi-test", + "cw-storage-plus", + "cw-utils", + "cw3", + "cw3-flex-multisig", + "cw4", + "cw4-group", + "nym-bandwidth-claim-contract", + "nym-coconut-bandwidth", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg", + "nym-coconut-dkg-common", + "nym-group-contract-common", + "nym-multisig-contract-common", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "cosmwasm-crypto" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" +dependencies = [ + "digest 0.9.0", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" +dependencies = [ + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + +[[package]] +name = "cosmwasm-std" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" +dependencies = [ + "base64 0.13.1", + "cosmwasm-crypto", + "cosmwasm-derive", + "forward_ref", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.6", + "rand_core 0.6.4", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.6", + "subtle 2.4.1", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "cw-controllers" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw-utils", + "derivative", + "itertools", + "prost", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + +[[package]] +name = "cw3" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw3-fixed-multisig" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54aa54c13f405ec4ab36b6217538bc957d439eee58f89312db05a79caf6706" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw3", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw3-flex-multisig" +version = "0.13.1" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw3", + "cw3-fixed-multisig", + "cw4", + "cw4-group", + "nym-group-contract-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "cw4" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + +[[package]] +name = "cw4-group" +version = "0.13.4" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw4", + "nym-group-contract-common", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek", + "hashbrown", + "hex", + "rand_core 0.6.4", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array 0.14.6", + "group", + "rand_core 0.6.4", + "sec1", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "enum-iterator" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355f93763ef7b0ae1c43c4d8eccc9d5848d84ad1a1d8ce61c421d1ac85a19d05" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "git2" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sec1", + "sha2", +] + +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "libgit2-sys" +version = "0.13.5+1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2", + "chacha", + "keystream", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "nym-bandwidth-claim-contract" +version = "0.1.0" +dependencies = [ + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-bandwidth" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-storage-plus", + "nym-bandwidth-claim-contract", + "nym-coconut-bandwidth-contract-common", + "nym-multisig-contract-common", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-dkg" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-multi-test", + "cw-storage-plus", + "cw4", + "cw4-group", + "lazy_static", + "nym-coconut-dkg-common", + "nym-group-contract-common", + "rusty-fork", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-contracts-common" +version = "0.3.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-crypto" +version = "0.2.0" +dependencies = [ + "bs58", + "ed25519-dalek", + "nym-pemstore", + "nym-sphinx-types", + "rand", + "subtle-encoding", + "thiserror", + "x25519-dalek", +] + +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +dependencies = [ + "cw4", + "schemars", + "serde", +] + +[[package]] +name = "nym-mixnet-contract" +version = "1.2.0" +dependencies = [ + "bs58", + "cosmwasm-derive", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw2", + "nym-crypto", + "nym-mixnet-contract-common", + "nym-vesting-contract-common", + "rand_chacha 0.2.2", + "schemars", + "semver", + "serde", + "thiserror", + "time", + "vergen", +] + +[[package]] +name = "nym-mixnet-contract-common" +version = "0.3.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "humantime-serde", + "log", + "nym-contracts-common", + "schemars", + "serde", + "serde_json", + "serde_repr", + "thiserror", + "time", +] + +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw3-fixed-multisig", + "cw4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-pemstore" +version = "0.2.0" +dependencies = [ + "pem", +] + +[[package]] +name = "nym-sphinx-types" +version = "0.2.0" +dependencies = [ + "sphinx-packet", +] + +[[package]] +name = "nym-vesting-contract" +version = "1.2.0" +dependencies = [ + "base64 0.21.0", + "cosmwasm-crypto", + "cosmwasm-derive", + "cosmwasm-std", + "cw-storage-plus", + "cw2", + "hex", + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-vesting-contract-common", + "rand_chacha 0.3.1", + "schemars", + "semver", + "serde", + "serde_json", + "thiserror", + "vergen", +] + +[[package]] +name = "nym-vesting-contract-common" +version = "0.3.0" +dependencies = [ + "cosmwasm-std", + "nym-contracts-common", + "nym-mixnet-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.36.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schemars" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array 0.14.6", + "pkcs8", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.3", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "signature" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.4", +] + +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes", + "arrayref", + "blake2", + "bs58", + "byteorder", + "chacha", + "curve25519-dalek", + "digest 0.9.0", + "hkdf", + "hmac", + "lioness", + "log", + "rand", + "rand_distr", + "sha2", + "subtle 2.4.1", +] + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8234ae35e70582bfa0f1fedffa6daa248e41dd045310b19800c4a36382c8f60" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.42.0", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.3", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d502c968c6a838ead8e69b2ee18ec708802f99db92a0d156705ec9ef801993b" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "7.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37" +dependencies = [ + "anyhow", + "cfg-if", + "enum-iterator", + "getset", + "git2", + "rustc_version", + "rustversion", + "thiserror", + "time", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "x25519-dalek" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077" +dependencies = [ + "curve25519-dalek", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 38c8c24f84..553b276573 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -27,3 +27,18 @@ codegen-units = 1 panic = 'abort' incremental = false overflow-checks = true + +[workspace.dependencies] +cosmwasm-crypto = "=1.0.0" +cosmwasm-derive = "=1.0.0" +cosmwasm-schema = "=1.0.0" +cosmwasm-std = "=1.0.0" +cosmwasm-storage = "=1.0.0" +cw-controllers = "=0.13.4" +cw-multi-test = "=0.13.4" +cw-storage-plus = "=0.13.4" +cw-utils = "=0.13.4" +cw2 = "=0.13.4" +cw3 = "=0.13.4" +cw3-fixed-multisig = "=0.13.4" +cw4 = "=0.13.4" diff --git a/contracts/coconut-bandwidth/Cargo.toml b/contracts/coconut-bandwidth/Cargo.toml index 3e3342bcd7..5064d41867 100644 --- a/contracts/coconut-bandwidth/Cargo.toml +++ b/contracts/coconut-bandwidth/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "coconut-bandwidth" +name = "nym-coconut-bandwidth" version = "0.1.0" edition = "2021" @@ -9,14 +9,14 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } -coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } -multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } +nym-bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } +nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } -cosmwasm-std = "1.0.0" -cosmwasm-storage = "1.0.0" -cw-storage-plus = "0.13.4" -cw-controllers = "0.13.4" +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cw-storage-plus = { workspace = true } +cw-controllers = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/coconut-bandwidth/src/contract.rs b/contracts/coconut-bandwidth/src/contract.rs index a5cfa5a415..6ed901a443 100644 --- a/contracts/coconut-bandwidth/src/contract.rs +++ b/contracts/coconut-bandwidth/src/contract.rs @@ -5,7 +5,9 @@ use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; -use coconut_bandwidth_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use nym_coconut_bandwidth_contract_common::msg::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, +}; use crate::error::ContractError; use crate::queries::{query_all_spent_credentials_paged, query_spent_credential}; diff --git a/contracts/coconut-bandwidth/src/queries.rs b/contracts/coconut-bandwidth/src/queries.rs index 1de10ef3a6..534696752f 100644 --- a/contracts/coconut-bandwidth/src/queries.rs +++ b/contracts/coconut-bandwidth/src/queries.rs @@ -1,11 +1,11 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::spend_credential::{ - PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse, -}; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; +use nym_coconut_bandwidth_contract_common::spend_credential::{ + PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse, +}; use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT}; diff --git a/contracts/coconut-bandwidth/src/storage.rs b/contracts/coconut-bandwidth/src/storage.rs index 549e64caaf..554c447672 100644 --- a/contracts/coconut-bandwidth/src/storage.rs +++ b/contracts/coconut-bandwidth/src/storage.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::spend_credential::SpendCredential; use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex}; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredential; // storage prefixes const SPEND_CREDENTIAL_PK_NAMESPACE: &str = "sc"; @@ -44,10 +44,10 @@ mod tests { use crate::storage::SpendCredential; use crate::support::tests::fixtures; use crate::support::tests::fixtures::TEST_MIX_DENOM; - use coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::Addr; use cosmwasm_std::Coin; + use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus; #[test] fn spend_credential_single_read_retrieval() { diff --git a/contracts/coconut-bandwidth/src/support/tests/fixtures.rs b/contracts/coconut-bandwidth/src/support/tests/fixtures.rs index c1c68a7f0c..1b5ac62dbe 100644 --- a/contracts/coconut-bandwidth/src/support/tests/fixtures.rs +++ b/contracts/coconut-bandwidth/src/support/tests/fixtures.rs @@ -1,8 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::spend_credential::{SpendCredential, SpendCredentialData}; use cosmwasm_std::{Addr, Coin}; +use nym_coconut_bandwidth_contract_common::spend_credential::{ + SpendCredential, SpendCredentialData, +}; pub const TEST_MIX_DENOM: &str = "unym"; diff --git a/contracts/coconut-bandwidth/src/support/tests/helpers.rs b/contracts/coconut-bandwidth/src/support/tests/helpers.rs index 31a4a6f279..d5a4f275b6 100644 --- a/contracts/coconut-bandwidth/src/support/tests/helpers.rs +++ b/contracts/coconut-bandwidth/src/support/tests/helpers.rs @@ -5,9 +5,9 @@ pub const MULTISIG_CONTRACT: &str = "multisig contract address"; pub const POOL_CONTRACT: &str = "mix pool contract address"; use crate::contract::instantiate; -use coconut_bandwidth_contract_common::msg::InstantiateMsg; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps}; +use nym_coconut_bandwidth_contract_common::msg::InstantiateMsg; use super::fixtures::TEST_MIX_DENOM; diff --git a/contracts/coconut-bandwidth/src/transactions.rs b/contracts/coconut-bandwidth/src/transactions.rs index 92d3a28bd1..9d7baf40b3 100644 --- a/contracts/coconut-bandwidth/src/transactions.rs +++ b/contracts/coconut-bandwidth/src/transactions.rs @@ -1,17 +1,17 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::spend_credential::{ +use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response}; +use nym_coconut_bandwidth_contract_common::spend_credential::{ to_cosmos_msg, SpendCredential, SpendCredentialData, }; -use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response}; use crate::error::ContractError; use crate::state::{ADMIN, CONFIG}; use crate::storage; -use coconut_bandwidth_contract_common::deposit::DepositData; -use coconut_bandwidth_contract_common::events::{ +use nym_coconut_bandwidth_contract_common::deposit::DepositData; +use nym_coconut_bandwidth_contract_common::events::{ DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, DEPOSIT_VALUE, }; @@ -113,11 +113,11 @@ mod tests { use super::*; use crate::support::tests::fixtures::spend_credential_data_fixture; use crate::support::tests::helpers::{self, MULTISIG_CONTRACT, POOL_CONTRACT}; - use coconut_bandwidth_contract_common::msg::ExecuteMsg; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{from_binary, Coin, CosmosMsg, WasmMsg}; use cw_controllers::AdminError; - use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; + use nym_coconut_bandwidth_contract_common::msg::ExecuteMsg; + use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; #[test] fn invalid_deposit() { diff --git a/contracts/coconut-dkg/Cargo.toml b/contracts/coconut-dkg/Cargo.toml index 4e8d1dcafc..f877df138d 100644 --- a/contracts/coconut-dkg/Cargo.toml +++ b/contracts/coconut-dkg/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "coconut-dkg" +name = "nym-coconut-dkg" version = "0.1.0" edition = "2021" @@ -9,21 +9,21 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } -cosmwasm-std = "1.0.0" -cosmwasm-storage = "1.0.0" -cw-storage-plus = "0.13.4" -cw-controllers = "0.13.4" -cw4 = { version = "0.13.4" } +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cw-storage-plus = { workspace = true } +cw-controllers = { workspace = true } +cw4 = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" [dev-dependencies] -cw-multi-test = { version = "0.13.4" } +cw-multi-test = { workspace = true } cw4-group = { path = "../multisig/cw4-group" } -group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } +nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } lazy_static = "1.4" rusty-fork = "0.3" diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index fb0138b47f..9b3b13212a 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -17,12 +17,12 @@ use crate::state::{State, MULTISIG, STATE}; use crate::verification_key_shares::queries::query_vk_shares_paged; use crate::verification_key_shares::transactions::try_commit_verification_key_share; use crate::verification_key_shares::transactions::try_verify_verification_key_share; -use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use coconut_dkg_common::types::{Epoch, EpochState}; use cosmwasm_std::{ entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, }; use cw4::Cw4Contract; +use nym_coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use nym_coconut_dkg_common::types::{Epoch, EpochState}; /// Instantiate the contract. /// @@ -136,13 +136,13 @@ mod tests { use super::*; use crate::support::tests::fixtures::TEST_MIX_DENOM; use crate::support::tests::helpers::{ADMIN_ADDRESS, MULTISIG_CONTRACT}; - use coconut_dkg_common::msg::ExecuteMsg::RegisterDealer; - use coconut_dkg_common::types::NodeIndex; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coins, Addr}; use cw4::Member; use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor}; - use group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; + use nym_coconut_dkg_common::msg::ExecuteMsg::RegisterDealer; + use nym_coconut_dkg_common::types::NodeIndex; + use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; fn instantiate_with_group(app: &mut App, members: &[Addr]) -> Addr { let group_code_id = app.store_code(Box::new(ContractWrapper::new( diff --git a/contracts/coconut-dkg/src/dealers/queries.rs b/contracts/coconut-dkg/src/dealers/queries.rs index b638629851..f7ef4209c1 100644 --- a/contracts/coconut-dkg/src/dealers/queries.rs +++ b/contracts/coconut-dkg/src/dealers/queries.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage::{self, IndexedDealersMap}; -use coconut_dkg_common::dealer::{DealerDetailsResponse, DealerType, PagedDealerResponse}; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; +use nym_coconut_dkg_common::dealer::{DealerDetailsResponse, DealerType, PagedDealerResponse}; fn query_dealers( deps: Deps<'_>, diff --git a/contracts/coconut-dkg/src/dealers/storage.rs b/contracts/coconut-dkg/src/dealers/storage.rs index 398193de23..b6c2c1f8bb 100644 --- a/contracts/coconut-dkg/src/dealers/storage.rs +++ b/contracts/coconut-dkg/src/dealers/storage.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::{DealerDetails, NodeIndex}; use cosmwasm_std::{Addr, StdResult, Storage}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, UniqueIndex}; +use nym_coconut_dkg_common::types::{DealerDetails, NodeIndex}; const CURRENT_DEALERS_PK: &str = "crd"; const PAST_DEALERS_PK: &str = "ptd"; diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index 9a4197dd8d..e7d019ee65 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -6,8 +6,8 @@ use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::STATE; -use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState}; use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; +use nym_coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState}; // currently we only require that // a) it's part of the signer group @@ -81,9 +81,9 @@ pub(crate) mod tests { use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::helpers; use crate::support::tests::helpers::{add_fixture_dealer, GROUP_MEMBERS}; - use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; use cosmwasm_std::testing::{mock_env, mock_info}; use cw4::Member; + use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; use rusty_fork::rusty_fork_test; rusty_fork_test! { diff --git a/contracts/coconut-dkg/src/dealings/queries.rs b/contracts/coconut-dkg/src/dealings/queries.rs index 0bf0452c14..a11b7bd4a5 100644 --- a/contracts/coconut-dkg/src/dealings/queries.rs +++ b/contracts/coconut-dkg/src/dealings/queries.rs @@ -3,10 +3,10 @@ use crate::dealings::storage; use crate::dealings::storage::DEALINGS_BYTES; -use coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse}; -use coconut_dkg_common::types::TOTAL_DEALINGS; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; +use nym_coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse}; +use nym_coconut_dkg_common::types::TOTAL_DEALINGS; pub fn query_dealings_paged( deps: Deps<'_>, diff --git a/contracts/coconut-dkg/src/dealings/storage.rs b/contracts/coconut-dkg/src/dealings/storage.rs index 26cb5b35db..0395fa8c74 100644 --- a/contracts/coconut-dkg/src/dealings/storage.rs +++ b/contracts/coconut-dkg/src/dealings/storage.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::{ContractSafeBytes, TOTAL_DEALINGS}; use cosmwasm_std::Addr; use cw_storage_plus::Map; +use nym_coconut_dkg_common::types::{ContractSafeBytes, TOTAL_DEALINGS}; pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2; pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1; diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index b9220cff9e..e5a9d9bb1b 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -6,8 +6,8 @@ use crate::dealings::storage::DEALINGS_BYTES; use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; -use coconut_dkg_common::types::{ContractSafeBytes, EpochState}; use cosmwasm_std::{DepsMut, MessageInfo, Response}; +use nym_coconut_dkg_common::types::{ContractSafeBytes, EpochState}; pub fn try_commit_dealings( deps: DepsMut<'_>, @@ -54,10 +54,10 @@ pub(crate) mod tests { use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture}; use crate::support::tests::helpers; use crate::support::tests::helpers::add_fixture_dealer; - use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::Addr; + use nym_coconut_dkg_common::dealer::DealerDetails; + use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; #[test] fn invalid_commit_dealing() { diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index c6aedb17e8..a7b968bf3f 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -3,8 +3,8 @@ use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD}; use crate::error::ContractError; -use coconut_dkg_common::types::{Epoch, InitialReplacementData}; use cosmwasm_std::Storage; +use nym_coconut_dkg_common::types::{Epoch, InitialReplacementData}; pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result { CURRENT_EPOCH @@ -28,8 +28,8 @@ pub(crate) fn query_initial_dealers( pub(crate) mod test { use super::*; use crate::support::tests::helpers::init_contract; - use coconut_dkg_common::types::{EpochState, TimeConfiguration}; use cosmwasm_std::testing::mock_env; + use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration}; #[test] fn query_state() { diff --git a/contracts/coconut-dkg/src/epoch_state/storage.rs b/contracts/coconut-dkg/src/epoch_state/storage.rs index 95536fd7c9..d146f5894a 100644 --- a/contracts/coconut-dkg/src/epoch_state/storage.rs +++ b/contracts/coconut-dkg/src/epoch_state/storage.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::{Epoch, InitialReplacementData}; use cw_storage_plus::Item; +use nym_coconut_dkg_common::types::{Epoch, InitialReplacementData}; pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch"); pub const THRESHOLD: Item = Item::new("threshold"); diff --git a/contracts/coconut-dkg/src/epoch_state/transactions.rs b/contracts/coconut-dkg/src/epoch_state/transactions.rs index 3a812f6437..b2fb55125e 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions.rs @@ -7,8 +7,8 @@ use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRES use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::STATE; -use coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData}; use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage}; +use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData}; fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> { THRESHOLD.remove(storage); @@ -182,12 +182,12 @@ pub(crate) mod tests { use crate::error::ContractError::EarlyEpochStateAdvancement; use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::helpers::{init_contract, GROUP_MEMBERS}; - use coconut_dkg_common::types::{ - ContractSafeBytes, DealerDetails, EpochState, TimeConfiguration, - }; use cosmwasm_std::testing::mock_env; use cosmwasm_std::Addr; use cw4::Member; + use nym_coconut_dkg_common::types::{ + ContractSafeBytes, DealerDetails, EpochState, TimeConfiguration, + }; use rusty_fork::rusty_fork_test; // Because of the global variable handling group, we need individual process for each test diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs index 78e91b6a14..cfbe82dfba 100644 --- a/contracts/coconut-dkg/src/epoch_state/utils.rs +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -3,8 +3,8 @@ use crate::epoch_state::storage::CURRENT_EPOCH; use crate::error::ContractError; -use coconut_dkg_common::types::EpochState; use cosmwasm_std::Storage; +use nym_coconut_dkg_common::types::EpochState; pub(crate) fn check_epoch_state( storage: &dyn Storage, @@ -25,8 +25,8 @@ pub(crate) fn check_epoch_state( pub(crate) mod test { use super::*; use crate::support::tests::helpers::init_contract; - use coconut_dkg_common::types::{Epoch, TimeConfiguration}; use cosmwasm_std::testing::mock_env; + use nym_coconut_dkg_common::types::{Epoch, TimeConfiguration}; #[test] pub fn check_state() { diff --git a/contracts/coconut-dkg/src/support/tests/fixtures.rs b/contracts/coconut-dkg/src/support/tests/fixtures.rs index 02a73b0ccf..1771ee1310 100644 --- a/contracts/coconut-dkg/src/support/tests/fixtures.rs +++ b/contracts/coconut-dkg/src/support/tests/fixtures.rs @@ -1,10 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::dealer::DealerDetails; -use coconut_dkg_common::types::ContractSafeBytes; -use coconut_dkg_common::verification_key::ContractVKShare; use cosmwasm_std::Addr; +use nym_coconut_dkg_common::dealer::DealerDetails; +use nym_coconut_dkg_common::types::ContractSafeBytes; +use nym_coconut_dkg_common::verification_key::ContractVKShare; pub const TEST_MIX_DENOM: &str = "unym"; diff --git a/contracts/coconut-dkg/src/support/tests/helpers.rs b/contracts/coconut-dkg/src/support/tests/helpers.rs index b806dd4989..f626b8bd89 100644 --- a/contracts/coconut-dkg/src/support/tests/helpers.rs +++ b/contracts/coconut-dkg/src/support/tests/helpers.rs @@ -3,8 +3,6 @@ use crate::contract::instantiate; use crate::dealers::storage::current_dealers; -use coconut_dkg_common::msg::InstantiateMsg; -use coconut_dkg_common::types::DealerDetails; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; use cosmwasm_std::{ from_binary, to_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps, @@ -12,6 +10,8 @@ use cosmwasm_std::{ }; use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse}; use lazy_static::lazy_static; +use nym_coconut_dkg_common::msg::InstantiateMsg; +use nym_coconut_dkg_common::types::DealerDetails; use std::sync::Mutex; use super::fixtures::TEST_MIX_DENOM; diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs index e3300330ea..958fd6b68f 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/queries.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -3,10 +3,10 @@ use crate::verification_key_shares::storage; use crate::verification_key_shares::storage::vk_shares; -use coconut_dkg_common::types::EpochId; -use coconut_dkg_common::verification_key::PagedVKSharesResponse; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse; pub fn query_vk_shares_paged( deps: Deps<'_>, diff --git a/contracts/coconut-dkg/src/verification_key_shares/storage.rs b/contracts/coconut-dkg/src/verification_key_shares/storage.rs index 13ecd5370a..b27605322d 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/storage.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/storage.rs @@ -4,10 +4,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE}; -use coconut_dkg_common::types::EpochId; -use coconut_dkg_common::verification_key::ContractVKShare; use cosmwasm_std::Addr; use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex}; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_dkg_common::verification_key::ContractVKShare; pub(crate) const VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT: u32 = 75; pub(crate) const VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT: u32 = 50; diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index 2d6002a003..677e4d70d6 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -8,9 +8,11 @@ use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::{MULTISIG, STATE}; use crate::verification_key_shares::storage::vk_shares; -use coconut_dkg_common::types::EpochState; -use coconut_dkg_common::verification_key::{to_cosmos_msg, ContractVKShare, VerificationKeyShare}; use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; +use nym_coconut_dkg_common::types::EpochState; +use nym_coconut_dkg_common::verification_key::{ + to_cosmos_msg, ContractVKShare, VerificationKeyShare, +}; pub fn try_commit_verification_key_share( deps: DepsMut<'_>, @@ -92,10 +94,10 @@ mod tests { use crate::epoch_state::transactions::advance_epoch_state; use crate::support::tests::helpers; use crate::support::tests::helpers::{add_fixture_dealer, MULTISIG_CONTRACT}; - use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::{EpochState, TimeConfiguration}; use cosmwasm_std::testing::{mock_env, mock_info}; use cw_controllers::AdminError; + use nym_coconut_dkg_common::dealer::DealerDetails; + use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration}; #[test] fn current_epoch_id() { diff --git a/contracts/coconut-test/Cargo.toml b/contracts/coconut-test/Cargo.toml index 7eaa724fd0..2a0eb5cb3f 100644 --- a/contracts/coconut-test/Cargo.toml +++ b/contracts/coconut-test/Cargo.toml @@ -2,31 +2,32 @@ name = "coconut-test" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } -coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } -coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } -multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } -group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } +nym-bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } +nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } +nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } -cosmwasm-std = "1.0.0" -cosmwasm-storage = "1.0.0" -cw3 = "0.13.4" -cw4 = "0.13.4" -cw-storage-plus = "0.13.4" -cw-controllers = "0.13.4" -cw-utils = "0.13.4" +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cw3 = { workspace = true } +cw4 = { workspace = true } +cw-storage-plus = { workspace = true } +cw-controllers = { workspace = true } +cw-utils = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" -coconut-bandwidth = { path = "../coconut-bandwidth" } -coconut-dkg = { path = "../coconut-dkg" } -cw-multi-test = { version = "0.13.4" } +nym-coconut-bandwidth = { path = "../coconut-bandwidth" } +nym-coconut-dkg = { path = "../coconut-dkg" } +cw-multi-test = { workspace = true } cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" } cw4-group = { path = "../multisig/cw4-group" } diff --git a/contracts/coconut-test/src/deposit_and_release.rs b/contracts/coconut-test/src/deposit_and_release.rs index 0212741261..374b14e3a6 100644 --- a/contracts/coconut-test/src/deposit_and_release.rs +++ b/contracts/coconut-test/src/deposit_and_release.rs @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::helpers::*; -use coconut_bandwidth::error::ContractError; -use coconut_bandwidth_contract_common::{ - deposit::DepositData, - msg::{ExecuteMsg, InstantiateMsg}, -}; use cosmwasm_std::{coins, Addr}; use cw_controllers::AdminError; use cw_multi_test::Executor; +use nym_coconut_bandwidth::error::ContractError; +use nym_coconut_bandwidth_contract_common::{ + deposit::DepositData, + msg::{ExecuteMsg, InstantiateMsg}, +}; const TEST_MIX_DENOM: &str = "unym"; diff --git a/contracts/coconut-test/src/helpers.rs b/contracts/coconut-test/src/helpers.rs index f5bd455d62..c679176177 100644 --- a/contracts/coconut-test/src/helpers.rs +++ b/contracts/coconut-test/src/helpers.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response}; use cw3_flex_multisig::state::CONFIG; use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper}; -use multisig_contract_common::error::ContractError; +use nym_multisig_contract_common::error::ContractError; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -43,18 +43,18 @@ pub fn mock_app(init_funds: &[Coin]) -> App { } pub fn contract_dkg() -> Box> { let contract = ContractWrapper::new( - coconut_dkg::contract::execute, - coconut_dkg::contract::instantiate, - coconut_dkg::contract::query, + nym_coconut_dkg::contract::execute, + nym_coconut_dkg::contract::instantiate, + nym_coconut_dkg::contract::query, ); Box::new(contract) } pub fn contract_bandwidth() -> Box> { let contract = ContractWrapper::new( - coconut_bandwidth::contract::execute, - coconut_bandwidth::contract::instantiate, - coconut_bandwidth::contract::query, + nym_coconut_bandwidth::contract::execute, + nym_coconut_bandwidth::contract::instantiate, + nym_coconut_bandwidth::contract::query, ); Box::new(contract) } diff --git a/contracts/coconut-test/src/spend_credential_creates_proposal.rs b/contracts/coconut-test/src/spend_credential_creates_proposal.rs index 7d68073b40..12742ef049 100644 --- a/contracts/coconut-test/src/spend_credential_creates_proposal.rs +++ b/contracts/coconut-test/src/spend_credential_creates_proposal.rs @@ -1,16 +1,16 @@ use crate::helpers::*; -use coconut_bandwidth::error::ContractError; -use coconut_bandwidth_contract_common::{ +use cosmwasm_std::{coins, Addr, Coin, Decimal}; +use cw_multi_test::Executor; +use cw_utils::{Duration, Threshold}; +use nym_coconut_bandwidth::error::ContractError; +use nym_coconut_bandwidth_contract_common::{ msg::{ ExecuteMsg as CoconutBandwidthExecuteMsg, InstantiateMsg as CoconutBandwidthInstantiateMsg, }, spend_credential::SpendCredentialData, }; -use cosmwasm_std::{coins, Addr, Coin, Decimal}; -use cw_multi_test::Executor; -use cw_utils::{Duration, Threshold}; -use group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; -use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; +use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; +use nym_multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; pub const TEST_COIN_DENOM: &str = "unym"; pub const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = diff --git a/contracts/coconut-test/src/submit_vk_creates_proposal.rs b/contracts/coconut-test/src/submit_vk_creates_proposal.rs index 884637f18c..540a6d8c02 100644 --- a/contracts/coconut-test/src/submit_vk_creates_proposal.rs +++ b/contracts/coconut-test/src/submit_vk_creates_proposal.rs @@ -7,19 +7,19 @@ use crate::helpers::{ use crate::spend_credential_creates_proposal::{ TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS, TEST_COCONUT_DKG_CONTRACT_ADDRESS, TEST_COIN_DENOM, }; -use coconut_dkg_common::msg::ExecuteMsg::{ - AdvanceEpochState, CommitVerificationKeyShare, RegisterDealer, -}; -use coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg; -use coconut_dkg_common::msg::QueryMsg::GetVerificationKeys; -use coconut_dkg_common::verification_key::PagedVKSharesResponse; use cosmwasm_std::{coins, Addr, Decimal}; use cw4::Member; use cw_multi_test::Executor; use cw_utils::{Duration, Threshold}; -use group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; -use multisig_contract_common::msg::ExecuteMsg::{Execute, Vote}; -use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; +use nym_coconut_dkg_common::msg::ExecuteMsg::{ + AdvanceEpochState, CommitVerificationKeyShare, RegisterDealer, +}; +use nym_coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg; +use nym_coconut_dkg_common::msg::QueryMsg::GetVerificationKeys; +use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse; +use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; +use nym_multisig_contract_common::msg::ExecuteMsg::{Execute, Vote}; +use nym_multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; #[test] fn dkg_proposal() { diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index 16a081adb6..9f180929d3 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-mixnet-contract" -version = "1.1.2" +version = "1.2.0" description = "Nym mixnet contract" edition = { workspace = true } authors = { workspace = true } @@ -22,22 +22,25 @@ name = "mixnet_contract" crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.2.0" } +mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" } +vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.3.0" } #nym-config = { path = "../../common/config"} -cosmwasm-std = "1.0.0" -cosmwasm-storage = "1.0.0" -cw-storage-plus = "0.13.4" +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cosmwasm-derive = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } bs58 = "0.4.0" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } time = { version = "0.3", features = ["macros"] } +semver = { version = "1.0.16", default-features = false } [dev-dependencies] -cosmwasm-schema = "1.0.0" +cosmwasm-schema = { workspace = true } rand_chacha = "0.2" #rand = "0.7" nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } @@ -48,3 +51,6 @@ vergen = { version = "=7.4.3", default-features = false, features = ["build", "g [features] default = [] contract-testing = ["mixnet-contract-common/contract-testing"] + +[profile.release] +overflow-checks = true diff --git a/contracts/mixnet/src/constants.rs b/contracts/mixnet/src/constants.rs index 882b89def4..504544acaa 100644 --- a/contracts/mixnet/src/constants.rs +++ b/contracts/mixnet/src/constants.rs @@ -47,6 +47,7 @@ pub(crate) const GATEWAYS_PK_NAMESPACE: &str = "gt"; pub(crate) const GATEWAYS_OWNER_IDX_NAMESPACE: &str = "gto"; pub(crate) const REWARDED_SET_KEY: &str = "rs"; +pub(crate) const CURRENT_EPOCH_STATUS_KEY: &str = "ces"; pub(crate) const CURRENT_INTERVAL_KEY: &str = "ci"; pub(crate) const EPOCH_EVENT_ID_COUNTER_KEY: &str = "eic"; pub(crate) const INTERVAL_EVENT_ID_COUNTER_KEY: &str = "iic"; diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index ade53d7d89..923b7614b5 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -1,5 +1,6 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + use crate::constants::{INITIAL_GATEWAY_PLEDGE_AMOUNT, INITIAL_MIXNODE_PLEDGE_AMOUNT}; use crate::interval::storage as interval_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; @@ -7,11 +8,17 @@ use crate::mixnodes::storage as mixnode_storage; use crate::rewards::storage as rewards_storage; use cosmwasm_std::{ entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, + StdError, }; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::{ ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg, }; +use semver::Version; + +// version info for migration info +const CONTRACT_NAME: &str = "crate:nym-mixnet-contract"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); fn default_initial_state( owner: Addr, @@ -50,11 +57,19 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> Result { + if msg.epochs_in_interval == 0 { + return Err(MixnetContractError::EpochsInIntervalZero); + } + + if msg.epoch_duration.as_secs() == 0 { + return Err(MixnetContractError::EpochDurationZero); + } + let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?; let vesting_contract_address = deps.api.addr_validate(&msg.vesting_contract_address)?; let state = default_initial_state( info.sender, - rewarding_validator_address, + rewarding_validator_address.clone(), msg.rewarding_denom, vesting_contract_address, ); @@ -64,10 +79,15 @@ pub fn instantiate( .initial_rewarding_params .into_rewarding_params(msg.epochs_in_interval)?; - interval_storage::initialise_storage(deps.storage, starting_interval)?; + interval_storage::initialise_storage( + deps.storage, + starting_interval, + rewarding_validator_address, + )?; mixnet_params_storage::initialise_storage(deps.storage, state)?; mixnode_storage::initialise_storage(deps.storage)?; rewards_storage::initialise_storage(deps.storage, reward_params)?; + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(Response::default()) } @@ -92,7 +112,9 @@ pub fn execute( ExecuteMsg::JoinFamily { signature, family_head, - } => crate::families::transactions::try_join_family(deps, info, signature, family_head), + } => { + crate::families::transactions::try_join_family(deps, info, None, signature, family_head) + } ExecuteMsg::LeaveFamily { signature, family_head, @@ -113,24 +135,26 @@ pub fn execute( ), ExecuteMsg::JoinFamilyOnBehalf { member_address, - signature, + node_identity_signature, + family_signature, family_head, } => crate::families::transactions::try_join_family_on_behalf( deps, info, member_address, - signature, + Some(node_identity_signature), + family_signature, family_head, ), ExecuteMsg::LeaveFamilyOnBehalf { member_address, - signature, + node_identity_signature, family_head, } => crate::families::transactions::try_leave_family_on_behalf( deps, info, member_address, - signature, + node_identity_signature, family_head, ), ExecuteMsg::KickFamilyMemberOnBehalf { @@ -189,6 +213,9 @@ pub fn execute( epoch_duration_secs, force_immediately, ), + ExecuteMsg::BeginEpochTransition {} => { + crate::interval::transactions::try_begin_epoch_transition(deps, env, info) + } ExecuteMsg::AdvanceCurrentEpoch { new_rewarded_set, // families_in_layer, @@ -369,6 +396,7 @@ pub fn query( QueryMsg::GetContractVersion {} => { to_binary(&crate::mixnet_contract_settings::queries::query_contract_version()) } + QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?), QueryMsg::GetStateParams {} => to_binary( &crate::mixnet_contract_settings::queries::query_contract_settings_params(deps)?, ), @@ -381,6 +409,9 @@ pub fn query( QueryMsg::GetRewardingParams {} => { to_binary(&crate::rewards::queries::query_rewarding_params(deps)?) } + QueryMsg::GetEpochStatus {} => { + to_binary(&crate::interval::queries::query_epoch_status(deps)?) + } QueryMsg::GetCurrentIntervalDetails {} => to_binary( &crate::interval::queries::query_current_interval_details(deps, env)?, ), @@ -547,6 +578,9 @@ pub fn query( limit, )?, ), + QueryMsg::GetNumberOfPendingEvents {} => to_binary( + &crate::interval::queries::query_number_of_pending_events(deps)?, + ), }; Ok(query_res?) @@ -558,6 +592,42 @@ pub fn migrate( _env: Env, msg: MigrateMsg, ) -> Result { + // this is the first migration that uses cw2 standard and thus the value in the storage doesn't yet exist + // set it instead. + if matches!( + cw2::get_contract_version(deps.storage), + Err(StdError::NotFound { .. }) + ) { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + crate::queued_migrations::create_epoch_status(deps.storage)?; + return Ok(Response::new()); + } + + // note: don't remove this particular bit of code as we have to ALWAYS check whether we have to update the stored version + let version: Version = CONTRACT_VERSION.parse().map_err(|error: semver::Error| { + MixnetContractError::SemVerFailure { + value: CONTRACT_VERSION.to_string(), + error_message: error.to_string(), + } + })?; + + let storage_version_raw = cw2::get_contract_version(deps.storage)?.version; + let storage_version: Version = + storage_version_raw + .parse() + .map_err(|error: semver::Error| MixnetContractError::SemVerFailure { + value: storage_version_raw, + error_message: error.to_string(), + })?; + + if storage_version < version { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // If state structure changed in any contract version in the way migration is needed, it + // should occur here + crate::queued_migrations::create_epoch_status(deps.storage)?; + } + // 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 diff --git a/contracts/mixnet/src/delegations/helpers.rs b/contracts/mixnet/src/delegations/helpers.rs index b21e0d9ba4..3991711a06 100644 --- a/contracts/mixnet/src/delegations/helpers.rs +++ b/contracts/mixnet/src/delegations/helpers.rs @@ -38,10 +38,10 @@ mod tests { test.add_immediate_delegation(delegator, og_amount, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - let dist1 = test.reward_with_distribution(mix_id, performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + let dist1 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); let mix_rewarding = test.mix_rewarding(mix_id); let delegation = test.delegation(mix_id, delegator, &None); diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index 271a2c190d..ae505f6986 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -5,7 +5,9 @@ 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::validate_delegation_stake; +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 mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ @@ -30,6 +32,8 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf( mix_id: MixId, delegate: String, ) -> Result { + 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)) } @@ -42,6 +46,9 @@ pub(crate) fn _try_delegate_to_mixnode( amount: Vec, proxy: Option, ) -> Result { + // delegation is only allowed if the epoch is currently not in the process of being advanced + ensure_epoch_in_progress_state(deps.storage)?; + // 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( @@ -89,6 +96,8 @@ pub(crate) fn try_remove_delegation_from_mixnode_on_behalf( mix_id: MixId, delegate: String, ) -> Result { + 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)) } @@ -100,6 +109,9 @@ pub(crate) fn _try_remove_delegation_from_mixnode( delegate: Addr, proxy: Option, ) -> Result { + // 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()); @@ -140,6 +152,40 @@ mod tests { use crate::support::tests::test_helpers::TestSetup; use cosmwasm_std::testing::mock_info; use cosmwasm_std::{coin, Decimal}; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let env = test.env(); + + let owner = "delegator"; + let mix_id = test.add_dummy_mixnode("mix-owner", None); + let sender = mock_info(owner, &[coin(50_000_000, TEST_COIN_DENOM)]); + + let res = try_delegate_to_mixnode(test.deps_mut(), env.clone(), sender, mix_id); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } #[test] fn can_only_be_done_towards_an_existing_mixnode() { @@ -353,6 +399,35 @@ 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 = "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 + } + ) + } } #[cfg(test)] @@ -363,6 +438,42 @@ mod tests { use crate::support::tests::test_helpers::TestSetup; use cosmwasm_std::coin; use cosmwasm_std::testing::mock_info; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let mix_id = test.add_dummy_mixnode("owner", None); + test.add_immediate_delegation("foomp", 1000u32, mix_id); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let env = test.env(); + let res = try_remove_delegation_from_mixnode( + test.deps_mut(), + env.clone(), + mock_info("sender", &[]), + mix_id, + ); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } #[test] fn cannot_be_performed_if_delegation_never_existed() { @@ -462,5 +573,40 @@ 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 + } + ) + } } } diff --git a/contracts/mixnet/src/families/storage.rs b/contracts/mixnet/src/families/storage.rs index 2a99f9054e..a60df7a87d 100644 --- a/contracts/mixnet/src/families/storage.rs +++ b/contracts/mixnet/src/families/storage.rs @@ -81,14 +81,13 @@ pub fn remove_family_member(store: &mut dyn Storage, member: IdentityKeyRef<'_>) MEMBERS.remove(store, member.to_string()) } -#[allow(dead_code)] pub fn is_family_member( store: &dyn Storage, f: &Family, member: IdentityKeyRef<'_>, ) -> Result { - let m = get_members(f, store)?; - Ok(m.contains(member)) + let existing_head = MEMBERS.may_load(store, member.to_owned())?; + Ok(existing_head.as_ref() == Some(f.head())) } pub fn is_any_member( diff --git a/contracts/mixnet/src/families/transactions.rs b/contracts/mixnet/src/families/transactions.rs index 7f9412f1f2..e392b62dea 100644 --- a/contracts/mixnet/src/families/transactions.rs +++ b/contracts/mixnet/src/families/transactions.rs @@ -1,5 +1,6 @@ use crate::support::helpers::{ - ensure_bonded, validate_family_signature, validate_node_identity_signature, + ensure_bonded, ensure_sent_by_vesting_contract, validate_family_signature, + validate_node_identity_signature, }; use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; @@ -28,6 +29,8 @@ pub fn try_create_family_on_behalf( owner_signature: String, label: &str, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let owner_address = deps.api.addr_validate(&owner_address)?; _try_create_family( deps, @@ -77,29 +80,47 @@ fn _try_create_family( pub fn try_join_family( deps: DepsMut, info: MessageInfo, - signature: String, + // Required for proxy joining + node_identity_signature: Option, + family_signature: String, family_head: IdentityKey, ) -> Result { let family_head = FamilyHead::new(&family_head); - _try_join_family(deps, &info.sender, signature, family_head) + _try_join_family( + deps, + &info.sender, + node_identity_signature, + family_signature, + family_head, + ) } pub fn try_join_family_on_behalf( deps: DepsMut, - _info: MessageInfo, + info: MessageInfo, member_address: String, - signature: String, + node_identity_signature: Option, + family_signature: String, family_head: IdentityKey, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let member_address = deps.api.addr_validate(&member_address)?; let family_head = FamilyHead::new(&family_head); - _try_join_family(deps, &member_address, signature, family_head) + _try_join_family( + deps, + &member_address, + node_identity_signature, + family_signature, + family_head, + ) } fn _try_join_family( deps: DepsMut, owner: &Addr, - signature: String, + node_identity_signature: Option, + family_signature: String, family_head: FamilyHead, ) -> Result { let existing_bond = crate::mixnodes::storage::mixnode_bonds() @@ -126,10 +147,19 @@ fn _try_join_family( )); } + if let Some(node_identity_signature) = node_identity_signature { + validate_node_identity_signature( + deps.as_ref(), + owner, + &node_identity_signature, + existing_bond.identity(), + )?; + } + validate_family_signature( deps.as_ref(), existing_bond.identity(), - &signature, + &family_signature, family_head.identity(), )?; @@ -152,20 +182,22 @@ pub fn try_leave_family( pub fn try_leave_family_on_behalf( deps: DepsMut, - _info: MessageInfo, + info: MessageInfo, member_address: String, - signature: String, + node_family_signature: String, family_head: IdentityKey, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let family_head = FamilyHead::new(&family_head); let member_address = deps.api.addr_validate(&member_address)?; - _try_leave_family(deps, &member_address, signature, family_head) + _try_leave_family(deps, &member_address, node_family_signature, family_head) } fn _try_leave_family( deps: DepsMut, owner: &Addr, - signature: String, + node_family_signature: String, family_head: FamilyHead, ) -> Result { let existing_bond = crate::mixnodes::storage::mixnode_bonds() @@ -194,11 +226,11 @@ fn _try_leave_family( }); } - validate_family_signature( + validate_node_identity_signature( deps.as_ref(), + owner, + &node_family_signature, existing_bond.identity(), - &signature, - family_head.identity(), )?; remove_family_member(deps.storage, existing_bond.identity()); @@ -217,11 +249,13 @@ pub fn try_head_kick_member( pub fn try_head_kick_member_on_behalf( deps: DepsMut, - _info: MessageInfo, + info: MessageInfo, head_address: String, owner_signature: String, member: IdentityKeyRef, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let head_address = deps.api.addr_validate(&head_address)?; _try_head_kick_member(deps, &head_address, owner_signature, member) } @@ -312,7 +346,7 @@ mod test { deps.as_mut(), env.clone(), mock_info(malicious_head, &[minimum_pledge.clone()]), - malicious_mixnode.clone(), + malicious_mixnode, cost_params.clone(), malicious_sig.clone(), ) @@ -320,28 +354,22 @@ mod test { crate::mixnodes::transactions::try_add_mixnode( deps.as_mut(), - env.clone(), - mock_info(member, &[minimum_pledge.clone()]), + env, + mock_info(member, &[minimum_pledge]), member_mixnode.clone(), - cost_params.clone(), + cost_params, member_sig.clone(), ) .unwrap(); - try_create_family( - deps.as_mut(), - mock_info(head.clone(), &[]), - head_sig.clone(), - "test", - ) - .unwrap(); + try_create_family(deps.as_mut(), mock_info(head, &[]), head_sig, "test").unwrap(); let family_head = FamilyHead::new(&head_mixnode.identity_key); assert!(get_family(&family_head, &deps.storage).is_ok()); let nope = try_create_family( deps.as_mut(), - mock_info(malicious_head.clone(), &[]), - malicious_sig.clone(), + mock_info(malicious_head, &[]), + malicious_sig, "test", ); @@ -368,6 +396,7 @@ mod test { try_join_family( deps.as_mut(), mock_info(member, &[]), + Some(member_sig.clone()), join_signature.clone(), head_mixnode.identity_key.clone(), ) @@ -380,7 +409,7 @@ mod test { try_leave_family( deps.as_mut(), mock_info(member, &[]), - join_signature.clone(), + member_sig.clone(), head_mixnode.identity_key.clone(), ) .unwrap(); @@ -391,6 +420,7 @@ mod test { try_join_family( deps.as_mut(), mock_info(member, &[]), + Some(member_sig.clone()), join_signature.clone(), head_mixnode.identity_key.clone(), ) @@ -411,4 +441,201 @@ mod test { // let family = get_family(&family_head, &deps.storage).unwrap(); // assert!(!is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap()); } + + #[cfg(test)] + mod creating_family { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + + #[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 (_, keypair) = test.add_dummy_mixnode_with_keypair(head, None); + let sig = keypair.private_key().sign_text(head); + + let res = try_create_family_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + head.to_string(), + sig, + "label", + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + } + + #[cfg(test)] + mod joining_family { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + + #[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); + + // TODO: those signatures are WRONG and have to be c hanged + let join_signature = head_keys + .private_key() + .sign_text(&member_keys.public_key().to_base58_string()); + + let member_sig = member_keys.private_key().sign_text(new_member); + + let head_identity = head_keys.public_key().to_base58_string(); + let res = try_join_family_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + new_member.to_string(), + Some(member_sig), + join_signature, + head_identity, + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + } + + #[cfg(test)] + mod leaving_family { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + + #[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); + + // TODO: those signatures are WRONG and have to be changed + let join_signature = head_keys + .private_key() + .sign_text(&member_keys.public_key().to_base58_string()); + + let member_sig = member_keys.private_key().sign_text(new_member); + + let head_identity = head_keys.public_key().to_base58_string(); + try_join_family_on_behalf( + test.deps_mut(), + mock_info(vesting_contract.as_ref(), &[]), + new_member.to_string(), + Some(member_sig.clone()), + join_signature, + head_identity, + ) + .unwrap(); + + let leave_signature = member_sig; + let res = try_leave_family_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + new_member.to_string(), + leave_signature, + head_keys.public_key().to_base58_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + } + + #[cfg(test)] + mod kicking_family_member { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + + #[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); + + // TODO: those signatures are WRONG and have to be c hanged + let join_signature = head_keys + .private_key() + .sign_text(&member_keys.public_key().to_base58_string()); + + let member_sig = member_keys.private_key().sign_text(new_member); + + let head_identity = head_keys.public_key().to_base58_string(); + try_join_family_on_behalf( + test.deps_mut(), + mock_info(vesting_contract.as_ref(), &[]), + new_member.to_string(), + Some(member_sig.clone()), + join_signature, + head_identity, + ) + .unwrap(); + + // TODO: a completely wrong signature is being used + let res = try_head_kick_member_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + head.to_string(), + head_keys.private_key().sign_text(head), + &member_keys.public_key().to_base58_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + } } diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index dc53817987..2078b0a180 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -4,7 +4,8 @@ use super::storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::support::helpers::{ - ensure_no_existing_bond, validate_node_identity_signature, validate_pledge, + ensure_no_existing_bond, ensure_sent_by_vesting_contract, validate_node_identity_signature, + validate_pledge, }; use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; @@ -38,6 +39,8 @@ pub fn try_add_gateway_on_behalf( owner: String, owner_signature: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_add_gateway( @@ -65,7 +68,7 @@ pub(crate) fn _try_add_gateway( let pledge = validate_pledge(pledge, minimum_pledge)?; // if the client has an active bonded mixnode or gateway, don't allow bonding - ensure_no_existing_bond(deps.storage, &owner)?; + ensure_no_existing_bond(&owner, deps.storage)?; // check if somebody else has already bonded a gateway with this identity if let Some(existing_bond) = @@ -110,6 +113,8 @@ pub fn try_remove_gateway_on_behalf( info: MessageInfo, owner: String, ) -> Result { + 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)) @@ -178,11 +183,14 @@ pub(crate) fn _try_remove_gateway( #[cfg(test)] pub mod tests { use crate::contract::execute; - use crate::gateways::transactions::try_add_gateway; + use crate::gateways::transactions::{ + try_add_gateway, try_add_gateway_on_behalf, try_remove_gateway_on_behalf, + }; use crate::interval::pending_events; use crate::mixnet_contract_settings::storage::minimum_gateway_pledge; use crate::support::tests; - use crate::support::tests::fixtures::TEST_COIN_DENOM; + use crate::support::tests::fixtures::{good_gateway_pledge, TEST_COIN_DENOM}; + use crate::support::tests::test_helpers::TestSetup; use crate::support::tests::{fixtures, test_helpers}; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coin, Addr, BankMsg, Response, Uint128}; @@ -280,6 +288,36 @@ pub mod tests { assert!(result.is_ok()); } + #[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_helpers::gateway_with_signature(&mut test.rng, owner); + + 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 deps = test_helpers::init_contract(); @@ -367,4 +405,31 @@ pub mod tests { assert_eq!(1, gateway_bonds.len()); assert_eq!(&Addr::unchecked("bob"), gateway_bonds[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 + } + ) + } } diff --git a/contracts/mixnet/src/interval/pending_events.rs b/contracts/mixnet/src/interval/pending_events.rs index b242944988..323107d71e 100644 --- a/contracts/mixnet/src/interval/pending_events.rs +++ b/contracts/mixnet/src/interval/pending_events.rs @@ -5,12 +5,11 @@ use crate::delegations; use crate::delegations::storage as delegations_storage; use crate::interval::helpers::change_interval_config; use crate::interval::storage; -use crate::mixnet_contract_settings::storage as mixnet_params_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; -use cosmwasm_std::{wasm_execute, Addr, Coin, DepsMut, Env, Response}; +use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking}; +use cosmwasm_std::{Addr, Coin, DepsMut, Env, Response}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ new_active_set_update_event, new_delegation_event, new_delegation_on_unbonded_node_event, @@ -24,7 +23,6 @@ use mixnet_contract_common::pending_events::{ }; use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate; use mixnet_contract_common::{BlockHeight, Delegation, MixId}; -use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; pub(crate) trait ContractExecutableEvent { // note: the error only means a HARD error like we failed to read from storage. @@ -56,27 +54,20 @@ 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 mut response = Response::new().add_message(return_tokens).add_event( - new_delegation_on_unbonded_node_event(&owner, &proxy, mix_id), - ); - - 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::TrackUndelegation { - owner: owner.into_string(), - mix_id, - amount, - }; - - let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_undelegate_message); - } - } + 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, + )?; return Ok(response); } @@ -163,26 +154,18 @@ 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 mut response = Response::new() + let response = Response::new() .add_message(return_tokens) - .add_event(new_undelegation_event(created_at, &owner, &proxy, mix_id)); - - 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::TrackUndelegation { - owner: owner.into_string(), - mix_id, - amount: tokens_to_return, - }; - - let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_undelegate_message); - } - } + .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, + )?; Ok(response) } @@ -219,24 +202,15 @@ pub(crate) fn unbond_mixnode( // decrement the associated layer count cleanup_post_unbond_mixnode_storage(deps.storage, env, &node_details)?; - let mut response = Response::new() + let response = Response::new() .add_message(return_tokens) - .add_event(new_mixnode_unbonding_event(created_at, mix_id)); - - 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::TrackUnbondMixnode { - owner: owner.clone().into_string(), - amount: tokens, - }; - - let track_unbond_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_unbond_message); - } - } + .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, + )?; Ok(response) } @@ -429,6 +403,7 @@ mod tests { use cosmwasm_std::Decimal; use mixnet_contract_common::Percent; use std::time::Duration; + use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; // note that authorization and basic validation has already been performed for all of those // before being pushed onto the event queues @@ -628,19 +603,31 @@ mod tests { let delegation_coin_new = coin(delegation_new, TEST_COIN_DENOM); // perform some rewarding here to advance the unit delegation beyond the initial value - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let owner = "delegator"; test.add_immediate_delegation(owner, delegation_og, mix_id); test.skip_to_next_epoch_end(); - let dist1 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let storage_key = Delegation::generate_storage_key(mix_id, &Addr::unchecked(owner), None); @@ -699,11 +686,17 @@ mod tests { let delegation_coin = coin(120_000_000u128, TEST_COIN_DENOM); // perform some rewarding here to advance the unit delegation beyond the initial value - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let storage_key = Delegation::generate_storage_key(mix_id, &Addr::unchecked(owner), None); @@ -749,21 +742,19 @@ mod tests { let delegation = 120_000_000u128; let delegation_coin = coin(delegation, TEST_COIN_DENOM); - let owner1 = "delegator1"; - let owner2 = "delegator2"; + let owner = "delegator"; let env = test.env(); unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap(); let vesting_contract = test.vesting_contract(); - let dummy_proxy = Addr::unchecked("not-vesting-contract"); // for a fresh delegation, nothing was added to the storage either let res_vesting = delegate( test.deps_mut(), &env, 123, - Addr::unchecked(owner1), + Addr::unchecked(owner), mix_id, delegation_coin.clone(), Some(vesting_contract.clone()), @@ -771,7 +762,7 @@ mod tests { .unwrap(); let storage_key = Delegation::generate_storage_key( mix_id, - &Addr::unchecked(owner1), + &Addr::unchecked(owner), Some(vesting_contract.clone()).as_ref(), ); assert!(delegations_storage::delegations() @@ -795,7 +786,7 @@ mod tests { found_track = true; assert_eq!(contract_addr, vesting_contract.as_str()); let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation { - owner: owner1.to_string(), + owner: owner.to_string(), mix_id, amount: delegation_coin.clone(), }) @@ -805,37 +796,42 @@ mod tests { } } 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(owner1), + Addr::unchecked(owner), mix_id, - delegation_coin.clone(), + delegation_coin, Some(dummy_proxy.clone()), ) - .unwrap(); - let storage_key = Delegation::generate_storage_key( - mix_id, - &Addr::unchecked(owner2), - Some(dummy_proxy.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_other_proxy).unwrap(); - assert_eq!(receiver, dummy_proxy.as_str()); - assert_eq!(sent_amount[0], delegation_coin); + .unwrap_err(); - // no track messages here! - for msg in &res_other_proxy.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { .. }) = &msg.msg { - panic!("we shouldn't have attempted to call any other contract!") + assert_eq!( + res_other_proxy, + MixnetContractError::ProxyIsNotVestingContract { + received: dummy_proxy, + vesting_contract } - } + ); } } @@ -885,18 +881,30 @@ mod tests { let delegation = 120_000_000u128; // perform some rewarding here to advance the unit delegation beyond the initial value - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.add_immediate_delegation(owner, delegation, mix_id); test.skip_to_next_epoch_end(); - let dist1 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let expected_reward = dist1.delegates + dist2.delegates; let truncated_reward = truncate_reward_amount(expected_reward); @@ -930,37 +938,23 @@ mod tests { let delegation = 120_000_000u128; let delegation_coin = coin(delegation, TEST_COIN_DENOM); - let owner1 = "delegator1"; - let owner2 = "delegator2"; + let owner = "delegator"; let vesting_contract = test.vesting_contract(); - let dummy_proxy = Addr::unchecked("not-vesting-contract"); - test.add_immediate_delegation_with_proxy( - owner1, - delegation, - mix_id, - vesting_contract.clone(), - ); - test.add_immediate_delegation_with_proxy( - owner2, - delegation, - mix_id, - dummy_proxy.clone(), - ); + test.add_immediate_delegation_with_legal_proxy(owner, delegation, mix_id); - // for a fresh delegation, nothing was added to the storage either let res_vesting = undelegate( test.deps_mut(), 123, - Addr::unchecked(owner1), + Addr::unchecked(owner), mix_id, Some(vesting_contract.clone()), ) .unwrap(); let storage_key = Delegation::generate_storage_key( mix_id, - &Addr::unchecked(owner1), + &Addr::unchecked(owner), Some(vesting_contract.clone()).as_ref(), ); assert!(delegations_storage::delegations() @@ -985,7 +979,7 @@ mod tests { found_track = true; assert_eq!(contract_addr, vesting_contract.as_str()); let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation { - owner: owner1.to_string(), + owner: owner.to_string(), mix_id, amount: delegation_coin.clone(), }) @@ -995,35 +989,41 @@ mod tests { } } 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(owner2), + Addr::unchecked(owner), mix_id, Some(dummy_proxy.clone()), ) - .unwrap(); - let storage_key = Delegation::generate_storage_key( - mix_id, - &Addr::unchecked(owner2), - Some(dummy_proxy.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_other_proxy).unwrap(); - assert_eq!(receiver, dummy_proxy.as_str()); - assert_eq!(sent_amount[0], delegation_coin); - - // no track messages here! - for msg in &res_other_proxy.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { .. }) = &msg.msg { - panic!("we shouldn't have attempted to call any other contract!") + .unwrap_err(); + assert_eq!( + res_other_proxy, + MixnetContractError::ProxyIsNotVestingContract { + received: dummy_proxy, + vesting_contract } - } + ); } } @@ -1062,11 +1062,17 @@ mod tests { .unwrap(); let layer = mix_details.layer; - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - let dist1 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let expected_reward = dist1.operator + dist2.operator; let truncated_reward = truncate_reward_amount(expected_reward); @@ -1109,19 +1115,14 @@ mod tests { let mut test = TestSetup::new(); let vesting_contract = test.vesting_contract(); - let dummy_proxy = Addr::unchecked("not-vesting-contract"); let pledge = Uint128::new(250_000_000); let pledge_coin = coin(250_000_000, TEST_COIN_DENOM); - let owner1 = "mix-owner1"; - let owner2 = "mix-owner2"; - let mix_id_vesting = - test.add_dummy_mixnode_with_proxy(owner1, Some(pledge), vesting_contract.clone()); - let mix_id_other_proxy = - test.add_dummy_mixnode_with_proxy(owner2, Some(pledge), dummy_proxy.clone()); + let owner = "mix-owner1"; + let mix_id_vesting = test.add_dummy_mixnode_with_legal_proxy(owner, Some(pledge)); let env = test.env(); - let res_vesting = unbond_mixnode(test.deps_mut(), &env, 123, mix_id_vesting).unwrap(); + 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) @@ -1129,13 +1130,13 @@ mod tests { .is_none()); // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap(); + 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_vesting.messages { + for msg in &res.messages { if let CosmosMsg::Wasm(WasmMsg::Execute { contract_addr, msg, @@ -1145,7 +1146,7 @@ mod tests { found_track = true; assert_eq!(contract_addr, vesting_contract.as_str()); let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUnbondMixnode { - owner: owner1.to_string(), + owner: owner.to_string(), amount: pledge_coin.clone(), }) .unwrap(); @@ -1154,24 +1155,32 @@ mod tests { } } 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_other_proxy).unwrap(); - assert!(mixnodes_storage::mixnode_bonds() - .may_load(test.deps().storage, mix_id_other_proxy) - .unwrap() - .is_none()); - // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res_other_proxy).unwrap(); - assert_eq!(receiver, dummy_proxy.as_str()); - assert_eq!(sent_amount[0], pledge_coin); - - // no track messages here! - for msg in &res_other_proxy.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { .. }) = &msg.msg { - panic!("we shouldn't have attempted to call any other contract!") + 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 } - } + ); } } @@ -1244,12 +1253,16 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111u128, mix_id_full_pledge); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); - let dist1 = - test.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); - let dist2 = - test.reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id_full_pledge, + test_helpers::performance(100.0), + ); assert_eq!(dist1, dist2) } @@ -1267,10 +1280,12 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge]); + test.force_change_rewarded_set(vec![mix_id_repledge]); - let dist = - test.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); let increase = test.coin(pledge2.u128()); increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap(); @@ -1306,15 +1321,19 @@ mod tests { ); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); // go through few epochs of rewarding for _ in 0..500 { test.skip_to_next_epoch_end(); - let dist1 = test - .reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); - let dist2 = test - .reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id_full_pledge, + test_helpers::performance(100.0), + ); assert_eq!(dist1, dist2) } @@ -1333,7 +1352,7 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge]); + test.force_change_rewarded_set(vec![mix_id_repledge]); let mut cumulative_op_reward = Decimal::zero(); let mut cumulative_del_reward = Decimal::zero(); @@ -1341,8 +1360,10 @@ mod tests { // go few epochs of rewarding before adding more pledge for _ in 0..500 { test.skip_to_next_epoch_end(); - let dist = test - .reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); cumulative_op_reward += dist.operator; cumulative_del_reward += dist.delegates; } @@ -1382,15 +1403,19 @@ mod tests { ); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); // go through few more epochs of rewarding for _ in 0..500 { test.skip_to_next_epoch_end(); - let dist1 = test - .reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); - let dist2 = test - .reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id_full_pledge, + test_helpers::performance(100.0), + ); assert_eq!(dist1, dist2) } diff --git a/contracts/mixnet/src/interval/queries.rs b/contracts/mixnet/src/interval/queries.rs index a0fe28ed28..e1f6a51a5d 100644 --- a/contracts/mixnet/src/interval/queries.rs +++ b/contracts/mixnet/src/interval/queries.rs @@ -1,4 +1,4 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::constants::{ @@ -9,12 +9,18 @@ use crate::constants::{ use crate::interval::storage; use cosmwasm_std::{Deps, Env, Order, StdResult}; use cw_storage_plus::Bound; +use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::pending_events::{PendingEpochEvent, PendingIntervalEvent}; use mixnet_contract_common::{ - CurrentIntervalResponse, EpochEventId, IntervalEventId, MixId, PagedRewardedSetResponse, - PendingEpochEventsResponse, PendingIntervalEventsResponse, + CurrentIntervalResponse, EpochEventId, EpochStatus, IntervalEventId, MixId, + NumberOfPendingEventsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse, + PendingIntervalEventsResponse, }; +pub fn query_epoch_status(deps: Deps<'_>) -> StdResult { + storage::current_epoch_status(deps.storage) +} + pub fn query_current_interval_details( deps: Deps<'_>, env: Env, @@ -106,10 +112,60 @@ pub fn query_pending_interval_events_paged( }) } +pub fn query_number_of_pending_events( + deps: Deps<'_>, +) -> Result { + let last_executed_epoch_id = storage::LAST_PROCESSED_EPOCH_EVENT.load(deps.storage)?; + let last_inserted_epoch_id = storage::EPOCH_EVENT_ID_COUNTER.load(deps.storage)?; + + let last_executed_interval_id = storage::LAST_PROCESSED_INTERVAL_EVENT.load(deps.storage)?; + let last_inserted_interval_id = storage::INTERVAL_EVENT_ID_COUNTER.load(deps.storage)?; + + Ok(NumberOfPendingEventsResponse { + epoch_events: last_inserted_epoch_id - last_executed_epoch_id, + interval_events: last_inserted_interval_id - last_executed_interval_id, + }) +} + #[cfg(test)] mod tests { use super::*; + use crate::support::tests::fixtures; use crate::support::tests::test_helpers::TestSetup; + use cosmwasm_std::Addr; + use mixnet_contract_common::{PendingEpochEventKind, PendingIntervalEventKind}; + use rand_chacha::rand_core::RngCore; + + fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) { + for _ in 0..n { + push_dummy_epoch_action(test) + } + } + + 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 env = test.env(); + storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap(); + } + + fn push_n_dummy_interval_actions(test: &mut TestSetup, n: usize) { + for _ in 0..n { + push_dummy_interval_action(test) + } + } + + fn push_dummy_interval_action(test: &mut TestSetup) { + let dummy_action = PendingIntervalEventKind::ChangeMixCostParams { + mix_id: test.rng.next_u32(), + new_costs: fixtures::mix_node_cost_params_fixture(), + }; + let env = test.env(); + storage::push_new_interval_event(test.deps_mut().storage, &env, dummy_action).unwrap(); + } #[test] fn querying_for_current_interval_details() { @@ -151,7 +207,7 @@ mod tests { fn set_rewarded_set_to_n_nodes(test: &mut TestSetup, n: usize) { let set = (1u32..).take(n).collect::>(); - test.update_rewarded_set(set) + test.force_change_rewarded_set(set) } #[test] @@ -237,25 +293,6 @@ mod tests { #[cfg(test)] mod pending_epoch_events { use super::*; - use cosmwasm_std::Addr; - use mixnet_contract_common::pending_events::PendingEpochEventKind; - use rand_chacha::rand_core::RngCore; - - fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) { - for _ in 0..n { - push_dummy_epoch_action(test) - } - } - - 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 env = test.env(); - storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap(); - } #[test] fn obeys_limits() { @@ -379,24 +416,6 @@ mod tests { #[cfg(test)] mod pending_interval_events { use super::*; - use crate::support::tests::fixtures; - use mixnet_contract_common::pending_events::PendingIntervalEventKind; - use rand_chacha::rand_core::RngCore; - - fn push_n_dummy_interval_actions(test: &mut TestSetup, n: usize) { - for _ in 0..n { - push_dummy_interval_action(test) - } - } - - fn push_dummy_interval_action(test: &mut TestSetup) { - let dummy_action = PendingIntervalEventKind::ChangeMixCostParams { - mix_id: test.rng.next_u32(), - new_costs: fixtures::mix_node_cost_params_fixture(), - }; - let env = test.env(); - storage::push_new_interval_event(test.deps_mut().storage, &env, dummy_action).unwrap(); - } #[test] fn obeys_limits() { @@ -520,4 +539,59 @@ mod tests { ) } } + + #[test] + fn querying_for_number_of_pending_events() { + let mut test = TestSetup::new(); + + // no events + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 0, + interval_events: 0 + }), + query_number_of_pending_events(test.deps()) + ); + + // add epoch event + push_dummy_epoch_action(&mut test); + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 1, + interval_events: 0 + }), + query_number_of_pending_events(test.deps()) + ); + + // add more epoch events + push_n_dummy_epoch_actions(&mut test, 42); + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 43, + interval_events: 0 + }), + query_number_of_pending_events(test.deps()) + ); + + // and now for interval... + // add interval event + push_dummy_interval_action(&mut test); + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 43, + interval_events: 1 + }), + query_number_of_pending_events(test.deps()) + ); + + // add more epoch events + push_n_dummy_interval_actions(&mut test, 42); + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 43, + interval_events: 43 + }), + query_number_of_pending_events(test.deps()) + ); + } } diff --git a/contracts/mixnet/src/interval/storage.rs b/contracts/mixnet/src/interval/storage.rs index 282876f5f8..4de46d47d2 100644 --- a/contracts/mixnet/src/interval/storage.rs +++ b/contracts/mixnet/src/interval/storage.rs @@ -1,21 +1,23 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::constants::{ - CURRENT_INTERVAL_KEY, EPOCH_EVENT_ID_COUNTER_KEY, INTERVAL_EVENT_ID_COUNTER_KEY, - LAST_EPOCH_EVENT_ID_KEY, LAST_INTERVAL_EVENT_ID_KEY, PENDING_EPOCH_EVENTS_NAMESPACE, - PENDING_INTERVAL_EVENTS_NAMESPACE, REWARDED_SET_KEY, + CURRENT_EPOCH_STATUS_KEY, CURRENT_INTERVAL_KEY, EPOCH_EVENT_ID_COUNTER_KEY, + INTERVAL_EVENT_ID_COUNTER_KEY, LAST_EPOCH_EVENT_ID_KEY, LAST_INTERVAL_EVENT_ID_KEY, + PENDING_EPOCH_EVENTS_NAMESPACE, PENDING_INTERVAL_EVENTS_NAMESPACE, REWARDED_SET_KEY, }; -use cosmwasm_std::{Env, Order, StdResult, Storage}; +use cosmwasm_std::{Addr, Env, Order, StdResult, Storage}; use cw_storage_plus::{Item, Map}; use mixnet_contract_common::pending_events::{ PendingEpochEventData, PendingEpochEventKind, PendingIntervalEventData, }; use mixnet_contract_common::{ - EpochEventId, Interval, IntervalEventId, MixId, PendingIntervalEventKind, RewardedSetNodeStatus, + EpochEventId, EpochStatus, Interval, IntervalEventId, MixId, PendingIntervalEventKind, + RewardedSetNodeStatus, }; use std::collections::HashMap; +pub(crate) const CURRENT_EPOCH_STATUS: Item<'_, EpochStatus> = Item::new(CURRENT_EPOCH_STATUS_KEY); pub(crate) const CURRENT_INTERVAL: Item<'_, Interval> = Item::new(CURRENT_INTERVAL_KEY); pub(crate) const REWARDED_SET: Map = Map::new(REWARDED_SET_KEY); @@ -39,6 +41,17 @@ pub(crate) const PENDING_EPOCH_EVENTS: Map pub(crate) const PENDING_INTERVAL_EVENTS: Map = Map::new(PENDING_INTERVAL_EVENTS_NAMESPACE); +pub(crate) fn current_epoch_status(storage: &dyn Storage) -> StdResult { + CURRENT_EPOCH_STATUS.load(storage) +} + +pub(crate) fn save_current_epoch_status( + storage: &mut dyn Storage, + status: &EpochStatus, +) -> StdResult<()> { + CURRENT_EPOCH_STATUS.save(storage, status) +} + pub(crate) fn current_interval(storage: &dyn Storage) -> StdResult { CURRENT_INTERVAL.load(storage) } @@ -69,6 +82,12 @@ pub(crate) fn push_new_epoch_event( env: &Env, event: PendingEpochEventKind, ) -> StdResult<()> { + // not included in non-test code as it messes with our return types as we expected `StdResult` + // from all storage-related operations. + // However, the callers MUST HAVE ensured the below invariant + #[cfg(test)] + crate::support::helpers::ensure_epoch_in_progress_state(storage).unwrap(); + let event_id = next_epoch_event_id_counter(storage)?; let event_data = event.attach_source_height(env.block.height); PENDING_EPOCH_EVENTS.save(storage, event_id, &event_data) @@ -79,6 +98,12 @@ pub(crate) fn push_new_interval_event( env: &Env, event: PendingIntervalEventKind, ) -> StdResult<()> { + // not included in non-test code as it messes with our return types as we expected `StdResult` + // from all storage-related operations. + // However, the callers MUST HAVE ensured the below invariant + #[cfg(test)] + crate::support::helpers::ensure_epoch_in_progress_state(storage).unwrap(); + let event_id = next_interval_event_id_counter(storage)?; let event_data = event.attach_source_height(env.block.height); PENDING_INTERVAL_EVENTS.save(storage, event_id, &event_data) @@ -130,12 +155,14 @@ pub(crate) fn update_rewarded_set( pub(crate) fn initialise_storage( storage: &mut dyn Storage, starting_interval: Interval, + rewarding_validator: Addr, ) -> StdResult<()> { save_interval(storage, &starting_interval)?; LAST_PROCESSED_EPOCH_EVENT.save(storage, &0)?; LAST_PROCESSED_INTERVAL_EVENT.save(storage, &0)?; EPOCH_EVENT_ID_COUNTER.save(storage, &0)?; - INTERVAL_EVENT_ID_COUNTER.save(storage, &0) + INTERVAL_EVENT_ID_COUNTER.save(storage, &0)?; + CURRENT_EPOCH_STATUS.save(storage, &EpochStatus::new(rewarding_validator)) } #[cfg(test)] diff --git a/contracts/mixnet/src/interval/transactions.rs b/contracts/mixnet/src/interval/transactions.rs index a55978e4e4..0392368389 100644 --- a/contracts/mixnet/src/interval/transactions.rs +++ b/contracts/mixnet/src/interval/transactions.rs @@ -1,4 +1,4 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::storage; @@ -8,16 +8,18 @@ use crate::interval::storage::push_new_interval_event; use crate::mixnodes::transactions::update_mixnode_layer; use crate::rewards; use crate::rewards::storage as rewards_storage; -use crate::support::helpers::{ensure_is_authorized, ensure_is_owner}; -use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Storage}; +use crate::support::helpers::{ + ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_is_owner, +}; +use cosmwasm_std::{DepsMut, Env, MessageInfo, Order, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ - new_advance_epoch_event, new_pending_epoch_events_execution_event, - new_pending_interval_config_update_event, new_pending_interval_events_execution_event, - new_reconcile_pending_events, + new_advance_epoch_event, new_epoch_transition_start_event, + new_pending_epoch_events_execution_event, new_pending_interval_config_update_event, + new_pending_interval_events_execution_event, new_reconcile_pending_events, }; use mixnet_contract_common::pending_events::PendingIntervalEventKind; -use mixnet_contract_common::{LayerAssignment, MixId}; +use mixnet_contract_common::{EpochState, EpochStatus, LayerAssignment, MixId}; use std::collections::BTreeSet; // those two should be called in separate tx (from advancing epoch), @@ -121,7 +123,8 @@ pub fn try_reconcile_epoch_events( // - somebody sneaks in some extra delegations // - the same person decides to pay the transaction fees and reconcile epoch events themselves // - the validator API distributes the rewards -> this new sneaky delegation is now included in reward calculation! - ensure_is_authorized(info.sender, deps.storage)?; + let mut current_epoch_status = ensure_can_advance_epoch(&info.sender, deps.storage)?; + current_epoch_status.ensure_is_in_event_reconciliation_state()?; let mut response = Response::new().add_event(new_reconcile_pending_events()); @@ -158,6 +161,24 @@ pub fn try_reconcile_epoch_events( .push(new_pending_interval_events_execution_event(executed)); } + // if there are no more events to clear, go into the next state + let pending_events = super::queries::query_number_of_pending_events(deps.as_ref())?; + // we can only progress if there are no epoch events AND if the interval has finished, that there are no interval events + let progress = if pending_events.epoch_events == 0 { + if interval.is_current_interval_over(&env) { + pending_events.interval_events == 0 + } else { + true + } + } else { + false + }; + + if progress { + current_epoch_status.state = EpochState::AdvancingEpoch; + storage::save_current_epoch_status(deps.storage, ¤t_epoch_status)?; + } + Ok(response) } @@ -199,17 +220,66 @@ fn update_rewarded_set( )?) } +pub fn try_begin_epoch_transition( + deps: DepsMut<'_>, + env: Env, + info: MessageInfo, +) -> Result { + // Only the rewarding validator(s) can attempt to advance epoch + ensure_is_authorized(&info.sender, deps.storage)?; + + // can't do pre-mature epoch transition... + let current_interval = storage::current_interval(deps.storage)?; + if !current_interval.is_current_epoch_over(&env) { + return Err(MixnetContractError::EpochInProgress { + current_block_time: env.block.time.seconds(), + epoch_start: current_interval.current_epoch_start_unix_timestamp(), + epoch_end: current_interval.current_epoch_end_unix_timestamp(), + }); + } + + // ensure some other validator (currently not a problem), hasn't already committed to epoch progression + ensure_epoch_in_progress_state(deps.storage)?; + + // Note: if at any point we decide to change our rewarded set to be few thousand nodes + // and the below call fails, we'll have to pass `last_node_in_rewarded_set` as an argument to this function + // and then verify whether the provided value is valid (by using range iterator on `REWARDED_SET` + // and checking if there are any other entries following the provided value) + let rewarded_set = storage::REWARDED_SET + .range(deps.storage, None, None, Order::Ascending) + .map(|kv| kv.map(|kv| kv.0)) + .collect::, _>>()?; + + // if there are no nodes to reward (i.e. empty rewarded set), we go straight into event reconciliation + let new_epoch_state = if let Some(last) = rewarded_set.last() { + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: *last, + } + } else { + EpochState::ReconcilingEvents + }; + + // progress into the first stage of epoch progression + let new_epoch_status = EpochStatus { + being_advanced_by: info.sender, + state: new_epoch_state, + }; + + storage::save_current_epoch_status(deps.storage, &new_epoch_status)?; + Ok(Response::new().add_event(new_epoch_transition_start_event(current_interval))) +} + pub fn try_advance_epoch( - mut deps: DepsMut<'_>, + deps: DepsMut<'_>, env: Env, info: MessageInfo, layer_assignments: Vec, expected_active_set_size: u32, ) -> Result { // Only rewarding validator can attempt to advance epoch - ensure_is_authorized(info.sender, deps.storage)?; - - let mut response = Response::new(); + let mut current_epoch_status = ensure_can_advance_epoch(&info.sender, deps.storage)?; + current_epoch_status.ensure_is_in_advancement_state()?; // we must make sure that we roll into new epoch / interval with up to date state // with no pending actions (like somebody wanting to update their profit margin) @@ -220,29 +290,10 @@ pub fn try_advance_epoch( epoch_start: current_interval.current_epoch_start_unix_timestamp(), epoch_end: current_interval.current_epoch_end_unix_timestamp(), }); - } else { - let (mut sub_response, executed) = - perform_pending_epoch_actions(deps.branch(), &env, None)?; - response.messages.append(&mut sub_response.messages); - response.attributes.append(&mut sub_response.attributes); - response.events.append(&mut sub_response.events); - response - .events - .push(new_pending_epoch_events_execution_event(executed)); } - // first clear epoch events queue and then touch the interval actions + // if the current interval is over, apply reward pool changes if current_interval.is_current_interval_over(&env) { - // the interval has finished -> we can change things such as the profit margin - let (mut sub_response, executed) = - perform_pending_interval_actions(deps.branch(), &env, None)?; - response.messages.append(&mut sub_response.messages); - response.attributes.append(&mut sub_response.attributes); - response.events.append(&mut sub_response.events); - response - .events - .push(new_pending_interval_events_execution_event(executed)); - // this one is a very important one! rewards::helpers::apply_reward_pool_changes(deps.storage)?; } @@ -260,7 +311,10 @@ pub fn try_advance_epoch( update_mixnode_layer(a.mix_id(), a.layer(), deps.storage)?; } - Ok(response.add_event(new_advance_epoch_event(updated_interval, num_nodes as u32))) + current_epoch_status.state = EpochState::InProgress; + storage::save_current_epoch_status(deps.storage, ¤t_epoch_status)?; + + Ok(Response::new().add_event(new_advance_epoch_event(updated_interval, num_nodes as u32))) } pub(crate) fn try_update_interval_config( @@ -273,6 +327,14 @@ pub(crate) fn try_update_interval_config( ) -> Result { ensure_is_owner(info.sender, deps.storage)?; + if epochs_in_interval == 0 { + return Err(MixnetContractError::EpochsInIntervalZero); + } + + if epoch_duration_secs == 0 { + return Err(MixnetContractError::EpochDurationZero); + } + let interval = storage::current_interval(deps.storage)?; if force_immediately || interval.is_current_interval_over(&env) { change_interval_config( @@ -283,6 +345,10 @@ pub(crate) fn try_update_interval_config( epoch_duration_secs, ) } else { + // changing interval config is only allowed if the epoch is currently not in the process of being advanced + // (unless the force flag was used) + ensure_epoch_in_progress_state(deps.storage)?; + // push the interval event let interval_event = PendingIntervalEventKind::UpdateIntervalConfig { epochs_in_interval, @@ -804,6 +870,136 @@ mod tests { } } + #[cfg(test)] + mod beginning_epoch_transition { + use super::*; + use cosmwasm_std::testing::mock_info; + + #[test] + fn returns_error_if_epoch_is_in_progress() { + let mut test = TestSetup::new(); + let env = test.env(); + let rewarding_validator = test.rewarding_validator(); + + let res = try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator); + assert!(matches!( + res, + Err(MixnetContractError::EpochInProgress { .. }) + )) + } + + #[test] + fn can_only_be_performed_if_in_progress_state() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + let res = try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator); + assert_eq!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { + current_state: bad_state + }) + ); + } + } + + #[test] + fn returns_error_if_not_performed_by_the_rewarding_validator() { + let mut test = TestSetup::new(); + let env = test.env(); + + test.skip_to_current_epoch_end(); + + let random = mock_info("alice", &[]); + let owner = test.owner(); + + let res = try_begin_epoch_transition(test.deps_mut(), env.clone(), random); + assert!(matches!(res, Err(MixnetContractError::Unauthorized))); + + let res = try_begin_epoch_transition(test.deps_mut(), env, owner); + assert!(matches!(res, Err(MixnetContractError::Unauthorized))); + } + + #[test] + fn returns_error_if_epoch_is_already_being_advanced() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + try_begin_epoch_transition(test.deps_mut(), env.clone(), rewarding_validator.clone()) + .unwrap(); + + let res = try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + + #[test] + fn epoch_state_is_correctly_updated_for_empty_rewarded_set() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::ReconcilingEvents, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + + #[test] + fn epoch_state_is_correctly_updated_for_nonempty_rewarded_set() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.force_change_rewarded_set(vec![1, 2, 3, 4, 5]); + test.skip_to_current_epoch_end(); + let env = test.env(); + + try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 5, + }, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + } + #[cfg(test)] mod reconciling_epoch_events { use super::*; @@ -816,12 +1012,140 @@ mod tests { use mixnet_contract_common::pending_events::PendingEpochEventKind; use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate; + #[test] + fn can_only_be_performed_if_in_reconciling_state() { + let bad_states = vec![ + EpochState::InProgress, + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + let res = + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None); + assert_eq!( + res, + Err(MixnetContractError::EpochNotInEventReconciliationState { + current_state: bad_state + }) + ); + } + } + + #[test] + fn epoch_state_is_correctly_updated_if_there_are_no_events() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + test.set_epoch_reconciliation_state(); + let env = test.env(); + + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::AdvancingEpoch, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + + #[test] + fn epoch_state_is_not_updated_if_some_events_are_cleared() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + push_n_dummy_epoch_actions(&mut test, 10); + push_n_dummy_interval_actions(&mut test, 10); + test.set_epoch_reconciliation_state(); + + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, Some(5)).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::ReconcilingEvents, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + + #[test] + fn epoch_state_is_correctly_updated_if_even_with_leftover_interval_events_if_interval_is_not_over( + ) { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + push_n_dummy_epoch_actions(&mut test, 10); + push_n_dummy_interval_actions(&mut test, 10); + test.set_epoch_reconciliation_state(); + + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::AdvancingEpoch, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + + #[test] + fn epoch_state_is_correctly_updated_if_all_events_are_cleared() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_interval_end(); + let env = test.env(); + + push_n_dummy_epoch_actions(&mut test, 10); + push_n_dummy_interval_actions(&mut test, 10); + test.set_epoch_reconciliation_state(); + + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::AdvancingEpoch, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + #[test] fn returns_error_if_epoch_is_in_progress() { let mut test = TestSetup::new(); let env = test.env(); let rewarding_validator = test.rewarding_validator(); + test.set_epoch_reconciliation_state(); let res = try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None); assert!(matches!( res, @@ -835,6 +1159,7 @@ mod tests { let env = test.env(); test.skip_to_current_epoch_end(); + test.set_epoch_reconciliation_state(); let random = mock_info("alice", &[]); let owner = test.owner(); @@ -853,6 +1178,7 @@ mod tests { push_n_dummy_epoch_actions(&mut test, 10); push_n_dummy_interval_actions(&mut test, 10); test.skip_to_current_epoch_end(); + test.set_epoch_reconciliation_state(); let env = test.env(); let rewarding_validator = test.rewarding_validator(); @@ -872,6 +1198,7 @@ mod tests { push_n_dummy_epoch_actions(&mut test, 10); push_n_dummy_interval_actions(&mut test, 10); test.skip_to_current_interval_end(); + test.set_epoch_reconciliation_state(); let rewarding_validator = test.rewarding_validator(); let env = test.env(); @@ -900,6 +1227,7 @@ mod tests { // all test cases are using the same one let rewarding_validator = test1.rewarding_validator(); + test1.set_epoch_reconciliation_state(); try_reconcile_epoch_events( test1.deps_mut(), env.clone(), @@ -948,6 +1276,7 @@ mod tests { assert!(epoch_events.is_empty()); assert!(interval_events.is_empty()); + test2.set_epoch_reconciliation_state(); try_reconcile_epoch_events( test2.deps_mut(), env.clone(), @@ -972,6 +1301,7 @@ mod tests { assert!(epoch_events.is_empty()); assert!(interval_events.is_empty()); + test3.set_epoch_reconciliation_state(); try_reconcile_epoch_events( test3.deps_mut(), env.clone(), @@ -984,6 +1314,7 @@ mod tests { assert!(epoch_events.is_empty()); assert!(interval_events.is_empty()); + test4.set_epoch_reconciliation_state(); try_reconcile_epoch_events(test4.deps_mut(), env, rewarding_validator, Some(100)) .unwrap(); let epoch_events = test4.pending_epoch_events(); @@ -1040,6 +1371,7 @@ mod tests { expected_events.push(new_pending_interval_events_execution_event(1)); test.skip_to_current_interval_end(); + test.set_epoch_reconciliation_state(); let env = test.env(); let rewarding_validator = test.rewarding_validator(); @@ -1142,15 +1474,97 @@ mod tests { use super::*; use crate::mixnodes::queries::query_mixnode_details; use crate::rewards::models::RewardPoolChange; - use crate::support::tests::fixtures::TEST_COIN_DENOM; use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{coin, coins, BankMsg, Decimal, Empty, SubMsg, Uint128}; - use mixnet_contract_common::events::{ - new_delegation_on_unbonded_node_event, new_rewarding_params_update_event, - }; + use cosmwasm_std::{Decimal, Uint128}; use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate; use mixnet_contract_common::{Layer, RewardedSetNodeStatus}; + #[test] + fn can_only_be_performed_if_in_advancing_epoch_state() { + let bad_states = vec![ + EpochState::InProgress, + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + let current_active_set = test.rewarding_params().active_set_size; + + test.skip_to_current_epoch_end(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + + let env = test.env(); + let sender = test.rewarding_validator(); + let res = try_advance_epoch( + test.deps_mut(), + env, + sender, + layer_assignments, + current_active_set, + ); + assert_eq!( + res, + Err(MixnetContractError::EpochNotInAdvancementState { + current_state: bad_state + }) + ); + } + } + + #[test] + fn epoch_state_is_correctly_updated() { + let mut test = TestSetup::new(); + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + let current_active_set = test.rewarding_params().active_set_size; + + test.skip_to_current_epoch_end(); + test.set_epoch_advancement_state(); + + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + + let env = test.env(); + let sender = test.rewarding_validator(); + try_advance_epoch( + test.deps_mut(), + env, + sender, + layer_assignments, + current_active_set, + ) + .unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::InProgress, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + #[test] fn can_only_be_performed_by_specified_rewarding_validator() { let mut test = TestSetup::new(); @@ -1161,6 +1575,7 @@ mod tests { let some_sender = mock_info("foomper", &[]); test.skip_to_current_epoch_end(); + test.set_epoch_advancement_state(); let layer_assignments = vec![ LayerAssignment::new(1, Layer::One), @@ -1188,13 +1603,14 @@ mod tests { layer_assignments, current_active_set, ); - println!("{:?}", res); assert!(res.is_ok()) } #[test] fn can_only_be_performed_if_epoch_is_over() { let mut test = TestSetup::new(); + test.set_epoch_advancement_state(); + let current_active_set = test.rewarding_params().active_set_size; test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); @@ -1241,6 +1657,7 @@ mod tests { // sanity check test.skip_to_current_epoch_end(); + let env = test.env(); let res = try_advance_epoch( test.deps_mut(), @@ -1252,171 +1669,11 @@ mod tests { assert!(res.is_ok()) } - #[test] - fn only_clears_epoch_events_if_interval_is_in_progress() { - let mut test = TestSetup::new(); - let current_active_set = test.rewarding_params().active_set_size; - - test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); - - push_n_dummy_epoch_actions(&mut test, 10); - push_n_dummy_interval_actions(&mut test, 10); - test.skip_to_current_epoch_end(); - - let layer_assignments = vec![ - LayerAssignment::new(1, Layer::One), - LayerAssignment::new(2, Layer::Two), - LayerAssignment::new(3, Layer::Three), - ]; - - let env = test.env(); - let sender = test.rewarding_validator(); - try_advance_epoch( - test.deps_mut(), - env, - sender, - layer_assignments, - current_active_set, - ) - .unwrap(); - - let epoch_events = test.pending_epoch_events(); - let interval_events = test.pending_interval_events(); - assert!(epoch_events.is_empty()); - assert_eq!(interval_events.len(), 10); - } - - #[test] - fn clears_both_epoch_and_interval_events_if_interval_has_finished() { - let mut test = TestSetup::new(); - let current_active_set = test.rewarding_params().active_set_size; - - test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); - - push_n_dummy_epoch_actions(&mut test, 10); - push_n_dummy_interval_actions(&mut test, 10); - test.skip_to_current_interval_end(); - - let layer_assignments = vec![ - LayerAssignment::new(1, Layer::One), - LayerAssignment::new(2, Layer::Two), - LayerAssignment::new(3, Layer::Three), - ]; - - let env = test.env(); - let sender = test.rewarding_validator(); - try_advance_epoch( - test.deps_mut(), - env, - sender, - layer_assignments, - current_active_set, - ) - .unwrap(); - - let epoch_events = test.pending_epoch_events(); - let interval_events = test.pending_interval_events(); - assert!(epoch_events.is_empty()); - assert!(interval_events.is_empty()); - } - - #[test] - fn if_executes_any_events_it_propagates_responses() { - let mut test = TestSetup::new(); - let env = test.env(); - let current_active_set = test.rewarding_params().active_set_size; - - test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); - - let mut expected_events = Vec::new(); - let mut expected_messages: Vec> = Vec::new(); - - let non_existent_delegation = PendingEpochEventKind::Delegate { - owner: Addr::unchecked("foomp"), - mix_id: 123, - amount: coin(123, TEST_COIN_DENOM), - proxy: None, - }; - 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 { - to_address: "foomp".to_string(), - amount: coins(123, TEST_COIN_DENOM), - })); - expected_events.push(new_pending_epoch_events_execution_event(1)); - - // interval event - let update = IntervalRewardingParamsUpdate { - rewarded_set_size: Some(500), - ..Default::default() - }; - let change_params = PendingIntervalEventKind::UpdateRewardingParams { update }; - storage::push_new_interval_event(test.deps_mut().storage, &env, change_params).unwrap(); - let interval = test.current_interval(); - let mut expected_updated = test.rewarding_params(); - expected_updated - .try_apply_updates(update, interval.epochs_in_interval()) - .unwrap(); - expected_events.push(new_rewarding_params_update_event( - env.block.height, - update, - expected_updated.interval, - )); - expected_events.push(new_pending_interval_events_execution_event(1)); - let current_interval = test.current_interval(); - let expected = current_interval.advance_epoch(); - expected_events.push(new_advance_epoch_event(expected, 3)); - - test.skip_to_current_interval_end(); - - let layer_assignments = vec![ - LayerAssignment::new(1, Layer::One), - LayerAssignment::new(2, Layer::Two), - LayerAssignment::new(3, Layer::Three), - ]; - - let env = test.env(); - let sender = test.rewarding_validator(); - let res = try_advance_epoch( - test.deps_mut(), - env, - sender, - layer_assignments, - current_active_set, - ) - .unwrap(); - - let mut expected = Response::new().add_events(expected_events); - expected.messages = expected_messages; - assert_eq!(res, expected); - assert_eq!( - 1, - storage::LAST_PROCESSED_EPOCH_EVENT - .load(test.deps().storage) - .unwrap() - ); - assert_eq!( - 1, - storage::LAST_PROCESSED_INTERVAL_EVENT - .load(test.deps().storage) - .unwrap() - ); - } - #[test] fn if_interval_is_over_applies_reward_pool_changes() { let mut test = TestSetup::new(); + test.set_epoch_advancement_state(); + let current_active_set = test.rewarding_params().active_set_size; test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); @@ -1446,6 +1703,7 @@ mod tests { // end of epoch - nothing has happened let sender = test.rewarding_validator(); test.skip_to_current_epoch_end(); + let env = test.env(); try_advance_epoch( test.deps_mut(), @@ -1465,6 +1723,8 @@ mod tests { let sender = test.rewarding_validator(); test.skip_to_current_interval_end(); + test.set_epoch_advancement_state(); + let env = test.env(); try_advance_epoch( test.deps_mut(), @@ -1497,6 +1757,8 @@ mod tests { #[test] fn updates_rewarded_set_and_interval_data() { let mut test = TestSetup::new(); + test.set_epoch_advancement_state(); + let current_active_set = test.rewarding_params().active_set_size; test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); @@ -1548,6 +1810,52 @@ mod tests { use cosmwasm_std::Decimal; use std::time::Duration; + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let owner = test.owner(); + let env = test.env(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + + storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + let res = try_update_interval_config( + test.deps_mut(), + env.clone(), + owner.clone(), + 100, + 1000, + false, + ); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + + let res_forced = try_update_interval_config( + test.deps_mut(), + env.clone(), + owner, + 100, + 1000, + true, + ); + assert!(res_forced.is_ok()) + } + } + #[test] fn can_only_be_done_by_contract_owner() { let mut test = TestSetup::new(); diff --git a/contracts/mixnet/src/lib.rs b/contracts/mixnet/src/lib.rs index 6f420d3541..fb98e68fe0 100644 --- a/contracts/mixnet/src/lib.rs +++ b/contracts/mixnet/src/lib.rs @@ -12,6 +12,7 @@ mod gateways; mod interval; mod mixnet_contract_settings; mod mixnodes; +mod queued_migrations; mod rewards; mod support; diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 9efcc3ab13..d31dbbf13e 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -10,8 +10,9 @@ use crate::mixnodes::helpers::{ get_mixnode_details_by_owner, must_get_mixnode_bond_by_owner, save_new_mixnode, }; use crate::support::helpers::{ - ensure_bonded, ensure_is_authorized, ensure_no_existing_bond, ensure_proxy_match, - validate_node_identity_signature, validate_pledge, + ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond, + ensure_proxy_match, ensure_sent_by_vesting_contract, validate_node_identity_signature, + validate_pledge, }; use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; @@ -47,7 +48,7 @@ pub fn assign_mixnode_layer( mix_id: MixId, layer: Layer, ) -> Result { - ensure_is_authorized(info.sender, deps.storage)?; + ensure_is_authorized(&info.sender, deps.storage)?; update_mixnode_layer(mix_id, layer, deps.storage)?; @@ -83,6 +84,8 @@ pub fn try_add_mixnode_on_behalf( owner: String, owner_signature: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_add_mixnode( @@ -116,7 +119,7 @@ fn _try_add_mixnode( // 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(deps.storage, &owner)?; + ensure_no_existing_bond(&owner, 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 @@ -165,6 +168,8 @@ pub fn try_increase_pledge_on_behalf( info: MessageInfo, owner: String, ) -> Result { + 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)) @@ -181,6 +186,9 @@ pub fn _try_increase_pledge( .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?; 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)?; @@ -205,6 +213,8 @@ pub fn try_remove_mixnode_on_behalf( info: MessageInfo, owner: String, ) -> Result { + 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)) @@ -231,6 +241,9 @@ pub(crate) fn _try_remove_mixnode( .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })? .1; + // unbonding 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 proxy matches ensure_proxy_match(&proxy, &existing_bond.proxy)?; ensure_bonded(&existing_bond)?; @@ -276,6 +289,8 @@ pub(crate) fn try_update_mixnode_config_on_behalf( new_config: MixNodeConfigUpdate, owner: String, ) -> Result { + 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)) @@ -329,6 +344,8 @@ pub(crate) fn try_update_mixnode_cost_params_on_behalf( new_costs: MixNodeCostParams, owner: String, ) -> Result { + 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)) @@ -344,6 +361,9 @@ pub(crate) fn _try_update_mixnode_cost_params( // see if the node still exists let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?; + // 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)?; @@ -371,10 +391,13 @@ pub mod tests { use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge; use crate::mixnodes::helpers::get_mixnode_details_by_id; use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM}; + use crate::support::tests::test_helpers::TestSetup; use crate::support::tests::{fixtures, test_helpers}; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{Order, StdResult, Uint128}; - use mixnet_contract_common::{ExecuteMsg, Layer, LayerDistribution, Percent}; + use mixnet_contract_common::{ + EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent, + }; #[test] fn mixnode_add() { @@ -497,51 +520,110 @@ pub mod tests { } #[test] - fn mixnode_remove() { - let mut deps = test_helpers::init_contract(); - let env = mock_env(); - let mut rng = test_helpers::test_rng(); + fn mixnode_add_with_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); - let sender = "alice"; - let info = mock_info(sender, &[]); + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "alice"; + let (mixnode, sig, _) = test_helpers::mixnode_with_signature(&mut test.rng, owner); + let cost_params = fixtures::mix_node_cost_params_fixture(); + + // we are informed that we didn't send enough funds + 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![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let env = test.env(); + let owner = "alice"; + let info = mock_info(owner, &[]); + + test.add_dummy_mixnode(owner, None); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + let res = try_remove_mixnode(test.deps_mut(), env.clone(), info); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } + + #[test] + fn mixnode_remove() { + let mut test = TestSetup::new(); + let env = test.env(); + + let owner = "alice"; + let info = mock_info(owner, &[]); // trying to remove your mixnode fails if you never had one in the first place - let res = try_remove_mixnode(deps.as_mut(), env.clone(), info.clone()); + let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone()); assert_eq!( res, Err(MixnetContractError::NoAssociatedMixNodeBond { - owner: Addr::unchecked(sender) + owner: Addr::unchecked(owner) }) ); - let mix_id = test_helpers::add_mixnode( - &mut rng, - deps.as_mut(), - env.clone(), - sender, - good_mixnode_pledge(), - ); + 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( - deps.as_mut(), + test.deps_mut(), env.clone(), - mock_info("proxy", &[]), - sender.to_string(), + mock_info(vesting_contract.as_ref(), &[]), + owner.to_string(), ); + assert_eq!( res, Err(MixnetContractError::ProxyMismatch { existing: "None".to_string(), - incoming: "proxy".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(deps.as_mut(), env.clone(), info.clone()); + let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone()); assert!(res.is_ok()); let mut pending_events = interval_storage::PENDING_EPOCH_EVENTS - .range(deps.as_ref().storage, None, None, Order::Ascending) + .range(test.deps().storage, None, None, Order::Ascending) .collect::>>() .unwrap(); assert_eq!(pending_events.len(), 1); @@ -553,18 +635,46 @@ pub mod tests { ); // but fails if repeated (since the node is already in the "unbonding" state)( - let res = try_remove_mixnode(deps.as_mut(), env, info); + let res = try_remove_mixnode(test.deps_mut(), env, info); assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } #[test] - fn updating_mixnode_config() { - let mut deps = test_helpers::init_contract(); - let env = mock_env(); - let mut rng = test_helpers::test_rng(); + fn mixnode_remove_with_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); - let sender = "alice"; - let info = mock_info(sender, &[]); + 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 updating_mixnode_config() { + let mut test = TestSetup::new(); + let env = test.env(); + + let owner = "alice"; + let info = mock_info(owner, &[]); let update = MixNodeConfigUpdate { host: "1.1.1.1:1234".to_string(), mix_port: 1234, @@ -574,43 +684,38 @@ pub mod tests { }; // try updating a non existing mixnode bond - let res = try_update_mixnode_config(deps.as_mut(), info.clone(), update.clone()); + let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone()); assert_eq!( res, Err(MixnetContractError::NoAssociatedMixNodeBond { - owner: Addr::unchecked(sender) + owner: Addr::unchecked(owner) }) ); - let mix_id = test_helpers::add_mixnode( - &mut rng, - deps.as_mut(), - env.clone(), - sender, - tests::fixtures::good_mixnode_pledge(), - ); + 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( - deps.as_mut(), - mock_info("proxy", &[]), + test.deps_mut(), + mock_info(vesting_contract.as_ref(), &[]), update.clone(), - sender.to_string(), + owner.to_string(), ); assert_eq!( res, Err(MixnetContractError::ProxyMismatch { existing: "None".to_string(), - incoming: "proxy".to_string() + incoming: vesting_contract.into_string() }) ); // "normal" update succeeds - let res = try_update_mixnode_config(deps.as_mut(), info.clone(), update.clone()); + let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone()); assert!(res.is_ok()); // and the config has actually been updated - let mix = must_get_mixnode_bond_by_owner(deps.as_ref().storage, &Addr::unchecked(sender)) - .unwrap(); + let mix = + must_get_mixnode_bond_by_owner(test.deps().storage, &Addr::unchecked(owner)).unwrap(); assert_eq!(mix.mix_node.host, update.host); assert_eq!(mix.mix_node.mix_port, update.mix_port); assert_eq!(mix.mix_node.verloc_port, update.verloc_port); @@ -618,19 +723,90 @@ pub mod tests { assert_eq!(mix.mix_node.version, update.version); // but we cannot perform any updates whilst the mixnode is already unbonding - try_remove_mixnode(deps.as_mut(), env, info.clone()).unwrap(); - let res = try_update_mixnode_config(deps.as_mut(), info, update); + try_remove_mixnode(test.deps_mut(), env, info.clone()).unwrap(); + let res = try_update_mixnode_config(test.deps_mut(), info, update); assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } #[test] - fn updating_mixnode_cost_params() { - let mut deps = test_helpers::init_contract(); - let env = mock_env(); - let mut rng = test_helpers::test_rng(); + fn updating_mixnode_config_with_illegal_proxy() { + let mut test = TestSetup::new(); - let sender = "alice"; - let info = mock_info(sender, &[]); + 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![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + let update = MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(42).unwrap(), + interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM), + }; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let env = test.env(); + let owner = "alice"; + let info = mock_info(owner, &[]); + + test.add_dummy_mixnode(owner, None); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + let res = + try_update_mixnode_cost_params(test.deps_mut(), env.clone(), info, update.clone()); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } + + #[test] + fn updating_mixnode_cost_params() { + let mut test = TestSetup::new(); + let env = test.env(); + + let owner = "alice"; + let info = mock_info(owner, &[]); let update = MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(42).unwrap(), interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM), @@ -638,7 +814,7 @@ pub mod tests { // try updating a non existing mixnode bond let res = try_update_mixnode_cost_params( - deps.as_mut(), + test.deps_mut(), env.clone(), info.clone(), update.clone(), @@ -646,36 +822,31 @@ pub mod tests { assert_eq!( res, Err(MixnetContractError::NoAssociatedMixNodeBond { - owner: Addr::unchecked(sender) + owner: Addr::unchecked(owner) }) ); - let mix_id = test_helpers::add_mixnode( - &mut rng, - deps.as_mut(), - env.clone(), - sender, - tests::fixtures::good_mixnode_pledge(), - ); + 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( - deps.as_mut(), + test.deps_mut(), env.clone(), - mock_info("proxy", &[]), + mock_info(vesting_contract.as_ref(), &[]), update.clone(), - sender.to_string(), + owner.to_string(), ); assert_eq!( res, Err(MixnetContractError::ProxyMismatch { existing: "None".to_string(), - incoming: "proxy".to_string() + incoming: vesting_contract.into_string() }) ); // "normal" update succeeds let res = try_update_mixnode_cost_params( - deps.as_mut(), + test.deps_mut(), env.clone(), info.clone(), update.clone(), @@ -684,7 +855,7 @@ pub mod tests { // see if the event has been pushed onto the queue let mut pending_events = interval_storage::PENDING_INTERVAL_EVENTS - .range(deps.as_ref().storage, None, None, Order::Ascending) + .range(test.deps().storage, None, None, Order::Ascending) .collect::>>() .unwrap(); assert_eq!(pending_events.len(), 1); @@ -699,20 +870,54 @@ pub mod tests { ); // execute the event - test_helpers::execute_all_pending_events(deps.as_mut(), env.clone()); + test_helpers::execute_all_pending_events(test.deps_mut(), env.clone()); // and see if the config has actually been updated - let mix = get_mixnode_details_by_id(deps.as_ref().storage, mix_id) + let mix = get_mixnode_details_by_id(test.deps().storage, mix_id) .unwrap() .unwrap(); assert_eq!(mix.rewarding_details.cost_params, update); // but we cannot perform any updates whilst the mixnode is already unbonding - try_remove_mixnode(deps.as_mut(), env.clone(), info.clone()).unwrap(); - let res = try_update_mixnode_cost_params(deps.as_mut(), env, info, update); + try_remove_mixnode(test.deps_mut(), env.clone(), info.clone()).unwrap(); + let res = try_update_mixnode_cost_params(test.deps_mut(), env, info, update); 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 deps = test_helpers::init_contract(); @@ -770,6 +975,40 @@ pub mod tests { setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING, }; use crate::support::tests::test_helpers::TestSetup; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let env = test.env(); + let owner = "mix-owner"; + + test.add_dummy_mixnode(owner, None); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let sender = mock_info(owner, &[test.coin(1000)]); + let res = try_increase_pledge(test.deps_mut(), env, sender); + + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } #[test] fn is_not_allowed_if_account_doesnt_own_mixnode() { @@ -797,7 +1036,11 @@ pub mod tests { let wrong_proxy = Addr::unchecked("unrelated-proxy"); test.add_dummy_mixnode(owner_without_proxy.as_str(), None); - test.add_dummy_mixnode_with_proxy(owner_with_proxy.as_str(), None, proxy.clone()); + test.add_dummy_mixnode_with_illegal_proxy( + owner_with_proxy.as_str(), + None, + proxy.clone(), + ); let res = _try_increase_pledge( test.deps_mut(), @@ -945,4 +1188,33 @@ 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 + } + ) + } } diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index 87d5c392c8..596179f6e6 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -1,2 +1,18 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + +use crate::interval::storage as interval_storage; +use cosmwasm_std::Storage; +use mixnet_contract_common::error::MixnetContractError; +use mixnet_contract_common::EpochStatus; + +pub(crate) fn create_epoch_status(storage: &mut dyn Storage) -> Result<(), MixnetContractError> { + let current_rewarding_validator = + crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)?; + interval_storage::save_current_epoch_status( + storage, + &EpochStatus::new(current_rewarding_validator), + )?; + + Ok(()) +} diff --git a/contracts/mixnet/src/rewards/helpers.rs b/contracts/mixnet/src/rewards/helpers.rs index 91b8fbdeb0..df2b315ef1 100644 --- a/contracts/mixnet/src/rewards/helpers.rs +++ b/contracts/mixnet/src/rewards/helpers.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::storage; @@ -8,7 +8,20 @@ use cosmwasm_std::{Coin, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::helpers::IntoBaseDecimal; use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding}; -use mixnet_contract_common::Delegation; +use mixnet_contract_common::{Delegation, EpochState, EpochStatus, MixId}; + +pub(crate) fn update_and_save_last_rewarded( + storage: &mut dyn Storage, + mut current_epoch_status: EpochStatus, + new_last_rewarded: MixId, +) -> Result<(), MixnetContractError> { + let is_done = current_epoch_status.update_last_rewarded(new_last_rewarded)?; + if is_done { + current_epoch_status.state = EpochState::ReconcilingEvents + } + interval_storage::save_current_epoch_status(storage, ¤t_epoch_status)?; + Ok(()) +} /// Recomputes rewarding parameters (such as staking supply, saturation point, etc) based on /// pending changes currently stored in `PENDING_REWARD_POOL_CHANGE`. @@ -215,14 +228,14 @@ mod tests { assert_eq!(res.amount, Uint128::zero()); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - let dist1 = test.reward_with_distribution(mix_id, performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + let dist1 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist3 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist3 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); let mix_details = get_mixnode_details_by_id(test.deps().storage, mix_id) .unwrap() @@ -254,14 +267,14 @@ mod tests { assert_eq!(res.amount, Uint128::zero()); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - let dist1 = test.reward_with_distribution(mix_id, performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + let dist1 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist3 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist3 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); let delegation_pre = test.delegation(mix_id, delegator, &None); let mix_rewarding = test.mix_rewarding(mix_id); diff --git a/contracts/mixnet/src/rewards/queries.rs b/contracts/mixnet/src/rewards/queries.rs index d49b1165c6..3804f83da7 100644 --- a/contracts/mixnet/src/rewards/queries.rs +++ b/contracts/mixnet/src/rewards/queries.rs @@ -297,10 +297,13 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.operator; let res = query_pending_operator_reward(test.deps(), owner.into()).unwrap(); @@ -317,7 +320,10 @@ mod tests { // reward it few more times for good measure for _ in 0..10 { test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.operator; let res = query_pending_operator_reward(test.deps(), owner.into()).unwrap(); @@ -341,10 +347,13 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.operator; let sender = mock_info(owner, &[]); @@ -370,9 +379,12 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info(owner, &[]); let env = test.env(); @@ -447,10 +459,13 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.delegates; let res = @@ -466,7 +481,10 @@ mod tests { // reward it few more times for good measure for _ in 0..10 { test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.delegates; let res = query_pending_delegator_reward(test.deps(), owner.into(), mix_id, None) @@ -491,10 +509,13 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.delegates; let sender = mock_info("mix-owner", &[]); @@ -521,10 +542,13 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.delegates; let sender = mock_info("mix-owner", &[]); @@ -557,34 +581,58 @@ mod tests { test.add_immediate_delegation(del2, 150_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.add_immediate_delegation(del3, 500_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(85.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(85.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(5.0)); + test.reward_with_distribution_with_state_bypass(mix_id, test_helpers::performance(5.0)); test.add_immediate_delegation(del4, 5_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.add_immediate_delegation(del2, 250_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(98.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(98.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.remove_immediate_delegation(del3, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(98.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(98.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let pending1 = query_pending_delegator_reward(test.deps(), del1.into(), mix_id, None).unwrap(); @@ -660,8 +708,11 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info(owner, &[]); let env = test.env(); @@ -686,8 +737,11 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info(owner, &[]); let env = test.env(); @@ -710,9 +764,12 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); - test.update_rewarded_set(vec![]); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); + test.force_change_rewarded_set(vec![]); let res = query_estimated_current_epoch_operator_reward( test.deps(), @@ -732,8 +789,11 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let res = query_estimated_current_epoch_operator_reward( test.deps(), @@ -753,8 +813,11 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let mix_rewarding = test.mix_rewarding(mix_id); let res = query_estimated_current_epoch_operator_reward( @@ -765,7 +828,10 @@ mod tests { .unwrap(); test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(95.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(95.0), + ); let expected = EstimatedCurrentEpochRewardResponse { original_stake: Some(coin(initial_stake.u128(), TEST_COIN_DENOM)), @@ -816,8 +882,11 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let res = query_estimated_current_epoch_delegator_reward( test.deps(), @@ -841,8 +910,11 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info("mix-owner", &[]); let env = test.env(); @@ -871,8 +943,11 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info("mix-owner", &[]); let env = test.env(); @@ -902,9 +977,12 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); - test.update_rewarded_set(vec![]); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); + test.force_change_rewarded_set(vec![]); let res = query_estimated_current_epoch_delegator_reward( test.deps(), @@ -929,8 +1007,11 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let res = query_estimated_current_epoch_delegator_reward( test.deps(), @@ -955,7 +1036,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mix_rewarding = test.mix_rewarding(mix_id); let res = query_estimated_current_epoch_delegator_reward( @@ -968,7 +1049,10 @@ mod tests { .unwrap(); test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(95.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(95.0), + ); let expected = EstimatedCurrentEpochRewardResponse { original_stake: Some(coin(initial_stake.u128(), TEST_COIN_DENOM)), @@ -1003,12 +1087,18 @@ mod tests { test.add_immediate_delegation(del2, initial_stake2, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(95.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(95.0), + ); test.add_immediate_delegation(del3, initial_stake3, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(85.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(85.0), + ); let mix_rewarding = test.mix_rewarding(mix_id); @@ -1045,7 +1135,10 @@ mod tests { let cur3 = initial_stake3_dec + est3; test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(95.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(95.0), + ); let share1 = cur1 / mix_rewarding.delegates * dist.delegates; let share2 = cur2 / mix_rewarding.delegates * dist.delegates; diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index d1552bb50b..a9ef3b6f5a 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::storage; @@ -9,9 +9,10 @@ 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_is_authorized, ensure_is_owner, ensure_proxy_match, - send_to_proxy_or_owner, + 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 cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; @@ -26,7 +27,7 @@ use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingInter use mixnet_contract_common::reward_params::{ IntervalRewardingParamsUpdate, NodeRewardParams, Performance, }; -use mixnet_contract_common::{Delegation, MixId}; +use mixnet_contract_common::{Delegation, EpochState, MixId}; use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; pub(crate) fn try_reward_mixnode( @@ -36,7 +37,9 @@ pub(crate) fn try_reward_mixnode( mix_id: MixId, node_performance: Performance, ) -> Result { - ensure_is_authorized(info.sender, deps.storage)?; + // check whether this `info.sender` is the same one as set in `epoch_status.being_advanced_by` + // if so, return `epoch_status` so we could avoid having to perform extra read from the storage + let current_epoch_status = ensure_can_advance_epoch(&info.sender, deps.storage)?; // see if the epoch has finished let interval = interval_storage::current_interval(deps.storage)?; @@ -47,6 +50,19 @@ pub(crate) fn try_reward_mixnode( epoch_end: interval.current_epoch_end_unix_timestamp(), }); } + let absolute_epoch_id = interval.current_epoch_absolute_id(); + + if matches!(current_epoch_status.state, EpochState::Rewarding {last_rewarded, ..} if last_rewarded == mix_id) + { + return Err(MixnetContractError::MixnodeAlreadyRewarded { + mix_id, + absolute_epoch_id, + }); + } + + // update the epoch state with this node as being rewarded most recently + // (if the transaction fails down the line, it will be reverted) + update_and_save_last_rewarded(deps.storage, current_epoch_status, mix_id)?; // there's a chance of this failing to load the details if the mixnode unbonded before rewards // were distributed and all of its delegators are also gone @@ -64,8 +80,7 @@ pub(crate) fn try_reward_mixnode( // check if this node has already been rewarded for the current epoch. // unlike the previous check, this one should be a hard error since this cannot be - // influenced by users actions - let absolute_epoch_id = interval.current_epoch_absolute_id(); + // influenced by users actions (note that previous epoch state checks should actually already guard us against it) if absolute_epoch_id == mix_rewarding.last_rewarded_epoch { return Err(MixnetContractError::MixnodeAlreadyRewarded { mix_id, @@ -130,6 +145,8 @@ pub(crate) fn try_withdraw_operator_reward_on_behalf( info: MessageInfo, owner: String, ) -> Result { + 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)) @@ -195,6 +212,8 @@ pub(crate) fn try_withdraw_delegator_reward_on_behalf( mix_id: MixId, owner: String, ) -> Result { + 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)) @@ -293,6 +312,10 @@ pub(crate) fn try_update_active_set_size( active_set_size, ))) } else { + // updating active sety size is only allowed if the epoch is currently not in the process of being advanced + // (unless the force flag was used) + ensure_epoch_in_progress_state(deps.storage)?; + // push the epoch event let epoch_event = PendingEpochEventKind::UpdateActiveSetSize { new_size: active_set_size, @@ -332,6 +355,10 @@ pub(crate) fn try_update_rewarding_params( rewarding_params.interval, ))) } else { + // changing rewarding parameters is only allowed if the epoch is currently not in the process of being advanced + // (unless the force flag was used) + ensure_epoch_in_progress_state(deps.storage)?; + // push the interval event let interval_event = PendingIntervalEventKind::UpdateRewardingParams { update: updated_params, @@ -366,7 +393,236 @@ pub mod tests { ZERO_PERFORMANCE_VALUE, }; use mixnet_contract_common::helpers::compare_decimals; - use mixnet_contract_common::RewardedSetNodeStatus; + use mixnet_contract_common::{EpochStatus, RewardedSetNodeStatus}; + + #[cfg(test)] + mod epoch_state_is_correctly_updated { + use super::*; + use mixnet_contract_common::EpochState; + + #[test] + fn when_target_mixnode_unbonded() { + let mut test = TestSetup::new(); + let mix_id_unbonded = test.add_dummy_mixnode("mix-owner-unbonded", None); + let mix_id_unbonded_leftover = + test.add_dummy_mixnode("mix-owner-unbonded-leftover", None); + let mix_id_never_existed = 42; + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(vec![ + mix_id_unbonded, + mix_id_unbonded_leftover, + mix_id_never_existed, + ]); + test.start_epoch_transition(); + + let env = test.env(); + + // note: we don't have to test for cases where `is_unbonding` is set to true on a mixnode + // since before performing the nym-api should clear out the event queue + + // manually adjust delegation info as to indicate the rewarding information shouldnt get removed + let mut rewarding_details = storage::MIXNODE_REWARDING + .load(test.deps().storage, mix_id_unbonded_leftover) + .unwrap(); + rewarding_details.delegates = Decimal::raw(12345); + rewarding_details.unique_delegations = 1; + storage::MIXNODE_REWARDING + .save( + test.deps_mut().storage, + mix_id_unbonded_leftover, + &rewarding_details, + ) + .unwrap(); + pending_events::unbond_mixnode(test.deps_mut(), &env, 123, mix_id_unbonded) + .unwrap(); + + pending_events::unbond_mixnode( + test.deps_mut(), + &env, + 123, + mix_id_unbonded_leftover, + ) + .unwrap(); + + let env = test.env(); + let sender = test.rewarding_validator(); + let performance = test_helpers::performance(100.0); + + try_reward_mixnode( + test.deps_mut(), + env.clone(), + sender.clone(), + mix_id_unbonded, + performance, + ) + .unwrap(); + assert_eq!( + EpochState::Rewarding { + last_rewarded: mix_id_unbonded, + final_node_id: mix_id_never_existed + }, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + + try_reward_mixnode( + test.deps_mut(), + env.clone(), + sender.clone(), + mix_id_unbonded_leftover, + performance, + ) + .unwrap(); + assert_eq!( + EpochState::Rewarding { + last_rewarded: mix_id_unbonded_leftover, + final_node_id: mix_id_never_existed + }, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + + try_reward_mixnode( + test.deps_mut(), + env.clone(), + sender.clone(), + mix_id_never_existed, + performance, + ) + .unwrap(); + assert_eq!( + EpochState::ReconcilingEvents, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + } + + #[test] + fn when_target_mixnode_has_zero_performance() { + let mut test = TestSetup::new(); + let mix_id = test.add_dummy_mixnode("mix-owner", None); + + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); + let zero_performance = test_helpers::performance(0.); + let env = test.env(); + let sender = test.rewarding_validator(); + + try_reward_mixnode(test.deps_mut(), env, sender, mix_id, zero_performance).unwrap(); + assert_eq!( + EpochState::ReconcilingEvents, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + } + + #[test] + fn when_theres_only_one_node_to_reward() { + let mut test = TestSetup::new(); + let mix_id = test.add_dummy_mixnode("mix-owner", None); + + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); + let performance = test_helpers::performance(100.0); + let env = test.env(); + let sender = test.rewarding_validator(); + + try_reward_mixnode(test.deps_mut(), env, sender, mix_id, performance).unwrap(); + assert_eq!( + EpochState::ReconcilingEvents, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + } + + #[test] + fn when_theres_multiple_nodes_to_reward() { + let mut test = TestSetup::new(); + + let mut ids = Vec::new(); + for i in 0..100 { + let mix_id = test.add_dummy_mixnode(&format!("mix-owner{i}"), None); + ids.push(mix_id); + } + + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(ids.clone()); + test.start_epoch_transition(); + let performance = test_helpers::performance(100.0); + let env = test.env(); + let sender = test.rewarding_validator(); + + for mix_id in ids { + try_reward_mixnode( + test.deps_mut(), + env.clone(), + sender.clone(), + mix_id, + performance, + ) + .unwrap(); + + let current_state = interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state; + if mix_id == 100 { + assert_eq!(EpochState::ReconcilingEvents, current_state) + } else { + assert_eq!( + EpochState::Rewarding { + last_rewarded: mix_id, + final_node_id: 100 + }, + current_state + ) + } + } + } + } + + #[test] + fn can_only_be_performed_if_in_rewarding_state() { + let bad_states = vec![ + EpochState::InProgress, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + test.skip_to_current_epoch_end(); + test.force_change_rewarded_set(vec![1, 2, 3]); + let env = test.env(); + + let res = try_reward_mixnode( + test.deps_mut(), + env, + rewarding_validator, + 1, + test_helpers::performance(100.), + ); + assert_eq!( + res, + Err(MixnetContractError::UnexpectedNonRewardingEpochState { + current_state: bad_state + }) + ); + } + } #[test] fn can_only_be_performed_by_specified_rewarding_validator() { @@ -377,7 +633,8 @@ pub mod tests { // skip time to when the following epoch is over (since mixnodes are not eligible for rewarding // in the same epoch they're bonded and we need the rewarding epoch to be over) test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); let performance = test_helpers::performance(100.); let env = test.env(); @@ -399,11 +656,12 @@ pub mod tests { let mix_id_unbonded_leftover = test.add_dummy_mixnode("mix-owner-unbonded-leftover", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![ - mix_id_never_existed, + test.force_change_rewarded_set(vec![ mix_id_unbonded, mix_id_unbonded_leftover, + mix_id_never_existed, ]); + test.start_epoch_transition(); let env = test.env(); @@ -433,9 +691,9 @@ pub mod tests { let performance = test_helpers::performance(100.0); for &mix_id in &[ - mix_id_never_existed, mix_id_unbonded, mix_id_unbonded_leftover, + mix_id_never_existed, ] { let res = try_reward_mixnode( test.deps_mut(), @@ -464,7 +722,7 @@ pub mod tests { // node is in the active set BUT the current epoch has just begun test.skip_to_next_epoch(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let performance = test_helpers::performance(100.); let env = test.env(); @@ -476,6 +734,7 @@ pub mod tests { // epoch is over (sanity check) test.skip_to_current_epoch_end(); + test.start_epoch_transition(); let env = test.env(); let res = try_reward_mixnode(test.deps_mut(), env, sender, mix_id, performance); assert!(res.is_ok()); @@ -508,6 +767,16 @@ pub mod tests { ) .unwrap(); + // actually add one more dummy node with high id so we wouldn't go into the next state + interval_storage::REWARDED_SET + .save( + test.deps_mut().storage, + 9001, + &RewardedSetNodeStatus::Standby, + ) + .unwrap(); + test.start_epoch_transition(); + let performance = test_helpers::performance(100.); let env = test.env(); let res_active = try_reward_mixnode( @@ -541,7 +810,8 @@ pub mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id, 42]); + test.start_epoch_transition(); let performance = test_helpers::performance(100.); let env = test.env(); let sender = test.rewarding_validator(); @@ -565,6 +835,8 @@ pub mod tests { // in the following epoch we're good again test.skip_to_next_epoch_end(); + test.start_epoch_transition(); + let env = test.env(); let res = try_reward_mixnode(test.deps_mut(), env, sender, mix_id, performance); assert!(res.is_ok()); @@ -576,7 +848,8 @@ pub mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id, 42]); + test.start_epoch_transition(); let zero_performance = test_helpers::performance(0.); let performance = test_helpers::performance(100.0); let env = test.env(); @@ -614,6 +887,8 @@ pub mod tests { // but in the next epoch, as always, we're good again test.skip_to_next_epoch_end(); + test.start_epoch_transition(); + let env = test.env(); let res = try_reward_mixnode(test.deps_mut(), env, sender, mix_id, performance).unwrap(); @@ -642,7 +917,8 @@ pub mod tests { let mix_id3 = test.add_dummy_mixnode("mix-owner3", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, mix_id2, mix_id3]); + test.force_change_rewarded_set(vec![mix_id1, mix_id2, mix_id3]); + test.start_epoch_transition(); let performance = test_helpers::performance(98.0); let env = test.env(); let sender = test.rewarding_validator(); @@ -726,7 +1002,8 @@ pub mod tests { let mix_id3 = test.add_dummy_mixnode("mix-owner3", Some(operator3)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, mix_id2, mix_id3]); + test.start_epoch_transition(); + test.force_change_rewarded_set(vec![mix_id1, mix_id2, mix_id3]); let performance = test_helpers::performance(98.0); test.add_immediate_delegation("delegator1", Uint128::new(100_000_000), mix_id2); @@ -735,8 +1012,12 @@ pub mod tests { test.add_immediate_delegation("delegator2", Uint128::new(123_456_000), mix_id3); test.add_immediate_delegation("delegator3", Uint128::new(9_100_000_000), mix_id3); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); + // repeat the rewarding the same set of delegates for few epochs for _ in 0..10 { + test.start_epoch_transition(); for &mix_id in &[mix_id1, mix_id2, mix_id3] { let mut sim = test.instantiate_simulator(mix_id); let dist = test.reward_with_distribution(mix_id, performance); @@ -747,6 +1028,8 @@ pub mod tests { let sim_res = sim.simulate_epoch_single_node(node_params).unwrap(); assert_eq!(sim_res, dist); } + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); test.skip_to_next_epoch_end(); } @@ -759,8 +1042,12 @@ pub mod tests { test.add_immediate_delegation("delegator5", Uint128::new(123_000_000), mix_id3); test.add_immediate_delegation("delegator6", Uint128::new(456_000_000), mix_id3); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); + let performance = test_helpers::performance(12.3); for _ in 0..10 { + test.start_epoch_transition(); for &mix_id in &[mix_id1, mix_id2, mix_id3] { let mut sim = test.instantiate_simulator(mix_id); let dist = test.reward_with_distribution(mix_id, performance); @@ -771,6 +1058,8 @@ pub mod tests { let sim_res = sim.simulate_epoch_single_node(node_params).unwrap(); assert_eq!(sim_res, dist); } + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); test.skip_to_next_epoch_end(); } } @@ -787,7 +1076,7 @@ pub mod tests { let mix_id2 = test.add_dummy_mixnode("mix-owner2", Some(operator2)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, mix_id2]); + test.force_change_rewarded_set(vec![mix_id1, mix_id2]); let performance = test_helpers::performance(98.0); test.add_immediate_delegation("delegator1", Uint128::new(100_000_000), mix_id1); @@ -800,6 +1089,8 @@ pub mod tests { let del21 = test.delegation(mix_id2, "delegator1", &None); for _ in 0..10 { + test.start_epoch_transition(); + // we know from the previous tests that actual rewarding distribution matches the simulator let mut sim1 = test.instantiate_simulator(mix_id1); let mut sim2 = test.instantiate_simulator(mix_id2); @@ -933,6 +1224,8 @@ pub mod tests { assert_eq!(dist2.delegates, computed_del21_reward); test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } // add more delegations and check few more epochs (so that the delegations would start from non-default unit delegation value) @@ -943,6 +1236,8 @@ pub mod tests { let del23 = test.delegation(mix_id2, "delegator3", &None); for _ in 0..10 { + test.start_epoch_transition(); + // we know from the previous tests that actual rewarding distribution matches the simulator let mut sim1 = test.instantiate_simulator(mix_id1); let mut sim2 = test.instantiate_simulator(mix_id2); @@ -1096,6 +1391,8 @@ pub mod tests { ); test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } } } @@ -1104,8 +1401,9 @@ pub mod tests { mod withdrawing_delegator_reward { use super::*; 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 cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128}; use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; #[test] @@ -1131,7 +1429,8 @@ pub mod tests { // perform some rewarding so that we'd have non-zero rewards test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, mix_id2]); + test.force_change_rewarded_set(vec![mix_id1, mix_id2]); + test.start_epoch_transition(); test.reward_with_distribution(mix_id1, test_helpers::performance(100.0)); test.reward_with_distribution(mix_id2, test_helpers::performance(100.0)); @@ -1178,7 +1477,8 @@ pub mod tests { // reward mix1, but don't reward mix2 test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, low_stake_id]); + test.force_change_rewarded_set(vec![mix_id1, low_stake_id]); + test.start_epoch_transition(); test.reward_with_distribution(mix_id1, test_helpers::performance(100.0)); test.reward_with_distribution(low_stake_id, test_helpers::performance(100.0)); @@ -1216,14 +1516,18 @@ pub mod tests { let performance = test_helpers::performance(100.0); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_unbonding, mix_id_unbonded_leftover]); + test.force_change_rewarded_set(vec![mix_id_unbonding, mix_id_unbonded_leftover]); // go through few rewarding cycles before unbonding nodes (partially or fully) for _ in 0..10 { + test.start_epoch_transition(); + test.reward_with_distribution(mix_id_unbonding, performance); test.reward_with_distribution(mix_id_unbonded_leftover, performance); test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } // start unbonding the first node and fully unbond the other @@ -1291,12 +1595,13 @@ pub mod tests { let performance = test_helpers::performance(100.0); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_single, mix_id_quad]); + test.force_change_rewarded_set(vec![mix_id_single, mix_id_quad]); // accumulate some rewards let mut accumulated_single = Decimal::zero(); let mut accumulated_quad = Decimal::zero(); for _ in 0..10 { + test.start_epoch_transition(); let dist = test.reward_with_distribution(mix_id_single, performance); // sanity check to make sure test is actually doing what it's supposed to be doing assert!(!dist.delegates.is_zero()); @@ -1306,6 +1611,8 @@ pub mod tests { accumulated_quad += dist.delegates; test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } let before = test.read_delegation(mix_id_single, delegator1, None); @@ -1361,9 +1668,13 @@ pub mod tests { // accumulate some more for _ in 0..10 { + test.start_epoch_transition(); + let dist = test.reward_with_distribution(mix_id_quad, performance); accumulated_quad += dist.delegates; test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } let before1_new = test.read_delegation(mix_id_quad, delegator1, None); @@ -1422,14 +1733,58 @@ 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 super::*; use crate::interval::pending_events; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers::TestSetup; - use cosmwasm_std::{BankMsg, CosmosMsg, Uint128}; + use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128}; #[test] fn can_only_be_done_if_bond_exists() { @@ -1440,7 +1795,8 @@ pub mod tests { let sender = mock_info("random-guy", &[]); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + 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(test.deps_mut(), sender.clone()); @@ -1466,7 +1822,8 @@ pub mod tests { // reward mix1, but don't reward mix2 test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1]); + test.force_change_rewarded_set(vec![mix_id1]); + test.start_epoch_transition(); test.reward_with_distribution(mix_id1, test_helpers::performance(100.0)); let res1 = try_withdraw_operator_reward(test.deps_mut(), sender1).unwrap(); @@ -1498,14 +1855,17 @@ pub mod tests { let performance = test_helpers::performance(100.0); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_unbonding, mix_id_unbonded_leftover]); + test.force_change_rewarded_set(vec![mix_id_unbonding, mix_id_unbonded_leftover]); // go through few rewarding cycles before unbonding nodes (partially or fully) for _ in 0..10 { + test.start_epoch_transition(); test.reward_with_distribution(mix_id_unbonding, performance); test.reward_with_distribution(mix_id_unbonded_leftover, performance); test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } // start unbonding the first node and fully unbond the other @@ -1537,12 +1897,89 @@ 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)] mod updating_active_set { use super::*; use crate::support::tests::test_helpers::TestSetup; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let owner = test.owner(); + let env = test.env(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let res = try_update_active_set_size( + test.deps_mut(), + env.clone(), + owner.clone(), + 100, + false, + ); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + + let res_forced = + try_update_active_set_size(test.deps_mut(), env.clone(), owner, 100, true); + assert!(res_forced.is_ok()) + } + } #[test] fn can_only_be_done_by_contract_owner() { @@ -1706,6 +2143,62 @@ pub mod tests { use super::*; use crate::support::tests::test_helpers::{assert_decimals, TestSetup}; use cosmwasm_std::Decimal; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + let update = IntervalRewardingParamsUpdate { + reward_pool: None, + staking_supply: None, + staking_supply_scale_factor: None, + sybil_resistance_percent: None, + active_set_work_factor: None, + interval_pool_emission: None, + rewarded_set_size: Some(123), + }; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let owner = test.owner(); + let env = test.env(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let res = try_update_rewarding_params( + test.deps_mut(), + env.clone(), + owner.clone(), + update.clone(), + false, + ); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + + let res_forced = try_update_rewarding_params( + test.deps_mut(), + env.clone(), + owner, + update.clone(), + true, + ); + assert!(res_forced.is_ok()) + } + } #[test] fn can_only_be_done_by_contract_owner() { diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index 8d602f48f6..fe13150ec3 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -1,11 +1,15 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 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::{Addr, BankMsg, Coin, CosmosMsg, Deps, Response, Storage}; +use cosmwasm_std::{ + wasm_execute, Addr, BankMsg, Coin, CosmosMsg, Deps, MessageInfo, Response, Storage, +}; use mixnet_contract_common::error::MixnetContractError; -use mixnet_contract_common::{IdentityKeyRef, MixNodeBond}; +use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond}; +use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; // helper trait to attach `Msg` to a response if it's provided pub(crate) trait AttachOptionalMessage { @@ -22,6 +26,99 @@ impl AttachOptionalMessage for Response { } } +// 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, + owner: String, + mix_id: MixId, + amount: Coin, + ) -> Result; + + fn maybe_add_track_vesting_unbond_mixnode_message( + self, + storage: &dyn Storage, + proxy: Option, + owner: String, + amount: Coin, + ) -> Result; +} + +impl VestingTracking for Response { + fn maybe_add_track_vesting_undelegation_message( + self, + storage: &dyn Storage, + proxy: Option, + owner: String, + mix_id: MixId, + amount: Coin, + ) -> Result { + // 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, + owner: String, + amount: Coin, + ) -> Result { + // 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) + } + } +} + // pub fn debug_with_visibility>(api: &dyn Api, msg: S) { // api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into())); // } @@ -117,16 +214,80 @@ pub(crate) fn validate_delegation_stake( Ok(delegation.pop().unwrap()) } -pub(crate) fn ensure_is_authorized( - sender: Addr, +pub(crate) fn ensure_epoch_in_progress_state( storage: &dyn Storage, ) -> Result<(), MixnetContractError> { - if sender != crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? { + let epoch_status = crate::interval::storage::current_epoch_status(storage)?; + if !matches!(epoch_status.state, EpochState::InProgress) { + return Err(MixnetContractError::EpochAdvancementInProgress { + current_state: epoch_status.state, + }); + } + Ok(()) +} + +// pub(crate) fn ensure_mix_rewarding_state(storage: &dyn Storage) -> Result<(), MixnetContractError> { +// let epoch_status = crate::interval::storage::current_epoch_status(storage)?; +// if !matches!(epoch_status.state, EpochState::Rewarding { .. }) { +// return Err(MixnetContractError::EpochNotInMixRewardingState { +// current_state: epoch_status.state, +// }); +// } +// Ok(()) +// } +// +// pub(crate) fn ensure_event_reconciliation_state( +// storage: &dyn Storage, +// ) -> Result<(), MixnetContractError> { +// let epoch_status = crate::interval::storage::current_epoch_status(storage)?; +// if !matches!(epoch_status.state, EpochState::ReconcilingEvents) { +// return Err(MixnetContractError::EpochNotInEventReconciliationState { +// current_state: epoch_status.state, +// }); +// } +// Ok(()) +// } +// +// pub(crate) fn ensure_epoch_advancement_state( +// storage: &dyn Storage, +// ) -> Result<(), MixnetContractError> { +// let epoch_status = crate::interval::storage::current_epoch_status(storage)?; +// if !matches!(epoch_status.state, EpochState::AdvancingEpoch) { +// return Err(MixnetContractError::EpochNotInAdvancementState { +// current_state: epoch_status.state, +// }); +// } +// Ok(()) +// } + +pub(crate) fn ensure_is_authorized( + sender: &Addr, + storage: &dyn Storage, +) -> Result<(), MixnetContractError> { + if sender != &crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? { return Err(MixnetContractError::Unauthorized); } Ok(()) } +pub(crate) fn ensure_can_advance_epoch( + sender: &Addr, + storage: &dyn Storage, +) -> Result { + let epoch_status = crate::interval::storage::current_epoch_status(storage)?; + if sender != &epoch_status.being_advanced_by { + // well, we know we're going to throw an error now, + // but we might as well also check if we're even a validator + // to return a possibly better error message + ensure_is_authorized(sender, storage)?; + return Err(MixnetContractError::RewardingValidatorMismatch { + current_validator: sender.clone(), + chosen_validator: epoch_status.being_advanced_by, + }); + } + Ok(epoch_status) +} + pub(crate) fn ensure_is_owner( sender: Addr, storage: &dyn Storage, @@ -158,6 +319,22 @@ pub(crate) fn ensure_proxy_match( 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 { @@ -170,8 +347,8 @@ pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractErro // check if the target address has already bonded a mixnode or gateway, // in either case, return an appropriate error pub(crate) fn ensure_no_existing_bond( - storage: &dyn Storage, sender: &Addr, + storage: &dyn Storage, ) -> Result<(), MixnetContractError> { if mixnodes_storage::mixnode_bonds() .idx diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index 5429dc841b..4e22de893f 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -15,9 +15,11 @@ pub mod test_helpers { use crate::delegations::queries::query_mixnode_delegations_paged; use crate::delegations::storage as delegations_storage; use crate::delegations::transactions::try_delegate_to_mixnode; - use crate::gateways::transactions::try_add_gateway; + 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::interval::transactions::{ - perform_pending_epoch_actions, perform_pending_interval_actions, + perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition, }; use crate::interval::{pending_events, storage as interval_storage}; use crate::mixnet_contract_settings::storage as mixnet_params_storage; @@ -25,6 +27,7 @@ pub mod test_helpers { minimum_mixnode_pledge, rewarding_denom, rewarding_validator_address, }; 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, }; @@ -40,7 +43,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, Addr, BankMsg, CosmosMsg, Storage}; + use cosmwasm_std::{coin, Addr, Api, BankMsg, CosmosMsg, Storage}; use cosmwasm_std::{Coin, Order}; use cosmwasm_std::{Decimal, Empty, MemoryStorage}; use cosmwasm_std::{Deps, OwnedDeps}; @@ -56,9 +59,10 @@ pub mod test_helpers { use mixnet_contract_common::rewarding::simulator::Simulator; use mixnet_contract_common::rewarding::RewardDistribution; use mixnet_contract_common::{ - Delegation, Gateway, InitialRewardingParams, InstantiateMsg, Interval, MixId, MixNode, - Percent, RewardedSetNodeStatus, + Delegation, EpochState, EpochStatus, Gateway, IdentityKey, InitialRewardingParams, + InstantiateMsg, Interval, MixId, MixNode, MixNodeBond, Percent, RewardedSetNodeStatus, }; + use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity::KeyPair; use rand_chacha::rand_core::{CryptoRng, RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -157,6 +161,42 @@ pub mod test_helpers { .collect::>() } + #[allow(unused)] + pub fn join_family( + &mut self, + member: &str, + member_keys: &identity::KeyPair, + head_keys: &identity::KeyPair, + ) { + let identity_signature = member_keys.private_key().sign_text(member); + let join_signature = head_keys + .private_key() + .sign(&member_keys.public_key().to_bytes()) + .to_base58_string(); + let head_identity = head_keys.public_key().to_base58_string(); + + try_join_family( + self.deps_mut(), + mock_info(member, &[]), + Some(identity_signature), + join_signature, + head_identity, + ) + .unwrap(); + } + + 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_keypair(head, None); + let sig = keys.private_key().sign_text(head); + + try_create_family(self.deps_mut(), mock_info(head, &[]), sig, label).unwrap(); + (mix_id, keys) + } + pub fn add_dummy_mixnode(&mut self, owner: &str, stake: Option) -> MixId { let stake = match stake { Some(amount) => { @@ -170,12 +210,11 @@ pub mod test_helpers { add_mixnode(&mut self.rng, self.deps.as_mut(), env, owner, vec![stake]) } - pub fn add_dummy_mixnode_with_proxy( + pub fn add_dummy_mixnode_with_keypair( &mut self, owner: &str, stake: Option, - proxy: Addr, - ) -> MixId { + ) -> (MixId, identity::KeyPair) { let stake = match stake { Some(amount) => { let denom = rewarding_denom(self.deps().storage).unwrap(); @@ -184,7 +223,9 @@ pub mod test_helpers { None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(), }; - let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut self.rng); + let proxy = self.vesting_contract(); + + let keypair = identity::KeyPair::new(&mut self.rng); let owner_signature = keypair .private_key() .sign(owner.as_bytes()) @@ -216,7 +257,98 @@ pub mod test_helpers { .unwrap(); // newly added mixnode gets assigned the current counter + 1 - current_id_counter + 1 + (current_id_counter + 1, keypair) + } + + pub fn add_dummy_mixnode_with_legal_proxy( + &mut self, + owner: &str, + stake: Option, + ) -> MixId { + self.add_dummy_mixnode_with_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, + 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, + ) -> 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 proxy = self.vesting_contract(); + + let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng); + let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut self.rng); + let owner_signature = keypair + .private_key() + .sign(owner.as_bytes()) + .to_base58_string(); + + let env = self.env(); + let info = mock_info(proxy.as_ref(), &[stake]); + + try_add_gateway_on_behalf( + self.deps_mut(), + env, + info, + Gateway { + identity_key: keypair.public_key().to_base58_string(), + sphinx_key: legit_sphinx_key.public_key().to_base58_string(), + ..tests::fixtures::gateway_fixture() + }, + 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, + 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 start_unbonding_mixnode(&mut self, mix_id: MixId) { @@ -263,7 +395,34 @@ pub mod test_helpers { .unwrap(); } - pub fn add_immediate_delegation_with_proxy( + pub fn add_immediate_delegation_with_legal_proxy( + &mut self, + delegator: &str, + amount: impl Into, + 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, @@ -275,17 +434,41 @@ pub mod test_helpers { denom, amount: amount.into(), }; - let env = self.env(); - pending_events::delegate( - self.deps_mut(), - &env, - env.block.height, - Addr::unchecked(delegator), + + 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, - amount, + mix_rewarding.total_unit_reward, + stored_delegation_amount, + self.env.block.height, Some(proxy), - ) - .unwrap(); + ); + + 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)] @@ -316,6 +499,48 @@ pub mod test_helpers { .unwrap(); } + pub fn start_epoch_transition(&mut self) { + let env = self.env.clone(); + let sender = self.rewarding_validator.clone(); + try_begin_epoch_transition(self.deps_mut(), env, sender).unwrap(); + } + + pub fn set_epoch_in_progress_state(&mut self) { + let being_advanced_by = self.rewarding_validator.sender.clone(); + interval_storage::save_current_epoch_status( + self.deps_mut().storage, + &EpochStatus { + being_advanced_by, + state: EpochState::InProgress, + }, + ) + .unwrap(); + } + + pub fn set_epoch_reconciliation_state(&mut self) { + let being_advanced_by = self.rewarding_validator.sender.clone(); + interval_storage::save_current_epoch_status( + self.deps_mut().storage, + &EpochStatus { + being_advanced_by, + state: EpochState::ReconcilingEvents, + }, + ) + .unwrap(); + } + + pub fn set_epoch_advancement_state(&mut self) { + let being_advanced_by = self.rewarding_validator.sender.clone(); + interval_storage::save_current_epoch_status( + self.deps_mut().storage, + &EpochStatus { + being_advanced_by, + state: EpochState::AdvancingEpoch, + }, + ) + .unwrap(); + } + #[allow(unused)] pub fn pending_operator_reward(&mut self, mix: MixId) -> Decimal { query_pending_mixnode_operator_reward(self.deps(), mix) @@ -376,10 +601,12 @@ pub mod test_helpers { ) } - interval_storage::save_interval(self.deps_mut().storage, &advanced).unwrap() + interval_storage::save_interval(self.deps_mut().storage, &advanced).unwrap(); + // if we're going into next epoch, we're back into in progress + self.set_epoch_in_progress_state(); } - pub fn update_rewarded_set(&mut self, nodes: Vec) { + pub fn force_change_rewarded_set(&mut self, nodes: Vec) { let active_set_size = rewards_storage::REWARDING_PARAMS .load(self.deps().storage) .unwrap() @@ -411,6 +638,20 @@ pub mod test_helpers { .collect::>() } + pub fn reward_with_distribution_with_state_bypass( + &mut self, + mix_id: MixId, + performance: Performance, + ) -> RewardDistribution { + let initial_status = + interval_storage::current_epoch_status(self.deps().storage).unwrap(); + self.start_epoch_transition(); + let res = self.reward_with_distribution(mix_id, performance); + interval_storage::save_current_epoch_status(self.deps_mut().storage, &initial_status) + .unwrap(); + res + } + pub fn reward_with_distribution( &mut self, mix_id: MixId, @@ -463,6 +704,11 @@ pub mod test_helpers { .unwrap() } + #[allow(unused)] + pub fn mix_bond(&self, mix_id: MixId) -> MixNodeBond { + mixnode_bonds().load(self.deps().storage, mix_id).unwrap() + } + pub fn delegation(&self, mix: MixId, owner: &str, proxy: &Option) -> Delegation { read_delegation(self.deps().storage, mix, &Addr::unchecked(owner), proxy).unwrap() } diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index dd127b54ba..0c45bb149d 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -17,20 +17,20 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -cw-utils = { version = "0.13.4" } -cw2 = { version = "0.13.4" } -cw3 = { version = "0.13.4" } -cw3-fixed-multisig = { version = "0.13.4", features = ["library"] } -cw4 = { version = "0.13.4" } -cw-storage-plus = { version = "0.13.4" } -cosmwasm-std = { version = "1.0.0" } +cw-utils = { workspace = true } +cw2 = { workspace = true } +cw3 = { workspace = true } +cw3-fixed-multisig = { workspace = true, features = ["library"] } +cw4 = { workspace = true } +cw-storage-plus = { workspace = true } +cosmwasm-std = { workspace = true } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } -multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } +nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } +nym-multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } [dev-dependencies] cosmwasm-schema = { version = "1.0.0" } cw4-group = { path = "../cw4-group", version = "0.13.4" } -cw-multi-test = { version = "0.13.4" } +cw-multi-test = { workspace = true } diff --git a/contracts/multisig/cw3-flex-multisig/src/contract.rs b/contracts/multisig/cw3-flex-multisig/src/contract.rs index edd1a648b2..bb24d38774 100644 --- a/contracts/multisig/cw3-flex-multisig/src/contract.rs +++ b/contracts/multisig/cw3-flex-multisig/src/contract.rs @@ -18,8 +18,8 @@ use cw_storage_plus::Bound; use cw_utils::{maybe_addr, Expiration, ThresholdResponse}; use crate::state::{Config, CONFIG}; -use multisig_contract_common::error::ContractError; -use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use nym_multisig_contract_common::error::ContractError; +use nym_multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw3-flex-multisig"; @@ -502,7 +502,7 @@ mod tests { // uploads code and returns address of group contract fn instantiate_group(app: &mut App, members: Vec) -> Addr { let group_id = app.store_code(contract_group()); - let msg = group_contract_common::msg::InstantiateMsg { + let msg = nym_group_contract_common::msg::InstantiateMsg { admin: Some(OWNER.into()), members, }; @@ -1410,7 +1410,7 @@ mod tests { // adds NEWBIE with 2 power -> with snapshot, invalid vote // removes VOTER3 -> with snapshot, can vote on proposal let newbie: &str = "newbie"; - let update_msg = group_contract_common::msg::ExecuteMsg::UpdateMembers { + let update_msg = nym_group_contract_common::msg::ExecuteMsg::UpdateMembers { remove: vec![VOTER3.into()], add: vec![member(VOTER2, 21), member(newbie, 2)], }; @@ -1623,7 +1623,7 @@ mod tests { // admin changes the group (3 -> 0, 2 -> 9, 0 -> 29) - total = 56, require 29 to pass let newbie: &str = "newbie"; - let update_msg = group_contract_common::msg::ExecuteMsg::UpdateMembers { + let update_msg = nym_group_contract_common::msg::ExecuteMsg::UpdateMembers { remove: vec![VOTER3.into()], add: vec![member(VOTER2, 9), member(newbie, 29)], }; @@ -1702,7 +1702,7 @@ mod tests { // admin changes the group (3 -> 0, 2 -> 9, 0 -> 28) - total = 55, require 28 to pass let newbie: &str = "newbie"; - let update_msg = group_contract_common::msg::ExecuteMsg::UpdateMembers { + let update_msg = nym_group_contract_common::msg::ExecuteMsg::UpdateMembers { remove: vec![VOTER3.into()], add: vec![member(VOTER2, 9), member(newbie, 29)], }; diff --git a/contracts/multisig/cw4-group/Cargo.toml b/contracts/multisig/cw4-group/Cargo.toml index 7c98f4c991..4605ec5bcc 100644 --- a/contracts/multisig/cw4-group/Cargo.toml +++ b/contracts/multisig/cw4-group/Cargo.toml @@ -24,17 +24,17 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } +nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } -cw-utils = { version = "0.13.4" } -cw2 = { version = "0.13.4" } -cw4 = { version = "0.13.4" } -cw-controllers = { version = "0.13.4" } -cw-storage-plus = { version = "0.13.4" } -cosmwasm-std = { version = "1.0.0" } +cw-utils = { workspace = true } +cw2 = { workspace = true } +cw4 = { workspace = true } +cw-controllers = { workspace = true } +cw-storage-plus = { workspace = true } +cosmwasm-std = { workspace = true } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } [dev-dependencies] -cosmwasm-schema = { version = "1.0.0" } +cosmwasm-schema = { workspace = true } diff --git a/contracts/multisig/cw4-group/src/contract.rs b/contracts/multisig/cw4-group/src/contract.rs index cb9c4f2787..543388c6fa 100644 --- a/contracts/multisig/cw4-group/src/contract.rs +++ b/contracts/multisig/cw4-group/src/contract.rs @@ -14,7 +14,7 @@ use cw_utils::maybe_addr; use crate::error::ContractError; use crate::state::{ADMIN, HOOKS, MEMBERS, TOTAL}; -use group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw4-group"; diff --git a/contracts/multisig/cw4-group/src/helpers.rs b/contracts/multisig/cw4-group/src/helpers.rs index ac0e56f42b..eae8a2434e 100644 --- a/contracts/multisig/cw4-group/src/helpers.rs +++ b/contracts/multisig/cw4-group/src/helpers.rs @@ -5,7 +5,7 @@ use std::ops::Deref; use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; use cw4::{Cw4Contract, Member}; -use group_contract_common::msg::ExecuteMsg; +use nym_group_contract_common::msg::ExecuteMsg; /// Cw4GroupContract is a wrapper around Cw4Contract that provides a lot of helpers /// for working with cw4-group contracts. diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index ad501e9bdb..341fdf284f 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-vesting-contract" -version = "1.1.4" +version = "1.2.0" description = "Nym vesting contract" edition = { workspace = true } authors = { workspace = true } @@ -20,22 +20,29 @@ name = "vesting_contract" crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" } -contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.2.0" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.2.0" } +mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" } +contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.3.0" } +vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.3.0" } -cosmwasm-std = { version = "1.0.0 "} -cw-storage-plus = { version = "0.13.4", features = ["iterator"] } +cosmwasm-std = { workspace = true } +cosmwasm-derive = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true, features = ["iterator"] } schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = { version = "1.0" } +semver = { version = "1.0.16", default-features = false } [dev-dependencies] rand_chacha = "0.3.1" base64 = "0.21.0" hex = "0.4.3" serde_json = "1.0.66" +cosmwasm-crypto = { workspace = true } [build-dependencies] vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] } + +[profile.release] +overflow-checks = true diff --git a/contracts/vesting/build.rs b/contracts/vesting/build.rs index 3567a950a4..dfffaafd59 100644 --- a/contracts/vesting/build.rs +++ b/contracts/vesting/build.rs @@ -9,5 +9,5 @@ fn main() { // If we don't have access to git information, such as in a docs.rs build, don't error *config.git_mut().skip_if_error_mut() = true; } - vergen(Config::default()).expect("failed to extract build metadata") + vergen(config).expect("failed to extract build metadata") } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 8ba6ebd223..39eff3a86c 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -10,11 +10,12 @@ use crate::vesting::{populate_vesting_periods, Account}; use contracts_common::ContractBuildInformation; use cosmwasm_std::{ coin, entry_point, to_binary, Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order, - QueryResponse, Response, StdResult, Timestamp, Uint128, + QueryResponse, Response, StdError, StdResult, Timestamp, Uint128, }; use cw_storage_plus::Bound; use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use mixnet_contract_common::{Gateway, MixId, MixNode}; +use semver::Version; use vesting_contract_common::events::{ new_ownership_transfer_event, new_periodic_vesting_account_event, new_staking_address_update_event, new_track_gateway_unbond_event, @@ -30,8 +31,25 @@ use vesting_contract_common::{ VestingCoinsResponse, VestingDelegation, }; +// version info for migration info +const CONTRACT_NAME: &str = "crate:nym-vesting-contract"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000); +// as of 01.02.23 the maximum number of delegations anyone has made towards particular mixnode is 12. +// thus restricting it to 25, which is more than double of that, doesn't seem too unreasonable. +// and is going to alleviate the issue of unbounded iteration in `remove_delegations_for_mix` +// that happens upon advancing the current epoch. +// +// However, do note it doesn't necessarily mean that upon reaching this limit it's impossible to perform +// further delegations (towards the same node) +// while this might not be the best workaround, you could remove the existing delegation +// (thus removing all separate entries from the storage, i.e. the `DELEGATIONS` map) +// and re-delegate it with the reclaimed amount (which will include all rewards) +// which will only result in a single key-value being stored. +pub const MAX_PER_MIX_DELEGATIONS: u32 = 25; + /// Instantiate the contract #[entry_point] pub fn instantiate( @@ -47,11 +65,48 @@ pub fn instantiate( ADMIN.save(deps.storage, &info.sender)?; MIXNET_CONTRACT_ADDRESS.save(deps.storage, &mixnet_contract_address)?; MIX_DENOM.save(deps.storage, &msg.mix_denom)?; + + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) } #[entry_point] -pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { +pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { + // this is the first migration that uses cw2 standard and thus the value in the storage doesn't yet exist + // set it instead. + if matches!( + cw2::get_contract_version(deps.storage), + Err(StdError::NotFound { .. }) + ) { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + // note: don't remove this particular bit of code as we have to ALWAYS check whether we have to update the stored version + let version: Version = + CONTRACT_VERSION + .parse() + .map_err(|error: semver::Error| ContractError::SemVerFailure { + value: CONTRACT_VERSION.to_string(), + error_message: error.to_string(), + })?; + + let storage_version_raw = cw2::get_contract_version(deps.storage)?.version; + let storage_version: Version = + storage_version_raw + .parse() + .map_err(|error: semver::Error| ContractError::SemVerFailure { + value: storage_version_raw, + error_message: error.to_string(), + })?; + + if storage_version < version { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // If state structure changed in any contract version in the way migration is needed, it + // should occur here + } + Ok(Response::new()) } @@ -68,13 +123,20 @@ pub fn execute( label, } => try_create_family(info, deps, owner_signature, label), ExecuteMsg::JoinFamily { - signature, + node_identity_signature, + family_signature, family_head, - } => try_join_family(info, deps, signature, family_head), + } => try_join_family( + info, + deps, + node_identity_signature, + family_signature, + family_head, + ), ExecuteMsg::LeaveFamily { - signature, + node_identity_signature, family_head, - } => try_leave_family(info, deps, signature, family_head), + } => try_leave_family(info, deps, node_identity_signature, family_head), ExecuteMsg::KickFamilyMember { signature, member } => { try_kick_family_member(info, deps, signature, member) } @@ -177,20 +239,26 @@ pub fn try_create_family( pub fn try_join_family( info: MessageInfo, deps: DepsMut, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: String, ) -> Result { let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; - account.try_join_family(deps.storage, signature, &family_head) + account.try_join_family( + deps.storage, + node_identity_signature, + family_signature, + &family_head, + ) } pub fn try_leave_family( info: MessageInfo, deps: DepsMut, - signature: String, + node_identity_signature: String, family_head: String, ) -> Result { let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; - account.try_leave_family(deps.storage, signature, &family_head) + account.try_leave_family(deps.storage, node_identity_signature, &family_head) } pub fn try_kick_family_member( info: MessageInfo, @@ -474,6 +542,14 @@ fn try_delegate_to_mixnode( env: Env, deps: DepsMut<'_>, ) -> Result { + // TODO + // as of 01.02.23 + // thus restricting it to 25, which is more than double of that, doesn't seem too unreasonable. + + // while this might not be the best workaround, if user wishes to delegate more tokens towards the same node + // they could remove the existing delegation (thus removing all separate entries from the storage) + // and re-delegate it with the reclaimed amount (which will include all rewards). + let mix_denom = MIX_DENOM.load(deps.storage)?; let amount = validate_funds(&[amount], mix_denom)?; @@ -602,6 +678,7 @@ pub(crate) fn try_create_periodic_vesting_account( pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result { let query_res = match msg { QueryMsg::GetContractVersion {} => to_binary(&get_contract_version()), + QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?), QueryMsg::GetAccountsPaged { start_next_after, limit, diff --git a/contracts/vesting/src/errors.rs b/contracts/vesting/src/errors.rs index 7fc596f6e4..195e12e697 100644 --- a/contracts/vesting/src/errors.rs +++ b/contracts/vesting/src/errors.rs @@ -1,3 +1,4 @@ +use crate::storage::AccountStorageKey; use cosmwasm_std::{Addr, OverflowError, StdError, Uint128}; use mixnet_contract_common::MixId; use thiserror::Error; @@ -84,6 +85,21 @@ pub enum ContractError { #[error("VESTING: {address} is not permitted to perform staking on behalf of {for_account}")] InvalidStakingAccount { address: Addr, for_account: Addr }, + #[error("VESTING: {address} ({acc_id} has already performed {num} individual delegations towards {mix_id}. No further delegations are allowed. Please consider consolidating those delegations instead. The current cap is {cap}.")] + TooManyDelegations { + address: Addr, + acc_id: AccountStorageKey, + mix_id: MixId, + num: u32, + cap: u32, + }, + + #[error("VESTING: Failed to parse {value} into a valid SemVer version: {error_message}")] + SemVerFailure { + value: String, + error_message: String, + }, + #[error("VESTING: {message}")] Other { message: String }, } diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs index a0b5b36ac7..d1c5af31e3 100644 --- a/contracts/vesting/src/storage.rs +++ b/contracts/vesting/src/storage.rs @@ -1,5 +1,6 @@ use crate::errors::ContractError; use crate::vesting::Account; +use cosmwasm_std::Order; use cosmwasm_std::{Addr, Api, Storage, Uint128}; use cw_storage_plus::{Item, Map}; use mixnet_contract_common::{IdentityKey, MixId}; @@ -55,7 +56,13 @@ pub fn save_delegation( amount: Uint128, storage: &mut dyn Storage, ) -> Result<(), ContractError> { - DELEGATIONS.save(storage, key, &amount)?; + let existing_delegation_amount = if let Some(delegation) = DELEGATIONS.may_load(storage, key)? { + delegation + } else { + Uint128::zero() + }; + let new_delegations_amount = existing_delegation_amount + amount; + DELEGATIONS.save(storage, key, &new_delegations_amount)?; Ok(()) } @@ -67,6 +74,27 @@ pub fn remove_delegation( Ok(()) } +pub fn load_delegation_timestamps( + prefix: (AccountStorageKey, MixId), + storage: &dyn Storage, +) -> Result, ContractError> { + let block_timestamps = DELEGATIONS + .prefix(prefix) + .keys(storage, None, None, Order::Ascending) + .collect::, _>>()?; + Ok(block_timestamps) +} + +pub fn count_subdelegations_for_mix( + prefix: (AccountStorageKey, MixId), + storage: &dyn Storage, +) -> u32 { + DELEGATIONS + .prefix(prefix) + .keys(storage, None, None, Order::Ascending) + .count() as u32 +} + pub fn load_withdrawn( key: AccountStorageKey, storage: &dyn Storage, diff --git a/contracts/vesting/src/traits/node_families.rs b/contracts/vesting/src/traits/node_families.rs index 274ac41f94..3e2e6e9863 100644 --- a/contracts/vesting/src/traits/node_families.rs +++ b/contracts/vesting/src/traits/node_families.rs @@ -13,7 +13,8 @@ pub trait NodeFamilies { fn try_join_family( &self, storage: &dyn Storage, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: IdentityKeyRef, ) -> Result; diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs index 23c932ef85..beaedf2be2 100644 --- a/contracts/vesting/src/vesting/account/delegating_account.rs +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -1,3 +1,4 @@ +use crate::contract::MAX_PER_MIX_DELEGATIONS; use crate::errors::ContractError; use crate::storage::save_delegation; use crate::storage::MIXNET_CONTRACT_ADDRESS; @@ -36,6 +37,17 @@ impl DelegatingAccount for Account { storage: &mut dyn Storage, ) -> Result { let current_balance = self.ensure_valid_additional_stake(&coin, storage)?; + let num_subdelegations = self.num_subdelegations_for_mix(mix_id, storage); + + if num_subdelegations >= MAX_PER_MIX_DELEGATIONS { + return Err(ContractError::TooManyDelegations { + address: self.owner_address.clone(), + acc_id: self.storage_key(), + mix_id, + num: num_subdelegations, + cap: MAX_PER_MIX_DELEGATIONS, + }); + } let msg = MixnetExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index 1df8c056c6..01151ddc6c 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -1,13 +1,13 @@ use super::VestingPeriod; use crate::errors::ContractError; use crate::storage::{ - load_balance, load_bond_pledge, load_gateway_pledge, load_withdrawn, remove_bond_pledge, - remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge, - save_gateway_pledge, save_withdrawn, AccountStorageKey, BlockTimestampSecs, DELEGATIONS, KEY, + count_subdelegations_for_mix, load_balance, load_bond_pledge, load_delegation_timestamps, + load_gateway_pledge, load_withdrawn, remove_bond_pledge, remove_delegation, + remove_gateway_pledge, save_account, save_balance, save_bond_pledge, save_gateway_pledge, + save_withdrawn, AccountStorageKey, BlockTimestampSecs, DELEGATIONS, KEY, }; use crate::traits::VestingAccount; use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128}; -use cw_storage_plus::Bound; use mixnet_contract_common::MixId; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -274,39 +274,21 @@ impl Account { .is_some() } + pub fn num_subdelegations_for_mix(&self, mix_id: MixId, storage: &dyn Storage) -> u32 { + count_subdelegations_for_mix((self.storage_key(), mix_id), storage) + } + pub fn remove_delegations_for_mix( &self, mix_id: MixId, storage: &mut dyn Storage, ) -> Result<(), ContractError> { - let limit = 50; - let mut start_after = None; - let mut block_heights = Vec::new(); - let mut prev_len = 0; - // TODO: Test this - loop { - block_heights.extend( - DELEGATIONS - .prefix((self.storage_key(), mix_id)) - .keys(storage, start_after, None, Order::Ascending) - .take(limit) - .filter_map(|key| key.ok()), - ); + // note that the limit is implicitly set to `MAX_PER_MIX_DELEGATIONS` + // as it should be impossible to create more delegations than that. + let block_timestamps = load_delegation_timestamps((self.storage_key(), mix_id), storage)?; - if prev_len == block_heights.len() { - break; - } - - prev_len = block_heights.len(); - - start_after = block_heights.last().map(|last| Bound::exclusive(*last)); - if start_after.is_none() { - break; - } - } - - for block_height in block_heights { - remove_delegation((self.storage_key(), mix_id, block_height), storage)?; + for block_timestamp in block_timestamps { + remove_delegation((self.storage_key(), mix_id, block_timestamp), storage)?; } Ok(()) } diff --git a/contracts/vesting/src/vesting/account/node_families.rs b/contracts/vesting/src/vesting/account/node_families.rs index a83efdabf9..edf07d27fd 100644 --- a/contracts/vesting/src/vesting/account/node_families.rs +++ b/contracts/vesting/src/vesting/account/node_families.rs @@ -24,12 +24,14 @@ impl NodeFamilies for Account { fn try_join_family( &self, storage: &dyn Storage, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: IdentityKeyRef, ) -> Result { let msg = MixnetExecuteMsg::JoinFamilyOnBehalf { member_address: self.owner_address().to_string(), - signature, + node_identity_signature, + family_signature, family_head: family_head.to_string(), }; @@ -41,12 +43,12 @@ impl NodeFamilies for Account { fn try_leave_family( &self, storage: &dyn Storage, - signature: String, + node_identity_signature: String, family_head: IdentityKeyRef, ) -> Result { let msg = MixnetExecuteMsg::LeaveFamilyOnBehalf { member_address: self.owner_address().to_string(), - signature, + node_identity_signature, family_head: family_head.to_string(), }; diff --git a/contracts/vesting/src/vesting/account/vesting_account.rs b/contracts/vesting/src/vesting/account/vesting_account.rs index cb17766013..5b64b9896e 100644 --- a/contracts/vesting/src/vesting/account/vesting_account.rs +++ b/contracts/vesting/src/vesting/account/vesting_account.rs @@ -132,7 +132,7 @@ impl VestingAccount for Account { } fn get_end_time(&self) -> Timestamp { - self.periods[(self.num_vesting_periods() - 1)].end_time() + self.periods[self.num_vesting_periods() - 1].end_time() } fn get_original_vesting(&self) -> Result { diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index 68a01b6a46..bd50838175 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -46,11 +46,12 @@ mod tests { use crate::traits::DelegatingAccount; use crate::traits::VestingAccount; use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount}; + use crate::vesting::{populate_vesting_periods, Account}; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coin, coins, Addr, Coin, Timestamp, Uint128}; use mixnet_contract_common::mixnode::MixNodeCostParams; use mixnet_contract_common::{Gateway, MixNode, Percent}; - use vesting_contract_common::messages::ExecuteMsg; + use vesting_contract_common::messages::{ExecuteMsg, VestingSpecification}; use vesting_contract_common::Period; use vesting_contract_common::PledgeCap; @@ -778,4 +779,75 @@ mod tests { let pledge = account.load_gateway_pledge(&deps.storage).unwrap().unwrap(); assert_eq!(Uint128::new(90_000_000_000), pledge.amount().amount); } + + #[test] + fn test_delegations_cap() { + let mut deps = init_contract(); + let mut env = mock_env(); + + let vesting_period_length_secs = 3600; + + let account_creation_timestamp = 1650000000; + let account_creation_blockheight = 12345; + + env.block.height = account_creation_blockheight; + env.block.time = Timestamp::from_seconds(account_creation_timestamp); + + // lets define some helper timestamps + + // lets create our vesting account + let periods = populate_vesting_periods( + account_creation_timestamp, + VestingSpecification::new(None, Some(vesting_period_length_secs), None), + ); + + let vesting_account = Account::new( + Addr::unchecked("owner"), + Some(Addr::unchecked("staking")), + Coin { + amount: Uint128::new(1_000_000_000_000), + denom: TEST_COIN_DENOM.to_string(), + }, + Timestamp::from_seconds(account_creation_timestamp), + periods, + Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))), + deps.as_mut().storage, + ) + .unwrap(); + + // time for some delegations + + let mix_id = 42; + + let delegation = Coin { + amount: Uint128::new(42), + denom: TEST_COIN_DENOM.to_string(), + }; + + // you can have at most `MAX_PER_MIX_DELEGATIONS` delegations so those should be fine + for _ in 0..MAX_PER_MIX_DELEGATIONS { + vesting_account + .try_delegate_to_mixnode(mix_id, delegation.clone(), &env, &mut deps.storage) + .unwrap(); + + env.block.height += 1; + env.block.time = env.block.time.plus_seconds(42); + } + + // but the additional one is going to fail + let res = vesting_account + .try_delegate_to_mixnode(mix_id, delegation.clone(), &env, &mut deps.storage) + .unwrap_err(); + + assert_eq!( + res, + ContractError::TooManyDelegations { + address: vesting_account.owner_address(), + acc_id: vesting_account.storage_key(), + mix_id, + num: MAX_PER_MIX_DELEGATIONS, + cap: MAX_PER_MIX_DELEGATIONS + } + ); + } } diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 54ef01fd38..a9dde6c94a 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.12" +version = "1.1.13" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -24,7 +24,7 @@ serde_json = "1.0.66" thiserror = "1.0.29" tokio = {version = "1.21.2", features = ["full"] } maxminddb = "0.23.0" -dotenv = "0.15.0" +dotenvy = "0.15.6" rand = "0.8.5" rand_seeder = "0.2.3" rand_pcg = "0.3.1" diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index a435da4a9c..676f1c5cea 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -4,7 +4,7 @@ extern crate rocket; extern crate rocket_okapi; use clap::Parser; -use dotenv::dotenv; +use dotenvy::dotenv; use log::info; use nym_bin_common::logging::setup_logging; use nym_network_defaults::setup_env; diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index 6cbb295b6b..1a8d34dc7a 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -42,6 +42,7 @@ pub(crate) struct PrettyDetailedMixNodeBond { pub operating_cost: Coin, pub profit_margin_percent: Percent, pub family_id: Option, + pub blacklisted: bool, } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index 0cbc7b0167..0fc351ed89 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -162,6 +162,7 @@ impl ThreadsafeMixNodesCache { operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(), profit_margin_percent: rewarding_info.cost_params.profit_margin_percent, family_id, + blacklisted: node.blacklisted, } } diff --git a/explorer-api/src/tasks.rs b/explorer-api/src/tasks.rs index 0602a2af69..2019d74dce 100644 --- a/explorer-api/src/tasks.rs +++ b/explorer-api/src/tasks.rs @@ -43,7 +43,7 @@ impl ExplorerApiTasks { async fn retrieve_all_mixnodes(&self) -> Vec { info!("About to retrieve all mixnode bonds..."); - self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed) + self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed_unfiltered) .await } diff --git a/explorer/src/api/index.ts b/explorer/src/api/index.ts index c83b033283..fa3dae5cb1 100644 --- a/explorer/src/api/index.ts +++ b/explorer/src/api/index.ts @@ -1,4 +1,5 @@ import { + API_BASE_URL, BLOCK_API, COUNTRY_DATA_API, GATEWAYS_API, @@ -27,6 +28,7 @@ import { StatusResponse, SummaryOverviewResponse, ValidatorsResponse, + Environment, GatewayBondAnnotated, GatewayBond, DirectoryService, @@ -157,3 +159,8 @@ export class Api { return json; }; } + +export const getEnvironment = (): Environment => { + const matchEnv = (env: Environment) => API_BASE_URL?.toLocaleLowerCase().includes(env) && env; + return matchEnv('sandbox') || matchEnv('qa') || 'mainnet'; +}; diff --git a/explorer/src/components/MixNodes/Economics/Columns.ts b/explorer/src/components/MixNodes/Economics/Columns.ts index c3ac85baaf..b2115c0749 100644 --- a/explorer/src/components/MixNodes/Economics/Columns.ts +++ b/explorer/src/components/MixNodes/Economics/Columns.ts @@ -36,16 +36,16 @@ export const EconomicsInfoColumns: ColumnsType[] = [ tooltipInfo: 'Monthly operational cost of running this node. This cost is set by the operator and it influences how the rewards are split between the operator and delegators.', }, - { - field: 'avgUptime', - title: 'Avg. Score', - width: '10%', - tooltipInfo: "Mixnode's average routing score in the last 24 hour", - }, { field: 'nodePerformance', title: 'Routing Score', + width: '10%', tooltipInfo: "Mixnode's most recent score (measured in the last 15 minutes). Routing score is relative to that of the network. Each time a gateway is tested, the test packets have to go through the full path of the network (gateway + 3 nodes). If a node in the path drop packets it will affect the score of the gateway and other nodes in the test.", }, + { + field: 'avgUptime', + title: 'Avg. Score', + tooltipInfo: "Mixnode's average routing score in the last 24 hour", + }, ]; diff --git a/explorer/src/components/MixNodes/Economics/Table.tsx b/explorer/src/components/MixNodes/Economics/Table.tsx index 74bfa30251..e6630334db 100644 --- a/explorer/src/components/MixNodes/Economics/Table.tsx +++ b/explorer/src/components/MixNodes/Economics/Table.tsx @@ -53,7 +53,6 @@ export const DelegatorsInfoTable: FCWithChildren { const { field } = columnsData[index]; const value: EconomicsRowsType = (eachRow as any)[field]; - console.log(value); return ( { const theme = useTheme(); - const { navState, updateNavState } = useMainContext(); + const { navState, updateNavState, environment } = useMainContext(); const [drawerOpen, setDrawerOpen] = React.useState(false); // Set maintenance banner to false by default to don't display it const [openMaintenance, setOpenMaintenance] = React.useState(false); + const explorerName = + `${environment && environment.charAt(0).toUpperCase() + environment.slice(1)} Explorer` || 'Mainnet Explorer'; + + const switchNetworkText = environment === 'mainnet' ? 'Switch to Testnet' : 'Switch to Mainnet'; + const switchNetworkLink = + environment === 'mainnet' ? 'https://sandbox-explorer.nymtech.net' : 'https://explorer.nymtech.net'; + const toggleDrawer = () => { setDrawerOpen(!drawerOpen); }; @@ -59,6 +66,7 @@ export const MobileNav: FCWithChildren = ({ children }) => { sx={{ display: 'flex', justifyContent: 'space-between', + alignItems: 'center', width: '100%', }} > @@ -72,7 +80,7 @@ export const MobileNav: FCWithChildren = ({ children }) => { }} > - + { color: theme.palette.nym.networkExplorer.nav.text, fontSize: '18px', fontWeight: 600, - ml: 2, }} > - - Network Explorer + + {explorerName} + - + ({ export const DarkLightSwitchMobile: FCWithChildren = () => { const { toggleMode } = useMainContext(); return ( - ); diff --git a/explorer/src/context/main.tsx b/explorer/src/context/main.tsx index 6c0dc490e1..38335830fa 100644 --- a/explorer/src/context/main.tsx +++ b/explorer/src/context/main.tsx @@ -10,9 +10,10 @@ import { MixnodeStatus, SummaryOverviewResponse, ValidatorsResponse, + Environment, } from '../typeDefs/explorer-api'; import { EnumFilterKey } from '../typeDefs/filters'; -import { Api } from '../api'; +import { Api, getEnvironment } from '../api'; import { NavOptionType, originalNavOptions } from './nav'; interface StateData { @@ -25,6 +26,7 @@ interface StateData { mode: PaletteMode; navState: NavOptionType[]; validators?: ApiState; + environment?: Environment; serviceProviders?: ApiState; } @@ -49,6 +51,9 @@ export const MainContext = React.createContext({ export const useMainContext = (): React.ContextType => React.useContext(MainContext); export const MainContextProvider: FCWithChildren = ({ children }) => { + // network explorer environment + const [environment, setEnvironment] = React.useState('mainnet'); + // light/dark mode const [mode, setMode] = React.useState('dark'); @@ -190,10 +195,12 @@ export const MainContextProvider: FCWithChildren = ({ children }) => { fetchCountryData(), fetchServiceProviders(), ]); + setEnvironment(getEnvironment()); }, []); const state = React.useMemo( () => ({ + environment, block, countryData, fetchMixnodes, @@ -210,6 +217,7 @@ export const MainContextProvider: FCWithChildren = ({ children }) => { serviceProviders, }), [ + environment, block, countryData, gateways, diff --git a/explorer/src/pages/GatewayDetail/index.tsx b/explorer/src/pages/GatewayDetail/index.tsx index cad23ad16f..3382947512 100644 --- a/explorer/src/pages/GatewayDetail/index.tsx +++ b/explorer/src/pages/GatewayDetail/index.tsx @@ -111,7 +111,12 @@ const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: Gat {uptimeStory && ( {uptimeStory.error && } - + )} diff --git a/explorer/src/pages/Gateways/index.tsx b/explorer/src/pages/Gateways/index.tsx index 5c52f4a5b3..77e9e04fa2 100644 --- a/explorer/src/pages/Gateways/index.tsx +++ b/explorer/src/pages/Gateways/index.tsx @@ -153,9 +153,9 @@ export const PageGateways: FCWithChildren = () => { renderCell: (params: GridRenderCellParams) => ( {params.value} diff --git a/explorer/src/pages/MixnodeDetail/index.tsx b/explorer/src/pages/MixnodeDetail/index.tsx index 706dcba197..288c1bd8c0 100644 --- a/explorer/src/pages/MixnodeDetail/index.tsx +++ b/explorer/src/pages/MixnodeDetail/index.tsx @@ -12,6 +12,7 @@ import { WorldMap } from '../../components/WorldMap'; import { MixNodeDetailSection } from '../../components/MixNodes/DetailSection'; import { MixnodeContextProvider, useMixnodeContext } from '../../context/mixnode'; import { Title } from '../../components/Title'; +import { useIsMobile } from '../../hooks/useIsMobile'; const columns: ColumnsType[] = [ { @@ -41,6 +42,7 @@ const columns: ColumnsType[] = [ field: 'self_percentage', width: '10%', title: 'Bond %', + tooltipInfo: "Percentage of the operator's bond to the total stake on the node", }, { @@ -64,7 +66,7 @@ const columns: ColumnsType[] = [ */ const PageMixnodeDetailWithState: FCWithChildren = () => { const { mixNode, mixNodeRow, description, stats, status, uptimeStory, uniqDelegations } = useMixnodeContext(); - console.log(mixNodeRow); + const isMobile = useIsMobile(); return ( @@ -73,6 +75,11 @@ const PageMixnodeDetailWithState: FCWithChildren = () => { {mixNodeRow && description?.data && ( <MixNodeDetailSection mixNodeRow={mixNodeRow} mixnodeDescription={description.data} /> )} + {mixNodeRow?.blacklisted && ( + <Typography textAlign={isMobile ? 'left' : 'right'} fontSize="smaller" sx={{ color: 'error.main' }}> + This node is having a poor performance + </Typography> + )} </Grid> </Grid> <Grid container> @@ -134,7 +141,12 @@ const PageMixnodeDetailWithState: FCWithChildren = () => { {uptimeStory && ( <ContentCard title="Routing Score"> {uptimeStory.error && <ComponentError text="There was a problem retrieving routing score." />} - <UptimeChart loading={uptimeStory.isLoading} xLabel="date" uptimeStory={uptimeStory} /> + <UptimeChart + loading={uptimeStory.isLoading} + xLabel="Date" + yLabel="Daily average" + uptimeStory={uptimeStory} + /> </ContentCard> )} </Grid> diff --git a/explorer/src/pages/ServiceProviders/index.tsx b/explorer/src/pages/ServiceProviders/index.tsx index 63bdf956ea..512042a4e5 100644 --- a/explorer/src/pages/ServiceProviders/index.tsx +++ b/explorer/src/pages/ServiceProviders/index.tsx @@ -22,9 +22,10 @@ const SupportedApps = () => { <FormControl size="small"> <Select value={selected} onChange={handleChange} displayEmpty sx={{ mr: 2 }}> <MenuItem value="">Supported Apps</MenuItem> - <MenuItem sx={{ opacity: 1 }}>Keybase</MenuItem> + <MenuItem>Keybase</MenuItem> <MenuItem>Telegram</MenuItem> <MenuItem>Electrum</MenuItem> + <MenuItem>Blockstream Green</MenuItem> </Select> </FormControl> ); diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index 6f1fe6a891..e6cc617ddb 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -89,6 +89,7 @@ export interface MixNodeResponseItem { uncapped_saturation: number; operating_cost: Amount; profit_margin_percent: string; + blacklisted: boolean; } export type MixNodeResponse = MixNodeResponseItem[]; @@ -238,6 +239,8 @@ export type MixNodeEconomicDynamicsStatsResponse = { current_interval_uptime: number; }; +export type Environment = 'mainnet' | 'sandbox' | 'qa'; + export type DirectoryServiceProvider = { id: string; description: string; diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 95dc96eb6a..75b3e5b078 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.12" +version = "1.1.13" authors = [ "Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>", @@ -23,7 +23,7 @@ clap = { version = "4.0", features = ["cargo", "derive"] } colored = "2.0" dashmap = "4.0" dirs = "4.0" -dotenv = "0.15.0" +dotenvy = { workspace = true } futures = "0.3" humantime-serde = "1.0.1" lazy_static = "1.4.0" @@ -52,8 +52,8 @@ tokio-util = { version = "0.7.4", features = ["codec"] } url = { version = "2.2", features = ["serde"] } # internal -coconut-interface = { path = "../common/coconut-interface" } -credentials = { path = "../common/credentials" } +nym-coconut-interface = { path = "../common/coconut-interface" } +nym-credentials = { path = "../common/credentials" } nym-config = { path = "../common/config" } nym-crypto = { path = "../common/crypto" } nym-bin-common = { path = "../common/bin-common" } diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index 714f7f446d..3fce04a8ae 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -22,8 +22,8 @@ thiserror = "1.0" nym-crypto = { path = "../../common/crypto" } nym-pemstore = { path = "../../common/pemstore" } -coconut-interface = { path = "../../common/coconut-interface" } -credentials = { path = "../../common/credentials" } +nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-credentials = { path = "../../common/credentials" } [dependencies.tungstenite] version = "0.13.0" diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 4f2e2735d0..0685c0d473 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -5,7 +5,7 @@ use crate::authentication::encrypted_address::EncryptedAddressBytes; use crate::iv::IV; use crate::registration::handshake::SharedKeys; use crate::{GatewayMacSize, PROTOCOL_VERSION}; -use coconut_interface::Credential; +use nym_coconut_interface::Credential; use nym_crypto::generic_array::typenum::Unsigned; use nym_crypto::hmac::recompute_keyed_hmac_and_verify_tag; use nym_crypto::symmetric::stream_cipher; diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index d51cf111a1..99f1cf095b 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use coconut_interface::Credential; +use nym_coconut_interface::Credential; pub struct Bandwidth { value: u64, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index 8088f93b14..7ff47a9f11 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -62,10 +62,10 @@ pub(crate) enum RequestHandlingError { ProposalIdError { reason: String }, #[error("Coconut interface error - {0}")] - CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), + CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), #[error("Credential error - {0}")] - CredentialError(#[from] credentials::error::Error), + CredentialError(#[from] nym_credentials::error::Error), } impl RequestHandlingError { @@ -225,7 +225,7 @@ where } let aggregated_verification_key = - credentials::obtain_aggregate_verification_key(&credential_api_clients).await?; + nym_credentials::obtain_aggregate_verification_key(&credential_api_clients).await?; if !credential.verify(&aggregated_verification_key) { return Err(RequestHandlingError::InvalidBandwidthCredential( diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index aa07567b0a..8c50f93c36 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use coconut_interface::Credential; use log::*; +use nym_coconut_interface::Credential; use std::time::{Duration, SystemTime}; use validator_client::nyxd::traits::DkgQueryClient; use validator_client::{ diff --git a/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs b/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs index 53efa7ebb6..eafc2992cf 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; -use bandwidth_claim_contract::msg::ExecuteMsg; -use bandwidth_claim_contract::payment::LinkPaymentData; -use credentials::token::bandwidth::TokenCredential; +use nym_bandwidth_claim_contract::msg::ExecuteMsg; +use nym_bandwidth_claim_contract::payment::LinkPaymentData; +use nym_credentials::token::bandwidth::TokenCredential; use nym_crypto::asymmetric::identity::{PublicKey, Signature, SIGNATURE_LENGTH}; use nym_network_defaults::{ETH_EVENT_NAME, ETH_MIN_BLOCK_DEPTH}; use std::str::FromStr; diff --git a/integrations/bity/src/lib.rs b/integrations/bity/src/lib.rs index 232d686e3e..0523b9e6ec 100644 --- a/integrations/bity/src/lib.rs +++ b/integrations/bity/src/lib.rs @@ -12,9 +12,9 @@ mod tests { fn get_order(prefix: &str) -> anyhow::Result<(OrderSignature, String)> { let mnemonic = "crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove"; - let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse().unwrap())?; + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse()?); - let accounts = wallet.try_derive_accounts().unwrap(); + let accounts = wallet.try_derive_accounts()?; let message = "This is the order message from Bity"; let signature = sign::sign_order(&wallet, &accounts[0], message.to_string())?; diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 8ac9dd291c..77f21164cd 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.13" +version = "1.1.14" authors = [ "Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>", @@ -22,7 +22,6 @@ clap = { version = "4.0", features = ["cargo", "derive"] } colored = "2.0" cupid = "0.6.1" dirs = "4.0" -dotenv = "0.15.0" futures = "0.3.0" humantime-serde = "1.0" lazy_static = "1.4.0" diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index bbcf60b629..b1f76ffae6 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-api" -version = "1.1.13" +version = "1.1.14" authors = [ "Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>", @@ -22,7 +22,6 @@ cfg-if = "1.0" clap = { version = "4.0", features = ["cargo", "derive"] } console-subscriber = { version = "0.1.1", optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable" dirs = "4.0" -dotenv = "0.15.0" futures = "0.3.24" humantime-serde = "1.0" lazy_static = "1.4.0" @@ -53,7 +52,7 @@ ts-rs = {version = "6.1", optional = true} anyhow = "1.0" getset = "0.1.1" -sqlx = { version = "0.6.1", features = [ +sqlx = { version = "0.6.2", features = [ "runtime-tokio-rustls", "sqlite", "macros", @@ -65,23 +64,23 @@ rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] } schemars = { version = "0.8", features = ["preserve_order"] } ## internal -coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } -coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } -coconut-interface = { path = "../common/coconut-interface" } +nym-coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-coconut-interface = { path = "../common/coconut-interface" } nym-config = { path = "../common/config" } -cosmwasm-std = "1.0.0" -credential-storage = { path = "../common/credential-storage" } -credentials = { path = "../common/credentials" } +cosmwasm-std = { workspace = true } +nym-credential-storage = { path = "../common/credential-storage" } +nym-credentials = { path = "../common/credentials" } nym-crypto = { path = "../common/crypto" } -cw3 = { version = "0.13.4" } -cw4 = { version = "0.13.4" } -dkg = { path = "../common/dkg", features = ["cw-types"] } +cw3 = { workspace = true } +cw4 = { workspace = true } +nym-dkg = { path = "../common/dkg", features = ["cw-types"] } gateway-client = { path = "../common/client-libs/gateway-client" } nym-inclusion-probability = { path = "../common/inclusion-probability" } nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" } nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } -multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } +nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } nymcoconut = { path = "../common/nymcoconut" } nym-sphinx = { path = "../common/nymsphinx" } nym-pemstore = { path = "../common/pemstore" } @@ -107,5 +106,5 @@ sqlx = { version = "0.6.2", features = [ ] } [dev-dependencies] -cw3 = "0.13.4" -cw-utils = "0.13.4" +cw3 = { workspace = true } +cw-utils = { workspace = true } diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 8290327315..ed8dc1dce6 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -8,13 +8,13 @@ edition = "2021" [dependencies] bs58 = "0.4.0" cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } -cosmwasm-std = { version = "1.0.0", default-features = false } +cosmwasm-std = { workspace = true, default-features = false } getset = "0.1.1" schemars = { version = "0.8", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } ts-rs = { version = "6.1.2", optional = true } -coconut-interface = { path = "../../common/coconut-interface" } +nym-coconut-interface = { path = "../../common/coconut-interface" } nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } [features] diff --git a/nym-api/nym-api-requests/src/coconut.rs b/nym-api/nym-api-requests/src/coconut.rs index 18afdd81e9..6f79bc2f21 100644 --- a/nym-api/nym-api-requests/src/coconut.rs +++ b/nym-api/nym-api-requests/src/coconut.rs @@ -5,7 +5,7 @@ use cosmrs::AccountId; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; -use coconut_interface::{ +use nym_coconut_interface::{ error::CoconutInterfaceError, Attribute, Base58, BlindSignRequest, Credential, VerificationKey, }; diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index daebc0b0ac..319d49edd1 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -111,6 +111,7 @@ pub struct MixNodeBondAnnotated { pub estimated_operator_apy: Decimal, pub estimated_delegators_apy: Decimal, pub family: Option<FamilyHead>, + pub blacklisted: bool, } impl MixNodeBondAnnotated { @@ -137,6 +138,7 @@ pub struct GatewayBondAnnotated { // NOTE: the performance field is deprecated in favour of node_performance pub performance: Performance, pub node_performance: NodePerformance, + pub blacklisted: bool, } impl GatewayBondAnnotated { diff --git a/nym-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs index 0d81e52001..3ada819048 100644 --- a/nym-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::error::Result; -use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; -use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -use coconut_dkg_common::types::{ - EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, -}; -use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use cw3::ProposalResponse; use cw4::MemberResponse; -use dkg::Threshold; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; +use nym_coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; +use nym_coconut_dkg_common::types::{ + EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, +}; +use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use nym_contracts_common::dealings::ContractSafeBytes; +use nym_dkg::Threshold; use validator_client::nyxd::cosmwasm_client::types::ExecuteResult; use validator_client::nyxd::{AccountId, Fee, TxResponse}; diff --git a/nym-api/src/coconut/comm.rs b/nym-api/src/coconut/comm.rs index bca6bfda12..39fb970a02 100644 --- a/nym-api/src/coconut/comm.rs +++ b/nym-api/src/coconut/comm.rs @@ -3,9 +3,9 @@ use crate::coconut::error::Result; use crate::nyxd; -use coconut_dkg_common::types::EpochId; -use coconut_interface::VerificationKey; -use credentials::coconut::utils::obtain_aggregate_verification_key; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_interface::VerificationKey; +use nym_credentials::coconut::utils::obtain_aggregate_verification_key; use validator_client::CoconutApiClient; #[async_trait] diff --git a/nym-api/src/coconut/deposit.rs b/nym-api/src/coconut/deposit.rs index 356850aebf..4d3eab394f 100644 --- a/nym-api/src/coconut/deposit.rs +++ b/nym-api/src/coconut/deposit.rs @@ -1,12 +1,12 @@ // Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::events::{ +use nym_api_requests::coconut::BlindSignRequestBody; +use nym_coconut_bandwidth_contract_common::events::{ DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, DEPOSIT_VALUE, }; -use credentials::coconut::bandwidth::BandwidthVoucher; -use nym_api_requests::coconut::BlindSignRequestBody; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; use nym_crypto::asymmetric::encryption; use nym_crypto::asymmetric::identity::{self, Signature}; use validator_client::nyxd::TxResponse; diff --git a/nym-api/src/coconut/dkg/client.rs b/nym-api/src/coconut/dkg/client.rs index f84498a36f..8064ca5a7b 100644 --- a/nym-api/src/coconut/dkg/client.rs +++ b/nym-api/src/coconut/dkg/client.rs @@ -3,15 +3,15 @@ use crate::coconut::client::Client; use crate::coconut::error::CoconutError; -use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -use coconut_dkg_common::types::{ - EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, NodeIndex, -}; -use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use cw3::ProposalResponse; use cw4::MemberResponse; -use dkg::Threshold; +use nym_coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; +use nym_coconut_dkg_common::types::{ + EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, NodeIndex, +}; +use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use nym_contracts_common::dealings::ContractSafeBytes; +use nym_dkg::Threshold; use validator_client::nyxd::cosmwasm_client::logs::{find_attribute, NODE_INDEX}; use validator_client::nyxd::cosmwasm_client::types::ExecuteResult; use validator_client::nyxd::AccountId; diff --git a/nym-api/src/coconut/dkg/controller.rs b/nym-api/src/coconut/dkg/controller.rs index 6ae32cfad5..b4fc713741 100644 --- a/nym-api/src/coconut/dkg/controller.rs +++ b/nym-api/src/coconut/dkg/controller.rs @@ -14,18 +14,18 @@ use crate::coconut::keypair::KeyPair as CoconutKeyPair; use crate::nyxd; use crate::support::config::Config; use anyhow::Result; -use coconut_dkg_common::types::EpochState; -use dkg::bte::keys::KeyPair as DkgKeyPair; +use nym_coconut_dkg_common::types::EpochState; +use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use nym_task::{TaskClient, TaskManager}; use rand::rngs::OsRng; -use rand::RngCore; +use rand::{CryptoRng, RngCore}; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use tokio::time::interval; pub(crate) fn init_keypair(config: &Config) -> Result<()> { let mut rng = OsRng; - let dkg_params = dkg::bte::setup(); + let dkg_params = nym_dkg::bte::setup(); let kp = DkgKeyPair::new(&dkg_params, &mut rng); nym_pemstore::store_keypair( &kp, @@ -46,7 +46,7 @@ pub(crate) struct DkgController<R> { polling_rate: Duration, } -impl<R: RngCore + Clone> DkgController<R> { +impl<R: RngCore + CryptoRng + Clone> DkgController<R> { pub(crate) async fn new( config: &Config, nyxd_client: nyxd::Client, diff --git a/nym-api/src/coconut/dkg/dealing.rs b/nym-api/src/coconut/dkg/dealing.rs index f4e01b57cc..7c8424acf6 100644 --- a/nym-api/src/coconut/dkg/dealing.rs +++ b/nym-api/src/coconut/dkg/dealing.rs @@ -4,10 +4,10 @@ use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::state::{ConsistentState, State}; use crate::coconut::error::CoconutError; -use coconut_dkg_common::types::TOTAL_DEALINGS; -use dkg::bte::setup; -use dkg::Dealing; +use nym_coconut_dkg_common::types::TOTAL_DEALINGS; use nym_contracts_common::dealings::ContractSafeBytes; +use nym_dkg::bte::setup; +use nym_dkg::Dealing; use rand::RngCore; use std::collections::VecDeque; @@ -86,11 +86,11 @@ pub(crate) mod tests { use crate::coconut::dkg::state::PersistentState; use crate::coconut::tests::DummyClient; use crate::coconut::KeyPair; - use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::InitialReplacementData; use cosmwasm_std::Addr; - use dkg::bte::keys::KeyPair as DkgKeyPair; - use dkg::bte::{Params, PublicKeyWithProof}; + use nym_coconut_dkg_common::dealer::DealerDetails; + use nym_coconut_dkg_common::types::InitialReplacementData; + use nym_dkg::bte::keys::KeyPair as DkgKeyPair; + use nym_dkg::bte::{Params, PublicKeyWithProof}; use nymcoconut::{ttp_keygen, Parameters}; use rand::rngs::OsRng; use std::collections::HashMap; diff --git a/nym-api/src/coconut/dkg/public_key.rs b/nym-api/src/coconut/dkg/public_key.rs index ed27542baa..cf779cd8a1 100644 --- a/nym-api/src/coconut/dkg/public_key.rs +++ b/nym-api/src/coconut/dkg/public_key.rs @@ -4,7 +4,7 @@ use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::state::State; use crate::coconut::error::CoconutError; -use coconut_dkg_common::dealer::DealerType; +use nym_coconut_dkg_common::dealer::DealerType; pub(crate) async fn public_key_submission( dkg_client: &DkgClient, @@ -46,7 +46,7 @@ pub(crate) mod tests { use crate::coconut::dkg::state::PersistentState; use crate::coconut::tests::DummyClient; use crate::coconut::KeyPair; - use dkg::bte::keys::KeyPair as DkgKeyPair; + use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use rand::rngs::OsRng; use std::path::PathBuf; use std::str::FromStr; @@ -65,7 +65,7 @@ pub(crate) mod tests { PathBuf::default(), PersistentState::default(), Url::parse("localhost:8000").unwrap(), - DkgKeyPair::new(&dkg::bte::setup(), OsRng), + DkgKeyPair::new(&nym_dkg::bte::setup(), OsRng), KeyPair::new(), ); diff --git a/nym-api/src/coconut/dkg/state.rs b/nym-api/src/coconut/dkg/state.rs index 6277eb6ccf..6d37161572 100644 --- a/nym-api/src/coconut/dkg/state.rs +++ b/nym-api/src/coconut/dkg/state.rs @@ -4,11 +4,11 @@ use crate::coconut::dkg::complaints::ComplaintReason; use crate::coconut::error::CoconutError; use crate::coconut::keypair::KeyPair as CoconutKeyPair; -use coconut_dkg_common::dealer::DealerDetails; -use coconut_dkg_common::types::EpochState; use cosmwasm_std::Addr; -use dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; -use dkg::{NodeIndex, RecoveredVerificationKeys, Threshold}; +use nym_coconut_dkg_common::dealer::DealerDetails; +use nym_coconut_dkg_common::types::EpochState; +use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; +use nym_dkg::{NodeIndex, RecoveredVerificationKeys, Threshold}; use nymcoconut::SecretKey; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -337,7 +337,7 @@ impl State { pub async fn set_coconut_keypair( &mut self, - coconut_keypair: Option<coconut_interface::KeyPair>, + coconut_keypair: Option<nym_coconut_interface::KeyPair>, ) { self.coconut_keypair.set(coconut_keypair).await } diff --git a/nym-api/src/coconut/dkg/verification_key.rs b/nym-api/src/coconut/dkg/verification_key.rs index b7ef1e2773..402db402ee 100644 --- a/nym-api/src/coconut/dkg/verification_key.rs +++ b/nym-api/src/coconut/dkg/verification_key.rs @@ -6,16 +6,16 @@ use crate::coconut::dkg::complaints::ComplaintReason; use crate::coconut::dkg::state::{ConsistentState, State}; use crate::coconut::error::CoconutError; use crate::coconut::helpers::accepted_vote_err; -use coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID; -use coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS}; -use coconut_dkg_common::verification_key::owner_from_cosmos_msgs; -use coconut_interface::KeyPair as CoconutKeyPair; use cosmwasm_std::Addr; -use credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; use cw3::{ProposalResponse, Status}; -use dkg::bte::{decrypt_share, setup}; -use dkg::error::DkgError; -use dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold}; +use nym_coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID; +use nym_coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS}; +use nym_coconut_dkg_common::verification_key::owner_from_cosmos_msgs; +use nym_coconut_interface::KeyPair as CoconutKeyPair; +use nym_credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; +use nym_dkg::bte::{decrypt_share, setup}; +use nym_dkg::error::DkgError; +use nym_dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold}; use nym_pemstore::KeyPairPath; use nymcoconut::tests::helpers::transpose_matrix; use nymcoconut::{check_vk_pairing, Base58, KeyPair, Parameters, SecretKey, VerificationKey}; @@ -277,11 +277,11 @@ pub(crate) mod tests { use crate::coconut::dkg::state::PersistentState; use crate::coconut::tests::DummyClient; use crate::coconut::KeyPair; - use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::InitialReplacementData; - use coconut_dkg_common::verification_key::ContractVKShare; - use dkg::bte::keys::KeyPair as DkgKeyPair; + use nym_coconut_dkg_common::dealer::DealerDetails; + use nym_coconut_dkg_common::types::InitialReplacementData; + use nym_coconut_dkg_common::verification_key::ContractVKShare; use nym_contracts_common::dealings::ContractSafeBytes; + use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use nymcoconut::aggregate_verification_keys; use rand::rngs::OsRng; use rand::Rng; diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 20a3c919dc..387f4219f0 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -7,11 +7,11 @@ use rocket::{response, Request, Response}; use std::io::Cursor; use thiserror::Error; -use dkg::error::DkgError; use nym_crypto::asymmetric::{ encryption::KeyRecoveryError, identity::{Ed25519RecoveryError, SignatureError}, }; +use nym_dkg::error::DkgError; use validator_client::nyxd::error::NyxdError; use crate::node_status_api::models::NymApiStorageError; @@ -71,13 +71,13 @@ pub enum CoconutError { DifferentPublicAttributes(String, String), #[error("Error in coconut interface - {0}")] - CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), + CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), #[error("Storage error - {0}")] StorageError(#[from] NymApiStorageError), #[error("Credentials error - {0}")] - CredentialsError(#[from] credentials::error::Error), + CredentialsError(#[from] nym_credentials::error::Error), #[error("Incorrect credential proposal description: {reason}")] IncorrectProposal { reason: String }, diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs index 986bb6b305..5d2cda6a6d 100644 --- a/nym-api/src/coconut/helpers.rs +++ b/nym-api/src/coconut/helpers.rs @@ -8,7 +8,8 @@ use validator_client::nyxd::error::NyxdError::AbciError; // thus the transaction might fail pub(crate) fn accepted_vote_err(ret: Result<(), CoconutError>) -> Result<(), CoconutError> { if let Err(CoconutError::NyxdError(AbciError { ref log, .. })) = ret { - let accepted_err = multisig_contract_common::error::ContractError::NotOpen {}.to_string(); + let accepted_err = + nym_multisig_contract_common::error::ContractError::NotOpen {}.to_string(); // If redundant voting is not the case, error out on all other error variants if !log.value().contains(&accepted_err) { ret?; diff --git a/nym-api/src/coconut/keypair.rs b/nym-api/src/coconut/keypair.rs index e97ca82000..330a1a26de 100644 --- a/nym-api/src/coconut/keypair.rs +++ b/nym-api/src/coconut/keypair.rs @@ -6,7 +6,7 @@ use tokio::sync::{RwLock, RwLockReadGuard}; #[derive(Clone, Debug)] pub struct KeyPair { - inner: Arc<RwLock<Option<coconut_interface::KeyPair>>>, + inner: Arc<RwLock<Option<nym_coconut_interface::KeyPair>>>, } impl KeyPair { @@ -20,7 +20,7 @@ impl KeyPair { self.inner.read().await } - pub async fn set(&self, keypair: Option<coconut_interface::KeyPair>) { + pub async fn set(&self, keypair: Option<nym_coconut_interface::KeyPair>) { let mut w_lock = self.inner.write().await; *w_lock = keypair; } diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index 265da682b7..d28b734d8d 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -7,23 +7,23 @@ use crate::coconut::deposit::extract_encryption_key; use crate::coconut::error::{CoconutError, Result}; use crate::coconut::helpers::accepted_vote_err; use crate::support::storage::NymApiStorage; -use coconut_bandwidth_contract_common::spend_credential::{ - funds_from_cosmos_msgs, SpendCredentialStatus, -}; -use coconut_dkg_common::types::EpochId; -use coconut_interface::KeyPair as CoconutKeyPair; -use coconut_interface::{ - Attribute, BlindSignRequest, BlindedSignature, Parameters, VerificationKey, -}; -use credentials::coconut::params::{ - NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm, -}; use getset::{CopyGetters, Getters}; use keypair::KeyPair; use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; +use nym_coconut_bandwidth_contract_common::spend_credential::{ + funds_from_cosmos_msgs, SpendCredentialStatus, +}; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_interface::KeyPair as CoconutKeyPair; +use nym_coconut_interface::{ + Attribute, BlindSignRequest, BlindedSignature, Parameters, VerificationKey, +}; use nym_config::defaults::NYM_API_VERSION; +use nym_credentials::coconut::params::{ + NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm, +}; use nym_crypto::asymmetric::encryption; use nym_crypto::shared_key::new_ephemeral_shared_key; use nym_crypto::symmetric::stream_cipher; @@ -190,7 +190,7 @@ impl InternalSignRequest { fn blind_sign(request: InternalSignRequest, key_pair: &CoconutKeyPair) -> Result<BlindedSignature> { let params = Parameters::new(request.total_params())?; - Ok(coconut_interface::blind_sign( + Ok(nym_coconut_interface::blind_sign( ¶ms, &key_pair.secret_key(), request.blind_sign_request(), diff --git a/nym-api/src/coconut/tests.rs b/nym-api/src/coconut/tests.rs index dd83eefc9e..065c7ac5a5 100644 --- a/nym-api/src/coconut/tests.rs +++ b/nym-api/src/coconut/tests.rs @@ -3,23 +3,23 @@ use super::InternalSignRequest; use crate::coconut::error::{CoconutError, Result}; -use coconut_bandwidth_contract_common::events::{ - DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, - DEPOSIT_VALUE, -}; -use coconut_bandwidth_contract_common::spend_credential::{ - SpendCredential, SpendCredentialResponse, -}; -use coconut_interface::{hash_to_scalar, Credential, VerificationKey}; use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; -use credentials::coconut::bandwidth::BandwidthVoucher; -use credentials::coconut::params::{ - NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm, -}; use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; +use nym_coconut_bandwidth_contract_common::events::{ + DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, + DEPOSIT_VALUE, +}; +use nym_coconut_bandwidth_contract_common::spend_credential::{ + SpendCredential, SpendCredentialResponse, +}; +use nym_coconut_interface::{hash_to_scalar, Credential, VerificationKey}; use nym_config::defaults::VOUCHER_INFO; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::params::{ + NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm, +}; use nym_crypto::shared_key::recompute_shared_key; use nym_crypto::symmetric::stream_cipher; use nymcoconut::tests::helpers::theta_from_keys_and_attributes; @@ -35,19 +35,19 @@ use validator_client::nyxd::{tx::Hash, AccountId, DeliverTx, Event, Fee, Tag, Tx use crate::coconut::State; use crate::support::storage::NymApiStorage; use async_trait::async_trait; -use coconut_dkg_common::dealer::{ - ContractDealing, DealerDetails, DealerDetailsResponse, DealerType, -}; -use coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX}; -use coconut_dkg_common::types::{ - EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, TOTAL_DEALINGS, -}; -use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use cw3::ProposalResponse; use cw4::MemberResponse; -use dkg::Threshold; +use nym_coconut_dkg_common::dealer::{ + ContractDealing, DealerDetails, DealerDetailsResponse, DealerType, +}; +use nym_coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX}; +use nym_coconut_dkg_common::types::{ + EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, TOTAL_DEALINGS, +}; +use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use nym_contracts_common::dealings::ContractSafeBytes; use nym_crypto::asymmetric::{encryption, identity}; +use nym_dkg::Threshold; use rand_07::rngs::OsRng; use rand_07::Rng; use rocket::http::Status; @@ -399,10 +399,11 @@ impl super::client::Client for DummyClient { }, ); let proposal_id = OsRng.gen(); - let verify_vk_share_req = coconut_dkg_common::msg::ExecuteMsg::VerifyVerificationKeyShare { - owner: Addr::unchecked(self.validator_address.as_ref()), - resharing, - }; + let verify_vk_share_req = + nym_coconut_dkg_common::msg::ExecuteMsg::VerifyVerificationKeyShare { + owner: Addr::unchecked(self.validator_address.as_ref()), + resharing, + }; let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: String::new(), msg: to_binary(&verify_vk_share_req).unwrap(), @@ -954,7 +955,7 @@ async fn verification_of_bandwidth_credential() { // Test the endpoint without any credential recorded in the Coconut Bandwidth Contract let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); - let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -1050,7 +1051,7 @@ async fn verification_of_bandwidth_credential() { // Test the endpoint with a proposal that has a different value for the funds to be released // then what's in the credential let funds = Coin::new((voucher_value + 10) as u128, TEST_COIN_DENOM); - let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -1088,7 +1089,7 @@ async fn verification_of_bandwidth_credential() { // Test the endpoint with every dependency met let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); - let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { diff --git a/nym-api/src/epoch_operations/error.rs b/nym-api/src/epoch_operations/error.rs index 6ebc8b5802..a57e0153fc 100644 --- a/nym-api/src/epoch_operations/error.rs +++ b/nym-api/src/epoch_operations/error.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node_status_api::models::NymApiStorageError; +use nym_mixnet_contract_common::EpochState; use thiserror::Error; use validator_client::nyxd::error::NyxdError; use validator_client::nyxd::AccountId; @@ -15,6 +16,12 @@ pub enum RewardingError { allowed_address: AccountId, }, + #[error("the current epoch is in the wrong state ({current_state}) to perform the requested operation: {operation}")] + InvalidEpochState { + current_state: EpochState, + operation: String, + }, + // #[error("There were no mixnodes to reward (network is dead)")] // NoMixnodesToReward, #[error("Failed to execute the smart contract - {0}")] diff --git a/nym-api/src/epoch_operations/event_reconciliation.rs b/nym-api/src/epoch_operations/event_reconciliation.rs new file mode 100644 index 0000000000..0279f96b21 --- /dev/null +++ b/nym-api/src/epoch_operations/event_reconciliation.rs @@ -0,0 +1,70 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_operations::error::RewardingError; +use crate::RewardedSetUpdater; +use nym_mixnet_contract_common::EpochState; +use std::cmp::max; + +impl RewardedSetUpdater { + pub(super) async fn reconcile_epoch_events(&self) -> Result<(), RewardingError> { + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + match epoch_status.state { + state @ EpochState::InProgress | state @ EpochState::Rewarding { .. } => { + // hard error, this shouldn't have happened! + error!("tried to perform node rewarding while in {state} state!"); + Err(RewardingError::InvalidEpochState { + current_state: state, + operation: "reconciling epoch events".to_string(), + }) + } + EpochState::AdvancingEpoch => { + warn!("we seem to have crashed mid epoch operations... no need to reconcile events as we've already done that!"); + Ok(()) + } + EpochState::ReconcilingEvents => { + log::info!("Reconciling all pending epoch events..."); + if let Err(err) = self._reconcile_epoch_events().await { + log::error!("FAILED to reconcile epoch events... - {err}"); + Err(err) + } else { + log::info!("Reconciled all pending epoch events... SUCCESS"); + Ok(()) + } + } + } + } + + async fn _reconcile_epoch_events(&self) -> Result<(), RewardingError> { + let pending_events = self.nyxd_client.get_pending_events_count().await?; + + // be conservative about number of events we resolve at once. + // contract should be able to handle few hundred at once + // but keep it on the lower side. + let limit = 100; + + let mut required_calls = pending_events / limit; + if pending_events % limit != 0 { + required_calls += 1; + } + + // make sure to at least call it once so that if there are no pending events, + // the epoch would get advanced into the next phase + for _ in 0..max(required_calls, 1) { + self.nyxd_client.reconcile_epoch_events(Some(limit)).await?; + } + + // in the incredibly unlikely/borderline impossible scenario a HUGE number of events got pushed + // while we were reconciling the events, do it one more time + // + // note: it's perfectly fine if we don't clear EXACTLY everything, + // since when we execute transaction to actually advance the epoch, + // it will resolve all remaining events. + let pending_events = self.nyxd_client.get_pending_events_count().await?; + if pending_events > 20 { + self.nyxd_client.reconcile_epoch_events(Some(limit)).await?; + } + + Ok(()) + } +} diff --git a/nym-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs index cdeddf2691..89daaebb4d 100644 --- a/nym-api/src/epoch_operations/mod.rs +++ b/nym-api/src/epoch_operations/mod.rs @@ -12,44 +12,24 @@ // 3. Eventually this whole procedure is going to get expanded to allow for distribution of rewarded set generation // and hence this might be a good place for it. -use crate::epoch_operations::helpers::stake_to_f64; use crate::node_status_api::ONE_DAY; use crate::nym_contract_cache::cache::NymContractCache; use crate::support::nyxd::Client; -use crate::support::storage::models::RewardingReport; use crate::support::storage::NymApiStorage; use error::RewardingError; -use nym_mixnet_contract_common::families::FamilyHead; -use nym_mixnet_contract_common::{ - reward_params::Performance, CurrentIntervalResponse, ExecuteMsg, Interval, MixId, -}; -use nym_mixnet_contract_common::{IdentityKey, Layer, LayerAssignment, MixNodeDetails}; +use nym_mixnet_contract_common::{CurrentIntervalResponse, Interval}; use nym_task::{TaskClient, TaskManager}; -use rand::prelude::SliceRandom; -use rand::rngs::OsRng; -use std::collections::HashMap; +pub(crate) use rewarding::MixnodeToReward; use std::collections::HashSet; use std::time::Duration; use tokio::time::sleep; pub(crate) mod error; +mod event_reconciliation; mod helpers; - -#[derive(Debug, Clone, Copy)] -pub(crate) struct MixnodeToReward { - pub(crate) mix_id: MixId, - - pub(crate) performance: Performance, -} - -impl From<MixnodeToReward> for ExecuteMsg { - fn from(mix_reward: MixnodeToReward) -> Self { - ExecuteMsg::RewardMixnode { - mix_id: mix_reward.mix_id, - performance: mix_reward.performance, - } - } -} +mod rewarded_set_assignment; +mod rewarding; +mod transition_beginning; pub struct RewardedSetUpdater { nyxd_client: Client, @@ -57,16 +37,6 @@ pub struct RewardedSetUpdater { storage: NymApiStorage, } -// Weight of a layer being chose is reciprocal to current count in layer -fn layer_weight(l: &Layer, layer_assignments: &HashMap<Layer, f32>) -> f32 { - let total = layer_assignments.values().fold(0., |acc, i| acc + i); - if total == 0. { - 1. - } else { - 1. - (layer_assignments.get(l).unwrap_or(&0.) / total) - } -} - impl RewardedSetUpdater { pub(crate) async fn current_interval_details( &self, @@ -86,176 +56,34 @@ impl RewardedSetUpdater { } } - async fn determine_layers( - &self, - rewarded_set: &[MixNodeDetails], - ) -> Result<(Vec<LayerAssignment>, HashMap<String, Layer>), RewardingError> { - let mut families_in_layer: HashMap<String, Layer> = HashMap::new(); - let mut assignments = vec![]; - let mut layer_assignments: HashMap<Layer, f32> = HashMap::new(); - let mut rng = OsRng; - let layers = vec![Layer::One, Layer::Two, Layer::Three]; - - let mix_to_family = self.nym_contract_cache.mix_to_family().await.to_vec(); - - let mix_to_family = mix_to_family - .into_iter() - .collect::<HashMap<IdentityKey, FamilyHead>>(); - - for mix in rewarded_set { - let family = mix_to_family.get(&mix.bond_information.identity().to_owned()); - // Get layer already assigned to nodes family, if any - let family_layer = family.and_then(|h| families_in_layer.get(h.identity())); - - // Same node families are always assigned to the same layer, otherwise layer selected by a random weighted choice - let layer = if let Some(layer) = family_layer { - layer.to_owned() - } else { - layers - .choose_weighted(&mut rng, |l| layer_weight(l, &layer_assignments))? - .to_owned() - }; - - assignments.push(LayerAssignment::new(mix.mix_id(), layer)); - - // layer accounting - let layer_entry = layer_assignments.entry(layer).or_insert(0.); - *layer_entry += 1.; - if let Some(family) = family { - families_in_layer.insert(family.identity().to_string(), layer); - } - } - - Ok((assignments, families_in_layer)) - } - - fn determine_rewarded_set( - &self, - mixnodes: &[MixNodeDetails], - nodes_to_select: u32, - ) -> Result<Vec<MixNodeDetails>, RewardingError> { - if mixnodes.is_empty() { - return Ok(Vec::new()); - } - - let mut rng = OsRng; - - // generate list of mixnodes and their relatively weight (by total stake) - let choices = mixnodes - .iter() - .map(|mix| { - let total_stake = stake_to_f64(mix.total_stake()); - (mix.to_owned(), total_stake) - }) - .collect::<Vec<_>>(); - - // the unwrap here is fine as an error can only be thrown under one of the following conditions: - // - our mixnode list is empty - we have already checked for that - // - we have invalid weights, i.e. less than zero or NaNs - it shouldn't happen in our case as we safely cast down from u128 - // - all weights are zero - it's impossible in our case as the list of nodes is not empty and weight is proportional to stake. You must have non-zero stake in order to bond - // - we have more than u32::MAX values (which is incredibly unrealistic to have 4B mixnodes bonded... literally every other person on the planet would need one) - Ok(choices - .choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1)? - .map(|(mix, _weight)| mix.to_owned()) - .collect()) - } - - async fn reward_current_rewarded_set( - &self, - current_interval: Interval, - ) -> Result<(), RewardingError> { - let to_reward = self.nodes_to_reward(current_interval).await; - - if let Some(existing_report) = self - .storage - .get_rewarding_report(current_interval.current_epoch_absolute_id()) - .await? - { - warn!("We have already rewarded mixnodes for this rewarding epoch ({}). {} nodes should have gotten rewards", existing_report.absolute_epoch_id, existing_report.eligible_mixnodes); - return Ok(()); - } - - if to_reward.is_empty() { - info!("There are no nodes to reward in this epoch"); - } else if let Err(err) = self.nyxd_client.send_rewarding_messages(&to_reward).await { - error!( - "failed to perform mixnode rewarding for epoch {}! Error encountered: {err}", - current_interval.current_epoch_absolute_id(), - ); - return Err(err.into()); - } - - log::info!("rewarded {} mixnodes...", to_reward.len()); - - let rewarding_report = RewardingReport { - absolute_epoch_id: current_interval.current_epoch_absolute_id(), - eligible_mixnodes: to_reward.len() as u32, - }; - - self.storage - .insert_rewarding_report(rewarding_report) - .await?; - - Ok(()) - } - - async fn nodes_to_reward(&self, interval: Interval) -> Vec<MixnodeToReward> { - // try to get current up to date view of the network bypassing the cache - // in case the epochs were significantly shortened for the purposes of testing - let rewarded_set: Vec<MixId> = match self.nyxd_client.get_rewarded_set_mixnodes().await { - Ok(nodes) => nodes.into_iter().map(|(id, _)| id).collect::<Vec<_>>(), - Err(err) => { - warn!("failed to obtain the current rewarded set - {err}. falling back to the cached version"); - self.nym_contract_cache - .rewarded_set() - .await - .into_inner() - .into_iter() - .map(|node| node.mix_id()) - .collect::<Vec<_>>() - } - }; - - let mut eligible_nodes = Vec::with_capacity(rewarded_set.len()); - for mix_id in rewarded_set { - let uptime = self - .storage - .get_average_mixnode_uptime_in_the_last_24hrs( - mix_id, - interval.current_epoch_end_unix_timestamp(), - ) - .await - .unwrap_or_default(); - eligible_nodes.push(MixnodeToReward { - mix_id, - performance: uptime.into(), - }) - } - - eligible_nodes - } - - async fn update_rewarded_set_and_advance_epoch( - &self, - all_mixnodes: &[MixNodeDetails], - ) -> Result<(), RewardingError> { - // we grab rewarding parameters here as they might have gotten updated when performing epoch actions - let rewarding_parameters = self.nyxd_client.get_current_rewarding_parameters().await?; - - let new_rewarded_set = - self.determine_rewarded_set(all_mixnodes, rewarding_parameters.rewarded_set_size)?; - - let (layer_assignments, _families_in_layer) = - self.determine_layers(&new_rewarded_set).await?; - - self.nyxd_client - .advance_current_epoch(layer_assignments, rewarding_parameters.active_set_size) - .await?; - - Ok(()) - } - // This is where the epoch gets advanced, and all epoch related transactions originate + /// Upon each epoch having finished the following actions are executed by this nym-api: + /// 1. it queries the mixnet contract to check the current `EpochState` in order to figure out whether + /// a different nym-api has already started epoch transition (not yet applicable) + /// 2. it sends a `BeginEpochTransition` message to the mixnet contract causing the following to happen: + /// - if successful, the address of the this validator is going to be saved as being responsible for progressing this epoch. + /// What it means in practice is that once we have multiple instances of nym-api running, + /// only this one will try to perform the rest of the actions. It will also allow it to + /// more easily recover in case of crashes. + /// - the `EpochState` changes to `Rewarding`, meaning the nym-api will now be allowed to send + /// `RewardMixnode` transactions. However, it's not going to be able anything else like `ReconcileEpochEvents` + /// until that is done. + /// - ability to send transactions (by other users) that get resolved once given epoch/interval rolls over, + /// such as `BondMixnode` or `DelegateToMixnode` will temporarily be frozen until the entire procedure is finished. + /// 3. it obtains the current rewarded set and for each node in there (**SORTED BY MIX_ID!!**), + /// it sends (in a single batch) `RewardMixnode` message with the measured performance. + /// Once the final message gets executed, the mixnet contract automatically transitions + /// the state to `ReconcilingEvents`. + /// 4. it obtains the number of pending epoch and interval events and repeatedly sends + /// `ReconcileEpochEvents` transaction until all of them are resolved. + /// At this point the mixnet contract automatically transitions the state to `AdvancingEpoch`. + /// 5. it obtains the list of all nodes on the network and pseudorandomly (but weighted by total stake) + /// determines the new rewarded set. It then assigns layers to the provided nodes taking + /// family information into consideration. Finally it sends `AdvanceCurrentEpoch` message + /// containing the set and layer information thus rolling over the epoch and changing the state + /// to `InProgress`. + /// 6. it purges old (older than 48h) measurement data + /// 7. the whole process repeats once the new epoch finishes async fn perform_epoch_operations(&self, interval: Interval) -> Result<(), RewardingError> { log::info!("The current epoch has finished."); log::info!( @@ -274,48 +102,39 @@ impl RewardedSetUpdater { let epoch_end = interval.current_epoch_end(); - let all_mixnodes = self.nym_contract_cache.mixnodes().await; + let all_mixnodes = self.nym_contract_cache.mixnodes_filtered().await; if all_mixnodes.is_empty() { + // that's a bit weird, but log::warn!("there don't seem to be any mixnodes on the network!") } + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + if !epoch_status.is_in_progress() { + if epoch_status.being_advanced_by.as_str() + != self.nyxd_client.client_address().await.as_ref() + { + // another nym-api is already handling + error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by); + return Ok(()); + } else { + warn!("we seem to have crashed mid-epoch advancement..."); + } + } else { + let should_continue = self.begin_epoch_transition().await?; + if !should_continue { + return Ok(()); + } + } + // Reward all the nodes in the still current, soon to be previous rewarded set - // log::info!("Rewarding the current rewarded set..."); - // if let Err(err) = self.reward_current_rewarded_set(interval).await { - // log::error!("FAILED to reward rewarded set - {err}"); - // // since we haven't advanced the epoch yet, we will attempt to reward those nodes again - // // next time we enter this function (i.e. within 2min or so) - // // - // // TODO: deal with the following edge case: - // // - the nym api REWARDS all mixnodes - // // - then crashes before advancing epoch - // // - upon restart it will attempt (and fail) to re-reward the mixnodes - // return Err(err); - // } else { - // log::info!("Rewarded current rewarded set... SUCCESS"); - // } + //log::info!("Rewarding the current rewarded set..."); + //self.reward_current_rewarded_set(interval).await?; // note: those operations don't really have to be atomic, so it's fine to send them // as separate transactions - - log::info!("Reconciling all pending epoch events..."); - if let Err(err) = self.nyxd_client.reconcile_epoch_events().await { - log::error!("FAILED to reconcile epoch events... - {err}"); - return Err(err.into()); - } else { - log::info!("Reconciled all pending epoch events... SUCCESS"); - } - - log::info!("Advancing epoch and updating the rewarded set..."); - if let Err(err) = self - .update_rewarded_set_and_advance_epoch(&all_mixnodes) - .await - { - log::error!("FAILED to advance the current epoch... - {err}"); - return Err(err); - } else { - log::info!("Advanced the epoch and updated the rewarded set... SUCCESS"); - } + self.reconcile_epoch_events().await?; + self.update_rewarded_set_and_advance_epoch(&all_mixnodes) + .await?; log::info!("Purging old node statuses from the storage..."); let cutoff = (epoch_end - 2 * ONE_DAY).unix_timestamp(); diff --git a/nym-api/src/epoch_operations/rewarded_set_assignment.rs b/nym-api/src/epoch_operations/rewarded_set_assignment.rs new file mode 100644 index 0000000000..adb7508867 --- /dev/null +++ b/nym-api/src/epoch_operations/rewarded_set_assignment.rs @@ -0,0 +1,196 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_operations::error::RewardingError; +use crate::epoch_operations::helpers::stake_to_f64; +use crate::RewardedSetUpdater; +use nym_mixnet_contract_common::families::FamilyHead; +use nym_mixnet_contract_common::{EpochState, IdentityKey, Layer, LayerAssignment, MixNodeDetails}; +use rand::prelude::SliceRandom; +use rand::rngs::OsRng; +use std::collections::HashMap; + +impl RewardedSetUpdater { + // Needs to run for active and reserve sets separatley, as it does not preserve order + async fn determine_layers( + &self, + set: &[MixNodeDetails], + ) -> Result<Vec<LayerAssignment>, RewardingError> { + let mut assignments = Vec::with_capacity(set.len()); + let target_layer_count = set.len() / 3; + + let mix_to_family = self.nym_contract_cache.mix_to_family().await.to_vec(); + + let mix_to_family = mix_to_family + .into_iter() + .collect::<HashMap<IdentityKey, FamilyHead>>(); + + let mut regular_nodes = Vec::with_capacity(set.len()); + + let mut families = HashMap::new(); + + for node in set.iter() { + if let Some(fh) = mix_to_family.get(node.bond_information.identity()) { + let family: &mut Vec<u32> = families.entry(fh.identity()).or_default(); + family.push(node.mix_id()) + } else { + regular_nodes.push(node.mix_id()) + } + } + + let mut layers = HashMap::new(); + layers.insert(Layer::One, Vec::with_capacity(target_layer_count)); + layers.insert(Layer::Two, Vec::with_capacity(target_layer_count)); + layers.insert(Layer::Three, Vec::with_capacity(target_layer_count)); + + // Assign all members of a family to same layer + for (_head, members) in families.iter_mut() { + let smallest_layer = layers + .iter() + .min_by_key(|(_layer, members)| members.len()) + .map(|(layer, _members)| *layer) + .unwrap_or(Layer::One); + + let entry = layers.entry(smallest_layer).or_default(); + if entry.len() + members.len() <= target_layer_count { + entry.extend_from_slice(members) + } + } + + // Assign nodes with no families into layers + for mix_id in regular_nodes.drain(..) { + let smallest_layer = layers + .iter() + .min_by_key(|(_layer, members)| members.len()) + .map(|(layer, _members)| *layer) + .unwrap_or(Layer::One); + + let entry = layers.entry(smallest_layer).or_default(); + if entry.len() < target_layer_count { + entry.push(mix_id) + } + } + + for (layer, members) in layers { + let layer_assignments = members + .into_iter() + .map(|mix_id| LayerAssignment::new(mix_id, layer)); + assignments.extend(layer_assignments); + } + Ok(assignments) + } + + fn determine_rewarded_set( + &self, + mixnodes: &[MixNodeDetails], + nodes_to_select: u32, + ) -> Result<Vec<MixNodeDetails>, RewardingError> { + if mixnodes.is_empty() { + return Ok(Vec::new()); + } + + let mut rng = OsRng; + + // generate list of mixnodes and their relatively weight (by total stake) + let choices = mixnodes + .iter() + .map(|mix| { + let total_stake = stake_to_f64(mix.total_stake()); + (mix.to_owned(), total_stake) + }) + .collect::<Vec<_>>(); + + // the unwrap here is fine as an error can only be thrown under one of the following conditions: + // - our mixnode list is empty - we have already checked for that + // - we have invalid weights, i.e. less than zero or NaNs - it shouldn't happen in our case as we safely cast down from u128 + // - all weights are zero - it's impossible in our case as the list of nodes is not empty and weight is proportional to stake. You must have non-zero stake in order to bond + // - we have more than u32::MAX values (which is incredibly unrealistic to have 4B mixnodes bonded... literally every other person on the planet would need one) + Ok(choices + .choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1)? + .map(|(mix, _weight)| mix.to_owned()) + .collect()) + } + + pub(super) async fn update_rewarded_set_and_advance_epoch( + &self, + all_mixnodes: &[MixNodeDetails], + ) -> Result<(), RewardingError> { + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + match epoch_status.state { + EpochState::AdvancingEpoch => { + log::info!("Advancing epoch and updating the rewarded set..."); + if let Err(err) = self + ._update_rewarded_set_and_advance_epoch(all_mixnodes) + .await + { + log::error!("FAILED to advance the current epoch... - {err}"); + Err(err) + } else { + log::info!("Advanced the epoch and updated the rewarded set... SUCCESS"); + Ok(()) + } + } + state => { + // hard error, this shouldn't have happened! + error!("tried to perform node rewarded set assignment while in {state} state!"); + Err(RewardingError::InvalidEpochState { + current_state: state, + operation: "assigning rewarded set".to_string(), + }) + } + } + } + + async fn _update_rewarded_set_and_advance_epoch( + &self, + all_mixnodes: &[MixNodeDetails], + ) -> Result<(), RewardingError> { + // we grab rewarding parameters here as they might have gotten updated when performing epoch actions + let rewarding_parameters = self.nyxd_client.get_current_rewarding_parameters().await?; + + debug!("Rewarding paremeters: {:?}", rewarding_parameters); + + let new_rewarded_set = + self.determine_rewarded_set(all_mixnodes, rewarding_parameters.rewarded_set_size)?; + + debug!("New rewarded set: {:?}", new_rewarded_set); + + let empty = vec![]; + + let (active_set, reserve_set) = if new_rewarded_set.len() + <= rewarding_parameters.active_set_size as usize + { + warn!("Active set size ({}) is greater then rewarded set len ({}), there will be no reserve set", rewarding_parameters.active_set_size, new_rewarded_set.len()); + (new_rewarded_set.as_slice(), empty.as_slice()) + } else { + new_rewarded_set.split_at(rewarding_parameters.active_set_size as usize) + }; + + let mut active_set_layer_assignments = self.determine_layers(active_set).await?; + debug!( + "Active set layer assignments: {:?}", + active_set_layer_assignments + ); + let reserve_set_layer_assignments = self.determine_layers(reserve_set).await?; + debug!( + "Reserve set layer assignments: {:?}", + reserve_set_layer_assignments + ); + + active_set_layer_assignments.extend(reserve_set_layer_assignments); + + debug!( + "Rewarded set layer assignments: {:?}", + active_set_layer_assignments + ); + + self.nyxd_client + .advance_current_epoch( + active_set_layer_assignments, + rewarding_parameters.active_set_size, + ) + .await?; + + Ok(()) + } +} diff --git a/nym-api/src/epoch_operations/rewarding.rs b/nym-api/src/epoch_operations/rewarding.rs new file mode 100644 index 0000000000..1630d369d7 --- /dev/null +++ b/nym-api/src/epoch_operations/rewarding.rs @@ -0,0 +1,133 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_operations::error::RewardingError; +use crate::support::storage::models::RewardingReport; +use crate::RewardedSetUpdater; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::{EpochState, ExecuteMsg, Interval, MixId}; + +#[derive(Debug, Clone, Copy)] +pub(crate) struct MixnodeToReward { + pub(crate) mix_id: MixId, + + pub(crate) performance: Performance, +} + +impl From<MixnodeToReward> for ExecuteMsg { + fn from(mix_reward: MixnodeToReward) -> Self { + ExecuteMsg::RewardMixnode { + mix_id: mix_reward.mix_id, + performance: mix_reward.performance, + } + } +} + +impl RewardedSetUpdater { + pub(super) async fn reward_current_rewarded_set( + &self, + current_interval: Interval, + ) -> Result<(), RewardingError> { + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + match epoch_status.state { + EpochState::InProgress => { + // hard error, this shouldn't have happened! + error!("tried to perform node rewarding while the epoch is still in progress!"); + Err(RewardingError::InvalidEpochState { + current_state: EpochState::InProgress, + operation: "mix rewarding".to_string(), + }) + } + EpochState::ReconcilingEvents | EpochState::AdvancingEpoch => { + warn!("we seem to have crashed mid epoch operations... no need to reward mixnodes as we've already done that! (or this could be a false positive if there were no nodes to reward - to fix this warning later)"); + Ok(()) + } + EpochState::Rewarding { .. } => { + log::info!("Rewarding the current rewarded set..."); + if let Err(err) = self._reward_current_rewarded_set(current_interval).await { + log::error!("FAILED to reward rewarded set - {err}"); + Err(err) + } else { + log::info!("Rewarded current rewarded set... SUCCESS"); + Ok(()) + } + } + } + } + + async fn _reward_current_rewarded_set( + &self, + current_interval: Interval, + ) -> Result<(), RewardingError> { + let mut to_reward = self.nodes_to_reward(current_interval).await; + to_reward.sort_by_key(|a| a.mix_id); + + if let Some(existing_report) = self + .storage + .get_rewarding_report(current_interval.current_epoch_absolute_id()) + .await? + { + warn!("We have already rewarded mixnodes for this rewarding epoch ({}). {} nodes should have gotten rewards", existing_report.absolute_epoch_id, existing_report.eligible_mixnodes); + return Ok(()); + } + + if to_reward.is_empty() { + info!("There are no nodes to reward in this epoch"); + } else if let Err(err) = self.nyxd_client.send_rewarding_messages(&to_reward).await { + error!( + "failed to perform mixnode rewarding for epoch {}! Error encountered: {err}", + current_interval.current_epoch_absolute_id(), + ); + return Err(err.into()); + } + + log::info!("rewarded {} mixnodes...", to_reward.len()); + + let rewarding_report = RewardingReport { + absolute_epoch_id: current_interval.current_epoch_absolute_id(), + eligible_mixnodes: to_reward.len() as u32, + }; + + self.storage + .insert_rewarding_report(rewarding_report) + .await?; + + Ok(()) + } + + async fn nodes_to_reward(&self, interval: Interval) -> Vec<MixnodeToReward> { + // try to get current up to date view of the network bypassing the cache + // in case the epochs were significantly shortened for the purposes of testing + let rewarded_set: Vec<MixId> = match self.nyxd_client.get_rewarded_set_mixnodes().await { + Ok(nodes) => nodes.into_iter().map(|(id, _)| id).collect::<Vec<_>>(), + Err(err) => { + warn!("failed to obtain the current rewarded set - {err}. falling back to the cached version"); + self.nym_contract_cache + .rewarded_set() + .await + .into_inner() + .into_iter() + .map(|node| node.mix_id()) + .collect::<Vec<_>>() + } + }; + + let mut eligible_nodes = Vec::with_capacity(rewarded_set.len()); + for mix_id in rewarded_set { + let uptime = self + .storage + .get_average_mixnode_uptime_in_the_last_24hrs( + mix_id, + interval.current_epoch_end_unix_timestamp(), + ) + .await + .unwrap_or_default(); + eligible_nodes.push(MixnodeToReward { + mix_id, + performance: uptime.into(), + }) + } + + eligible_nodes + } +} diff --git a/nym-api/src/epoch_operations/transition_beginning.rs b/nym-api/src/epoch_operations/transition_beginning.rs new file mode 100644 index 0000000000..4df4ed7dfe --- /dev/null +++ b/nym-api/src/epoch_operations/transition_beginning.rs @@ -0,0 +1,31 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_operations::error::RewardingError; +use crate::epoch_operations::RewardedSetUpdater; + +impl RewardedSetUpdater { + // returns boolean indicating whether we should bother continuing + pub(super) async fn begin_epoch_transition(&self) -> Result<bool, RewardingError> { + info!("starting the epoch transition..."); + if let Err(err) = self._begin_epoch_transition().await { + // perform the state query again to make sure it's not because other nym-api (not yet applicable) + // wasn't faster than us + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + if !epoch_status.is_in_progress() { + log::error!("FAILED to begin epoch progression: {err}"); + Err(err) + } else { + error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by); + Ok(false) + } + } else { + log::info!("Begun epoch transition... SUCCESS"); + Ok(true) + } + } + + async fn _begin_epoch_transition(&self) -> Result<(), RewardingError> { + Ok(self.nyxd_client.begin_epoch_transition().await?) + } +} diff --git a/nym-api/src/network_monitor/mod.rs b/nym-api/src/network_monitor/mod.rs index 4e32c61f7b..25b6494da5 100644 --- a/nym-api/src/network_monitor/mod.rs +++ b/nym-api/src/network_monitor/mod.rs @@ -16,9 +16,9 @@ use crate::nym_contract_cache::cache::NymContractCache; use crate::storage::NymApiStorage; use crate::support::config::Config; use crate::support::nyxd; -use credential_storage::PersistentStorage; use futures::channel::mpsc; use gateway_client::bandwidth::BandwidthController; +use nym_credential_storage::PersistentStorage; use nym_crypto::asymmetric::{encryption, identity}; use nym_task::TaskManager; use std::sync::Arc; @@ -97,8 +97,10 @@ impl<'a> NetworkMonitorBuilder<'a> { let bandwidth_controller = { let client = self._nyxd_client.0.read().await; BandwidthController::new( - credential_storage::initialise_storage(self.config.get_credentials_database_path()) - .await, + nym_credential_storage::initialise_storage( + self.config.get_credentials_database_path(), + ) + .await, client.clone(), ) }; diff --git a/nym-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs index 1a58db1cae..cf536a6322 100644 --- a/nym-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -6,7 +6,6 @@ use crate::network_monitor::monitor::gateway_clients_cache::{ }; use crate::network_monitor::monitor::gateways_pinger::GatewayPinger; use crate::network_monitor::monitor::receiver::{GatewayClientUpdate, GatewayClientUpdateSender}; -use credential_storage::PersistentStorage; use futures::channel::mpsc; use futures::stream::{self, FuturesUnordered, StreamExt}; use futures::task::Context; @@ -15,6 +14,7 @@ use gateway_client::error::GatewayClientError; use gateway_client::{AcknowledgementReceiver, GatewayClient, MixnetMessageReceiver}; use log::{debug, info, trace, warn}; use nym_config::defaults::REMAINING_BANDWIDTH_THRESHOLD; +use nym_credential_storage::PersistentStorage; use nym_crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH}; use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; diff --git a/nym-api/src/node_status_api/cache/mod.rs b/nym-api/src/node_status_api/cache/mod.rs index d996fbb69b..ba74ab561c 100644 --- a/nym-api/src/node_status_api/cache/mod.rs +++ b/nym-api/src/node_status_api/cache/mod.rs @@ -89,10 +89,15 @@ impl NodeStatusCache { } } - pub(crate) async fn mixnodes_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { + pub(crate) async fn mixnodes_annotated_full(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { self.get(|c| c.mixnodes_annotated.clone()).await } + pub(crate) async fn mixnodes_annotated_filtered(&self) -> Option<Vec<MixNodeBondAnnotated>> { + let full = self.mixnodes_annotated_full().await?; + Some(full.value.into_iter().filter(|m| !m.blacklisted).collect()) + } + pub(crate) async fn rewarded_set_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { self.get(|c| c.rewarded_set_annotated.clone()).await } @@ -101,10 +106,15 @@ impl NodeStatusCache { self.get(|c| c.active_set_annotated.clone()).await } - pub(crate) async fn gateways_annotated(&self) -> Option<Cache<Vec<GatewayBondAnnotated>>> { + pub(crate) async fn gateways_annotated_full(&self) -> Option<Cache<Vec<GatewayBondAnnotated>>> { self.get(|c| c.gateways_annotated.clone()).await } + pub(crate) async fn gateways_annotated_filtered(&self) -> Option<Vec<GatewayBondAnnotated>> { + let full = self.gateways_annotated_full().await?; + Some(full.value.into_iter().filter(|m| !m.blacklisted).collect()) + } + pub(crate) async fn inclusion_probabilities(&self) -> Option<Cache<InclusionProbabilities>> { self.get(|c| c.inclusion_probabilities.clone()).await } @@ -126,7 +136,7 @@ impl NodeStatusCache { return (Some(bond.clone()), MixnodeStatus::Standby); } - let all_bonded = &self.mixnodes_annotated().await.unwrap().into_inner(); + let all_bonded = &self.mixnodes_annotated_filtered().await.unwrap(); if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) { (Some(bond.clone()), MixnodeStatus::Inactive) } else { diff --git a/nym-api/src/node_status_api/cache/node_sets.rs b/nym-api/src/node_status_api/cache/node_sets.rs index 2d856bf9e7..a80c84d2d1 100644 --- a/nym-api/src/node_status_api/cache/node_sets.rs +++ b/nym-api/src/node_status_api/cache/node_sets.rs @@ -6,7 +6,7 @@ use nym_mixnet_contract_common::{reward_params::Performance, Interval, MixId}; use nym_mixnet_contract_common::{ GatewayBond, IdentityKey, MixNodeDetails, RewardedSetNodeStatus, RewardingParams, }; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; pub(super) fn to_rewarded_set_node_status( rewarded_set: &[MixNodeDetails], @@ -84,6 +84,7 @@ pub(super) async fn annotate_nodes_with_details( current_interval: Interval, rewarded_set: &HashMap<MixId, RewardedSetNodeStatus>, mix_to_family: Vec<(IdentityKey, FamilyHead)>, + blacklist: &HashSet<MixId>, ) -> Vec<MixNodeBondAnnotated> { let mix_to_family = mix_to_family .into_iter() @@ -135,6 +136,7 @@ pub(super) async fn annotate_nodes_with_details( .cloned(); annotated.push(MixNodeBondAnnotated { + blacklisted: blacklist.contains(&mixnode.mix_id()), mixnode_details: mixnode, stake_saturation, uncapped_stake_saturation, @@ -152,6 +154,7 @@ pub(crate) async fn annotate_gateways_with_details( storage: &Option<NymApiStorage>, gateway_bonds: Vec<GatewayBond>, current_interval: Interval, + blacklist: &HashSet<IdentityKey>, ) -> Vec<GatewayBondAnnotated> { let mut annotated = Vec::new(); for gateway_bond in gateway_bonds { @@ -175,6 +178,7 @@ pub(crate) async fn annotate_gateways_with_details( .unwrap_or_default(); annotated.push(GatewayBondAnnotated { + blacklisted: blacklist.contains(&gateway_bond.gateway.identity_key), gateway_bond, performance, node_performance, diff --git a/nym-api/src/node_status_api/cache/refresher.rs b/nym-api/src/node_status_api/cache/refresher.rs index 146e75ebfb..7e84ea2ee8 100644 --- a/nym-api/src/node_status_api/cache/refresher.rs +++ b/nym-api/src/node_status_api/cache/refresher.rs @@ -109,13 +109,17 @@ impl NodeStatusCacheRefresher { log::info!("Updating node status cache"); // Fetch contract cache data to work with - let mixnode_details = self.contract_cache.mixnodes().await; + let mixnode_details = self.contract_cache.mixnodes_all().await; let interval_reward_params = self.contract_cache.interval_reward_params().await; let current_interval = self.contract_cache.current_interval().await; let rewarded_set = self.contract_cache.rewarded_set().await; let active_set = self.contract_cache.active_set().await; let mix_to_family = self.contract_cache.mix_to_family().await; - let gateway_bonds = self.contract_cache.gateways().await; + let gateway_bonds = self.contract_cache.gateways_all().await; + + // get blacklists + let mixnodes_blacklist = self.contract_cache.mixnodes_blacklist().await; + let gateways_blacklist = self.contract_cache.gateways_blacklist().await; let interval_reward_params = interval_reward_params.ok_or(NodeStatusCacheError::SourceDataMissing)?; @@ -140,6 +144,7 @@ impl NodeStatusCacheRefresher { current_interval, &rewarded_set_node_status, mix_to_family.to_vec(), + &mixnodes_blacklist, ) .await; @@ -147,8 +152,13 @@ impl NodeStatusCacheRefresher { let (rewarded_set, active_set) = split_into_active_and_rewarded_set(&mixnodes_annotated, &rewarded_set_node_status); - let gateways_annotated = - annotate_gateways_with_details(&self.storage, gateway_bonds, current_interval).await; + let gateways_annotated = annotate_gateways_with_details( + &self.storage, + gateway_bonds, + current_interval, + &gateways_blacklist, + ) + .await; // Update the cache self.cache diff --git a/nym-api/src/node_status_api/helpers.rs b/nym-api/src/node_status_api/helpers.rs index d131c6088e..6a67910810 100644 --- a/nym-api/src/node_status_api/helpers.rs +++ b/nym-api/src/node_status_api/helpers.rs @@ -24,17 +24,19 @@ async fn get_gateway_bond_annotated( cache: &NodeStatusCache, identity: &str, ) -> Result<GatewayBondAnnotated, ErrorResponse> { - let gateways = cache.gateways_annotated().await.ok_or(ErrorResponse::new( - "no data available", - Status::ServiceUnavailable, - ))?; + let gateways = cache + .gateways_annotated_filtered() + .await + .ok_or(ErrorResponse::new( + "no data available", + Status::ServiceUnavailable, + ))?; gateways - .into_inner() .into_iter() .find(|gateway| gateway.identity() == identity) .ok_or(ErrorResponse::new( - "mixnode bond not found", + "gateway bond not found", Status::NotFound, )) } @@ -43,13 +45,15 @@ async fn get_mixnode_bond_annotated( cache: &NodeStatusCache, mix_id: MixId, ) -> Result<MixNodeBondAnnotated, ErrorResponse> { - let mixnodes = cache.mixnodes_annotated().await.ok_or(ErrorResponse::new( - "no data available", - Status::ServiceUnavailable, - ))?; + let mixnodes = cache + .mixnodes_annotated_filtered() + .await + .ok_or(ErrorResponse::new( + "no data available", + Status::ServiceUnavailable, + ))?; mixnodes - .into_inner() .into_iter() .find(|mixnode| mixnode.mix_id() == mix_id) .ok_or(ErrorResponse::new( @@ -374,7 +378,16 @@ pub(crate) async fn _get_mixnode_inclusion_probabilities( pub(crate) async fn _get_mixnodes_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> { cache - .mixnodes_annotated() + .mixnodes_annotated_filtered() + .await + .unwrap_or_default() +} + +pub(crate) async fn _get_mixnodes_detailed_unfiltered( + cache: &NodeStatusCache, +) -> Vec<MixNodeBondAnnotated> { + cache + .mixnodes_annotated_full() .await .unwrap_or_default() .into_inner() @@ -400,7 +413,16 @@ pub(crate) async fn _get_active_set_detailed(cache: &NodeStatusCache) -> Vec<Mix pub(crate) async fn _get_gateways_detailed(cache: &NodeStatusCache) -> Vec<GatewayBondAnnotated> { cache - .gateways_annotated() + .gateways_annotated_filtered() + .await + .unwrap_or_default() +} + +pub(crate) async fn _get_gateways_detailed_unfiltered( + cache: &NodeStatusCache, +) -> Vec<GatewayBondAnnotated> { + cache + .gateways_annotated_full() .await .unwrap_or_default() .into_inner() diff --git a/nym-api/src/node_status_api/mod.rs b/nym-api/src/node_status_api/mod.rs index 1b05827e97..6906e8aab3 100644 --- a/nym-api/src/node_status_api/mod.rs +++ b/nym-api/src/node_status_api/mod.rs @@ -48,9 +48,11 @@ pub(crate) fn node_status_routes( routes::get_gateway_avg_uptime, routes::get_mixnode_inclusion_probabilities, routes::get_mixnodes_detailed, + routes::get_mixnodes_detailed_unfiltered, routes::get_rewarded_set_detailed, routes::get_active_set_detailed, routes::get_gateways_detailed, + routes::get_gateways_detailed_unfiltered, ] } else { // in the minimal variant we would not have access to endpoints relying on existence diff --git a/nym-api/src/node_status_api/routes.rs b/nym-api/src/node_status_api/routes.rs index 9d1f9d7dc0..b014abef06 100644 --- a/nym-api/src/node_status_api/routes.rs +++ b/nym-api/src/node_status_api/routes.rs @@ -6,11 +6,11 @@ use super::NodeStatusCache; use crate::node_status_api::helpers::{ _compute_mixnode_reward_estimation, _gateway_core_status_count, _gateway_report, _gateway_uptime_history, _get_active_set_detailed, _get_gateway_avg_uptime, - _get_mixnode_avg_uptime, _get_mixnode_inclusion_probabilities, - _get_mixnode_inclusion_probability, _get_mixnode_reward_estimation, - _get_mixnode_stake_saturation, _get_mixnode_status, _get_mixnodes_detailed, - _get_rewarded_set_detailed, _mixnode_core_status_count, _mixnode_report, - _mixnode_uptime_history, + _get_gateways_detailed_unfiltered, _get_mixnode_avg_uptime, + _get_mixnode_inclusion_probabilities, _get_mixnode_inclusion_probability, + _get_mixnode_reward_estimation, _get_mixnode_stake_saturation, _get_mixnode_status, + _get_mixnodes_detailed, _get_mixnodes_detailed_unfiltered, _get_rewarded_set_detailed, + _mixnode_core_status_count, _mixnode_report, _mixnode_uptime_history, }; use crate::node_status_api::models::ErrorResponse; use crate::storage::NymApiStorage; @@ -188,6 +188,14 @@ pub async fn get_mixnodes_detailed( Json(_get_mixnodes_detailed(cache).await) } +#[openapi(tag = "status")] +#[get("/mixnodes/detailed-unfiltered")] +pub async fn get_mixnodes_detailed_unfiltered( + cache: &State<NodeStatusCache>, +) -> Json<Vec<MixNodeBondAnnotated>> { + Json(_get_mixnodes_detailed_unfiltered(cache).await) +} + #[openapi(tag = "status")] #[get("/mixnodes/rewarded/detailed")] pub async fn get_rewarded_set_detailed( @@ -211,3 +219,11 @@ pub async fn get_gateways_detailed( ) -> Json<Vec<GatewayBondAnnotated>> { Json(_get_gateways_detailed(cache).await) } + +#[openapi(tag = "status")] +#[get("/gateways/detailed-unfiltered")] +pub async fn get_gateways_detailed_unfiltered( + cache: &State<NodeStatusCache>, +) -> Json<Vec<GatewayBondAnnotated>> { + Json(_get_gateways_detailed_unfiltered(cache).await) +} diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index 1ee787af17..cd885f7a5d 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -67,50 +67,48 @@ impl NymContractCache { } } - pub async fn mixnodes_blacklist(&self) -> Option<Cache<HashSet<MixId>>> { + pub async fn mixnodes_blacklist(&self) -> Cache<HashSet<MixId>> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => Some(cache.mixnodes_blacklist.clone()), + Ok(cache) => cache.mixnodes_blacklist.clone(), Err(err) => { error!("{err}"); - None + Cache::new(HashSet::new()) } } } - pub async fn gateways_blacklist(&self) -> Option<Cache<HashSet<IdentityKey>>> { + pub async fn gateways_blacklist(&self) -> Cache<HashSet<IdentityKey>> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => Some(cache.gateways_blacklist.clone()), + Ok(cache) => cache.gateways_blacklist.clone(), Err(err) => { error!("{err}"); - None + Cache::new(HashSet::new()) } } } pub async fn update_mixnodes_blacklist(&self, add: HashSet<MixId>, remove: HashSet<MixId>) { let blacklist = self.mixnodes_blacklist().await; - if let Some(blacklist) = blacklist { - let mut blacklist = blacklist - .value - .union(&add) - .cloned() - .collect::<HashSet<MixId>>(); - let to_remove = blacklist - .intersection(&remove) - .cloned() - .collect::<HashSet<MixId>>(); - for key in to_remove { - blacklist.remove(&key); + let mut blacklist = blacklist + .value + .union(&add) + .cloned() + .collect::<HashSet<MixId>>(); + let to_remove = blacklist + .intersection(&remove) + .cloned() + .collect::<HashSet<MixId>>(); + for key in to_remove { + blacklist.remove(&key); + } + match time::timeout(Duration::from_millis(100), self.inner.write()).await { + Ok(mut cache) => { + cache.mixnodes_blacklist.update(blacklist); } - match time::timeout(Duration::from_millis(100), self.inner.write()).await { - Ok(mut cache) => { - cache.mixnodes_blacklist.update(blacklist); - return; - } - Err(err) => error!("{err}"), + Err(err) => { + error!("Failed to update mixnodes blacklist: {err}"); } } - error!("Failed to update mixnodes blacklist"); } pub async fn update_gateways_blacklist( @@ -119,49 +117,52 @@ impl NymContractCache { remove: HashSet<IdentityKey>, ) { let blacklist = self.gateways_blacklist().await; - if let Some(blacklist) = blacklist { - let mut blacklist = blacklist - .value - .union(&add) - .cloned() - .collect::<HashSet<IdentityKey>>(); - let to_remove = blacklist - .intersection(&remove) - .cloned() - .collect::<HashSet<IdentityKey>>(); - for key in to_remove { - blacklist.remove(&key); + let mut blacklist = blacklist + .value + .union(&add) + .cloned() + .collect::<HashSet<IdentityKey>>(); + let to_remove = blacklist + .intersection(&remove) + .cloned() + .collect::<HashSet<IdentityKey>>(); + for key in to_remove { + blacklist.remove(&key); + } + match time::timeout(Duration::from_millis(100), self.inner.write()).await { + Ok(mut cache) => { + cache.gateways_blacklist.update(blacklist); } - match time::timeout(Duration::from_millis(100), self.inner.write()).await { - Ok(mut cache) => { - cache.gateways_blacklist.update(blacklist); - return; - } - Err(err) => error!("{err}"), + Err(err) => { + error!("Failed to update gateways blacklist: {err}"); } } - error!("Failed to update gateways blacklist"); } - pub async fn mixnodes(&self) -> Vec<MixNodeDetails> { + pub async fn mixnodes_filtered(&self) -> Vec<MixNodeDetails> { + let mixnodes = self.mixnodes_all().await; + if mixnodes.is_empty() { + return Vec::new(); + } let blacklist = self.mixnodes_blacklist().await; - let mixnodes = match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => cache.mixnodes.clone(), - Err(err) => { - error!("{err}"); - return Vec::new(); - } - }; - if let Some(blacklist) = blacklist { + if !blacklist.is_empty() { mixnodes - .value - .iter() + .into_iter() .filter(|mix| !blacklist.value.contains(&mix.mix_id())) - .cloned() .collect() } else { - mixnodes.value + mixnodes + } + } + + pub async fn mixnodes_all(&self) -> Vec<MixNodeDetails> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.mixnodes.clone().value, + Err(err) => { + error!("{err}"); + Vec::new() + } } } @@ -181,25 +182,21 @@ impl NymContractCache { } } - pub async fn gateways(&self) -> Vec<GatewayBond> { - let blacklist = self.gateways_blacklist().await; - let gateways = match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => cache.gateways.clone(), - Err(err) => { - error!("{err}"); - return Vec::new(); - } - }; + pub async fn gateways_filtered(&self) -> Vec<GatewayBond> { + let gateways = self.gateways_all().await; + if gateways.is_empty() { + return Vec::new(); + } - if let Some(blacklist) = blacklist { + let blacklist = self.gateways_blacklist().await; + + if !blacklist.is_empty() { gateways - .value - .iter() + .into_iter() .filter(|mix| !blacklist.value.contains(mix.identity())) - .cloned() .collect() } else { - gateways.value + gateways } } @@ -277,7 +274,7 @@ impl NymContractCache { return (Some(bond.clone()), MixnodeStatus::Standby); } - let all_bonded = &self.mixnodes().await; + let all_bonded = &self.mixnodes_filtered().await; if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) { (Some(bond.clone()), MixnodeStatus::Inactive) } else { diff --git a/nym-api/src/nym_contract_cache/routes.rs b/nym-api/src/nym_contract_cache/routes.rs index 31268f62f8..bdf268ddfc 100644 --- a/nym-api/src/nym_contract_cache/routes.rs +++ b/nym-api/src/nym_contract_cache/routes.rs @@ -20,7 +20,7 @@ use std::collections::HashSet; #[openapi(tag = "contract-cache")] #[get("/mixnodes")] pub async fn get_mixnodes(cache: &State<NymContractCache>) -> Json<Vec<MixNodeDetails>> { - Json(cache.mixnodes().await) + Json(cache.mixnodes_filtered().await) } // DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated, @@ -41,7 +41,7 @@ pub async fn get_mixnodes_detailed( #[openapi(tag = "contract-cache")] #[get("/gateways")] pub async fn get_gateways(cache: &State<NymContractCache>) -> Json<Vec<GatewayBond>> { - Json(cache.gateways().await) + Json(cache.gateways_filtered().await) } #[openapi(tag = "contract-cache")] @@ -68,7 +68,7 @@ pub async fn get_rewarded_set_detailed( #[openapi(tag = "contract-cache")] #[get("/mixnodes/active")] pub async fn get_active_set(cache: &State<NymContractCache>) -> Json<Vec<MixNodeDetails>> { - Json(cache.mixnodes().await) + Json(cache.mixnodes_filtered().await) } // DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated, @@ -91,7 +91,12 @@ pub async fn get_active_set_detailed( pub async fn get_blacklisted_mixnodes( cache: &State<NymContractCache>, ) -> Json<Option<HashSet<MixId>>> { - Json(cache.mixnodes_blacklist().await.map(|c| c.value)) + let blacklist = cache.mixnodes_blacklist().await.value; + if blacklist.is_empty() { + Json(None) + } else { + Json(Some(blacklist)) + } } #[openapi(tag = "contract-cache")] @@ -99,7 +104,12 @@ pub async fn get_blacklisted_mixnodes( pub async fn get_blacklisted_gateways( cache: &State<NymContractCache>, ) -> Json<Option<HashSet<String>>> { - Json(cache.gateways_blacklist().await.map(|c| c.value)) + let blacklist = cache.gateways_blacklist().await.value; + if blacklist.is_empty() { + Json(None) + } else { + Json(Some(blacklist)) + } } #[openapi(tag = "contract-cache")] diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 7d624cc03e..0686e5c9ef 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -6,23 +6,23 @@ use crate::epoch_operations::MixnodeToReward; use crate::support::config::Config; use anyhow::Result; use async_trait::async_trait; -use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; -use coconut_dkg_common::types::InitialReplacementData; -use coconut_dkg_common::{ +use cw3::ProposalResponse; +use cw4::MemberResponse; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; +use nym_coconut_dkg_common::types::InitialReplacementData; +use nym_coconut_dkg_common::{ dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}, types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId}, verification_key::{ContractVKShare, VerificationKeyShare}, }; -use cw3::ProposalResponse; -use cw4::MemberResponse; use nym_config::defaults::{ChainDetails, NymNetworkDetails, DEFAULT_NYM_API_PORT}; use nym_contracts_common::dealings::ContractSafeBytes; use nym_mixnet_contract_common::families::{Family, FamilyHead}; use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::reward_params::RewardingParams; use nym_mixnet_contract_common::{ - CurrentIntervalResponse, ExecuteMsg, GatewayBond, IdentityKey, LayerAssignment, MixId, - RewardedSetNodeStatus, + CurrentIntervalResponse, EpochStatus, ExecuteMsg, GatewayBond, IdentityKey, LayerAssignment, + MixId, RewardedSetNodeStatus, }; use nym_vesting_contract_common::AccountVestingCoins; use std::sync::Arc; @@ -157,6 +157,12 @@ impl Client { Ok(self.0.read().await.get_current_interval_details().await?) } + pub(crate) async fn get_current_epoch_status( + &self, + ) -> Result<EpochStatus, ValidatorClientError> { + Ok(self.0.read().await.nyxd.get_current_epoch_status().await?) + } + pub(crate) async fn get_current_rewarding_parameters( &self, ) -> Result<RewardingParams, ValidatorClientError> { @@ -213,6 +219,21 @@ impl Client { self.0.read().await.get_all_family_members().await } + pub(crate) async fn get_pending_events_count(&self) -> Result<u32, ValidatorClientError> { + let pending = self.0.read().await.get_number_of_pending_events().await?; + Ok(pending.epoch_events + pending.interval_events) + } + + pub(crate) async fn begin_epoch_transition(&self) -> Result<(), ValidatorClientError> { + self.0 + .write() + .await + .nyxd + .begin_epoch_transition(None) + .await?; + Ok(()) + } + pub(crate) async fn send_rewarding_messages( &self, nodes: &[MixnodeToReward], @@ -268,12 +289,15 @@ impl Client { Ok(()) } - pub(crate) async fn reconcile_epoch_events(&self) -> Result<(), ValidatorClientError> { + pub(crate) async fn reconcile_epoch_events( + &self, + limit: Option<u32>, + ) -> Result<(), ValidatorClientError> { self.0 .write() .await .nyxd - .reconcile_epoch_events(None, None) + .reconcile_epoch_events(limit, None) .await?; Ok(()) } @@ -329,7 +353,7 @@ impl crate::coconut::client::Client for Client { async fn get_current_epoch_threshold( &self, - ) -> crate::coconut::error::Result<Option<dkg::Threshold>> { + ) -> crate::coconut::error::Result<Option<nym_dkg::Threshold>> { Ok(self .0 .read() diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 7e78b5fbb8..c7e509b38d 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -21,7 +21,6 @@ tauri-macros = "^1.2.1" [dependencies] anyhow = "1.0" bip39 = "1.0" -chrono = "0.4" dirs = "4.0" eyre = "0.6.5" fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"} @@ -40,10 +39,12 @@ tap = "1.0.1" tauri = { version = "^1.2.2", features = ["clipboard-write-text", "macos-private-api", "notification-all", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] } tendermint-rpc = "0.23.0" thiserror = "1.0" +time = { version = "0.3.17", features = ["local-offset"] } tokio = { version = "1.24.1", features = ["sync", "time"] } url = "2.2" yaml-rust = "0.4" + client-core = { path = "../../../clients/client-core" } nym-api-requests = { path = "../../../nym-api/nym-api-requests" } nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common"} diff --git a/nym-connect/desktop/src-tauri/src/logging.rs b/nym-connect/desktop/src-tauri/src/logging.rs index 804c1f2dae..093452d67c 100644 --- a/nym-connect/desktop/src-tauri/src/logging.rs +++ b/nym-connect/desktop/src-tauri/src/logging.rs @@ -1,9 +1,26 @@ -use std::str::FromStr; - use fern::colors::{Color, ColoredLevelConfig}; use serde::Serialize; use serde_repr::{Deserialize_repr, Serialize_repr}; +use std::str::FromStr; use tauri::Manager; +use time::{format_description, OffsetDateTime}; + +fn formatted_time() -> String { + // if we fail to obtain local time according to local offset, fallback to utc + let _now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); + + // if we're running it in the unit test, use the hardcoded value + #[cfg(test)] + let _now = OffsetDateTime::from_unix_timestamp(1666666666).unwrap(); + + // the unwraps are fine as we know this description is correct + // note: the reason for this very particular format is a very simple one + // it's what we've always been using since we copied it from the example, + // so feel free to update it to whatever + let format = + format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]").unwrap(); + _now.format(&format).unwrap() +} pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> { let colors = ColoredLevelConfig::new() @@ -21,7 +38,7 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE .format(move |out, message, record| { out.finish(format_args!( "{}[{}][{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + formatted_time(), record.target(), colors.color(record.level()), message, @@ -33,7 +50,7 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE .format(move |out, message, record| { out.finish(format_args!( "{}[{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + formatted_time(), record.target(), message, )) @@ -116,3 +133,15 @@ impl From<log::Level> for LogLevel { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn log_formatting() { + let expected_chrono_formated = "[2022-10-25][02:57:46]".to_string(); + let new_time_based = formatted_time(); + assert_eq!(new_time_based, expected_chrono_formated) + } +} diff --git a/nym-connect/mobile/src-tauri/Cargo.lock b/nym-connect/mobile/src-tauri/Cargo.lock index a11deefe2c..696e24bcb6 100644 --- a/nym-connect/mobile/src-tauri/Cargo.lock +++ b/nym-connect/mobile/src-tauri/Cargo.lock @@ -612,7 +612,7 @@ dependencies = [ [[package]] name = "client-core" -version = "1.1.11" +version = "1.1.12" dependencies = [ "async-trait", "dashmap", @@ -689,39 +689,6 @@ dependencies = [ "objc", ] -[[package]] -name = "coconut-bandwidth-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "multisig-contract-common", - "nym-contracts-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58", - "getset", - "nymcoconut", - "serde", - "thiserror", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -948,30 +915,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "credential-storage" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "sqlx 0.5.13", - "thiserror", - "tokio", -] - -[[package]] -name = "credentials" -version = "0.1.0" -dependencies = [ - "bls12_381 0.5.0", - "coconut-interface", - "cosmrs", - "nym-api-requests", - "nym-crypto", - "thiserror", - "validator-client", -] - [[package]] name = "crossbeam-channel" version = "0.5.6" @@ -1408,27 +1351,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" -[[package]] -name = "dkg" -version = "0.1.0" -dependencies = [ - "bitvec", - "bls12_381 0.6.0", - "bs58", - "ff 0.11.1", - "group 0.11.0", - "lazy_static", - "nym-pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - [[package]] name = "dotenv" version = "0.15.0" @@ -1901,14 +1823,14 @@ name = "gateway-client" version = "0.1.0" dependencies = [ "async-trait", - "coconut-interface", - "credential-storage", - "credentials", "futures", "gateway-requests", "getrandom 0.2.8", "log", "mobile-storage", + "nym-coconut-interface", + "nym-credential-storage", + "nym-credentials", "nym-crypto", "nym-network-defaults", "nym-pemstore", @@ -1934,10 +1856,10 @@ name = "gateway-requests" version = "0.1.0" dependencies = [ "bs58", - "coconut-interface", - "credentials", "futures", "log", + "nym-coconut-interface", + "nym-credentials", "nym-crypto", "nym-pemstore", "nym-sphinx", @@ -2249,15 +2171,6 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "group-contract-common" -version = "0.1.0" -dependencies = [ - "cw4", - "schemars", - "serde", -] - [[package]] name = "gtk" version = "0.16.2" @@ -3244,20 +3157,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw3-fixed-multisig", - "cw4", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -3435,10 +3334,10 @@ name = "nym-api-requests" version = "0.1.0" dependencies = [ "bs58", - "coconut-interface", "cosmrs", "cosmwasm-std", "getset", + "nym-coconut-interface", "nym-mixnet-contract-common", "schemars", "serde", @@ -3446,7 +3345,7 @@ dependencies = [ [[package]] name = "nym-bin-common" -version = "0.1.0" +version = "0.2.0" dependencies = [ "clap", "clap_complete", @@ -3458,6 +3357,39 @@ dependencies = [ "vergen", ] +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-interface" +version = "0.1.0" +dependencies = [ + "bs58", + "getset", + "nymcoconut", + "serde", + "thiserror", +] + [[package]] name = "nym-config" version = "0.1.0" @@ -3514,7 +3446,7 @@ dependencies = [ [[package]] name = "nym-contracts-common" -version = "0.1.0" +version = "0.2.0" dependencies = [ "bs58", "cosmwasm-std", @@ -3524,8 +3456,32 @@ dependencies = [ ] [[package]] -name = "nym-crypto" +name = "nym-credential-storage" version = "0.1.0" +dependencies = [ + "async-trait", + "log", + "sqlx 0.5.13", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-credentials" +version = "0.1.0" +dependencies = [ + "bls12_381 0.5.0", + "cosmrs", + "nym-api-requests", + "nym-coconut-interface", + "nym-crypto", + "thiserror", + "validator-client", +] + +[[package]] +name = "nym-crypto" +version = "0.2.0" dependencies = [ "aes 0.8.2", "blake3", @@ -3545,6 +3501,27 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "nym-dkg" +version = "0.1.0" +dependencies = [ + "bitvec", + "bls12_381 0.6.0", + "bs58", + "ff 0.11.1", + "group 0.11.0", + "lazy_static", + "nym-pemstore", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + [[package]] name = "nym-execute" version = "0.1.0" @@ -3554,8 +3531,17 @@ dependencies = [ ] [[package]] -name = "nym-mixnet-contract-common" +name = "nym-group-contract-common" version = "0.1.0" +dependencies = [ + "cw4", + "schemars", + "serde", +] + +[[package]] +name = "nym-mixnet-contract-common" +version = "0.2.0" dependencies = [ "bs58", "cosmwasm-std", @@ -3570,12 +3556,26 @@ dependencies = [ "time 0.3.19", ] +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw3-fixed-multisig", + "cw4", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "nym-network-defaults" version = "0.1.0" dependencies = [ "cfg-if", - "dotenv", + "dotenvy", "hex-literal", "once_cell", "serde", @@ -3596,20 +3596,39 @@ dependencies = [ ] [[package]] -name = "nym-pemstore" +name = "nym-ordered-buffer" version = "0.1.0" +dependencies = [ + "log", + "thiserror", +] + +[[package]] +name = "nym-pemstore" +version = "0.2.0" dependencies = [ "pem", ] +[[package]] +name = "nym-service-providers-common" +version = "0.1.0" +dependencies = [ + "async-trait", + "log", + "nym-bin-common", + "nym-sphinx-anonymous-replies", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "nym-socks5-client" -version = "1.1.11" +version = "1.1.12" dependencies = [ "clap", "client-core", - "coconut-interface", - "credentials", "dirs", "futures", "gateway-client", @@ -3618,22 +3637,24 @@ dependencies = [ "log", "mobile-storage", "nym-bin-common", + "nym-coconut-interface", "nym-config", + "nym-credentials", "nym-crypto", "nym-network-defaults", + "nym-ordered-buffer", "nym-pemstore", + "nym-service-providers-common", + "nym-socks5-proxy-helpers", + "nym-socks5-requests", "nym-sphinx", "nym-task", "nym-topology", - "ordered-buffer", "pin-project", "pretty_env_logger", - "proxy-helpers", "rand 0.7.3", "serde", "serde_json", - "service-providers-common", - "socks5-requests", "tap", "thiserror", "tokio", @@ -3641,6 +3662,31 @@ dependencies = [ "validator-client", ] +[[package]] +name = "nym-socks5-proxy-helpers" +version = "0.1.0" +dependencies = [ + "bytes", + "futures", + "log", + "nym-ordered-buffer", + "nym-socks5-requests", + "nym-task", + "tokio", + "tokio-util", +] + +[[package]] +name = "nym-socks5-requests" +version = "0.1.0" +dependencies = [ + "nym-service-providers-common", + "nym-sphinx-addressing", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "nym-sphinx" version = "0.1.0" @@ -3761,7 +3807,7 @@ dependencies = [ [[package]] name = "nym-sphinx-types" -version = "0.1.0" +version = "0.2.0" dependencies = [ "sphinx-packet", ] @@ -3796,14 +3842,16 @@ dependencies = [ [[package]] name = "nym-vesting-contract" -version = "1.1.4" +version = "1.2.0-pre.1" dependencies = [ "cosmwasm-std", "cw-storage-plus", + "cw2", "nym-contracts-common", "nym-mixnet-contract-common", "nym-vesting-contract-common", "schemars", + "semver 1.0.16", "serde", "thiserror", "vergen", @@ -3811,7 +3859,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" -version = "0.1.0" +version = "0.2.0" dependencies = [ "cosmwasm-std", "nym-contracts-common", @@ -3827,11 +3875,11 @@ dependencies = [ "bls12_381 0.6.0", "bs58", "digest 0.9.0", - "dkg", "ff 0.11.1", "getrandom 0.2.8", "group 0.11.0", "itertools", + "nym-dkg", "nym-pemstore", "rand 0.8.5", "serde", @@ -3962,14 +4010,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ordered-buffer" -version = "0.1.0" -dependencies = [ - "log", - "thiserror", -] - [[package]] name = "os_str_bytes" version = "6.4.1" @@ -4460,20 +4500,6 @@ dependencies = [ "prost", ] -[[package]] -name = "proxy-helpers" -version = "0.1.0" -dependencies = [ - "bytes", - "futures", - "log", - "nym-task", - "ordered-buffer", - "socks5-requests", - "tokio", - "tokio-util", -] - [[package]] name = "quick-error" version = "1.2.3" @@ -5309,19 +5335,6 @@ dependencies = [ "syn", ] -[[package]] -name = "service-providers-common" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "nym-bin-common", - "nym-sphinx-anonymous-replies", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "servo_arc" version = "0.1.1" @@ -5460,17 +5473,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "socks5-requests" -version = "0.1.0" -dependencies = [ - "nym-sphinx-addressing", - "serde", - "serde_json", - "service-providers-common", - "thiserror", -] - [[package]] name = "soup3" version = "0.3.2" @@ -6748,9 +6750,6 @@ dependencies = [ "async-trait", "base64 0.13.1", "bip39", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", - "coconut-interface", "colored 2.0.0", "cosmrs", "cosmwasm-std", @@ -6758,15 +6757,18 @@ dependencies = [ "cw4", "flate2", "futures", - "group-contract-common", "itertools", "log", - "multisig-contract-common", "nym-api-requests", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", "nym-config", "nym-contracts-common", "nym-execute", + "nym-group-contract-common", "nym-mixnet-contract-common", + "nym-multisig-contract-common", "nym-network-defaults", "nym-vesting-contract", "nym-vesting-contract-common", @@ -6778,6 +6780,7 @@ dependencies = [ "thiserror", "tokio", "url", + "zeroize", ] [[package]] diff --git a/nym-wallet/CHANGELOG.md b/nym-wallet/CHANGELOG.md index 882ae175ad..8eccaf2471 100644 --- a/nym-wallet/CHANGELOG.md +++ b/nym-wallet/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +## [v1.1.13] (2023-03-15) + +- Wallet - in the vesting section separate the "Unlocked transferable tokens" into "Unlocked vesting tokens" and "Transferable rewards" for better clarity ([#3132]) +- Wallet - change the Bonding flow to include an additional step for the user to provide a unique signature when they bond their node ([#3109]) + +[#3132]: https://github.com/nymtech/nym/issues/3132 +[#3109]: https://github.com/nymtech/nym/issues/3109 + ## [v1.1.11] (2023-03-07) - Wallet: optional gas and memo fields ([#2222]) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index f07787fc14..d88f92b3ea 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -84,15 +84,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anyhow" version = "1.0.69" @@ -478,21 +469,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "time 0.1.45", - "wasm-bindgen", - "winapi", -] - [[package]] name = "cipher" version = "0.3.0" @@ -519,9 +495,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.1.2" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd125be87bf4c255ebc50de0b7f4d2a6201e8ac3dc86e39c0ad081dc5e7236fe" +checksum = "501ff0a401473ea1d4c3b125ff95506b62c5bc5768d818634195fbb7c4ad5ff4" dependencies = [ "clap", ] @@ -598,49 +574,6 @@ dependencies = [ "objc", ] -[[package]] -name = "coconut-bandwidth-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "multisig-contract-common", - "nym-contracts-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58", - "getset", - "nymcoconut", - "serde", - "thiserror", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -1027,50 +960,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cxx" -version = "1.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "darling" version = "0.13.4" @@ -1196,31 +1085,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] -name = "dkg" -version = "0.1.0" -dependencies = [ - "bitvec", - "bls12_381", - "bs58", - "ff", - "group", - "lazy_static", - "nym-pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - -[[package]] -name = "dotenv" -version = "0.15.0" +name = "dotenvy" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" [[package]] name = "dtoa" @@ -1898,15 +1766,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "group-contract-common" -version = "0.1.0" -dependencies = [ - "cw4", - "schemars", - "serde", -] - [[package]] name = "gtk" version = "0.15.5" @@ -2243,30 +2102,6 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "iana-time-zone" -version = "0.1.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -dependencies = [ - "cxx", - "cxx-build", -] - [[package]] name = "ico" version = "0.2.0" @@ -2560,15 +2395,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -2708,20 +2534,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw3-fixed-multisig", - "cw4", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -2871,15 +2683,24 @@ dependencies = [ "syn", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "nym-api-requests" version = "0.1.0" dependencies = [ "bs58", - "coconut-interface", "cosmrs", "cosmwasm-std", "getset", + "nym-coconut-interface", "nym-mixnet-contract-common", "schemars", "serde", @@ -2898,6 +2719,39 @@ dependencies = [ "vergen", ] +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-interface" +version = "0.1.0" +dependencies = [ + "bs58", + "getset", + "nymcoconut", + "serde", + "thiserror", +] + [[package]] name = "nym-config" version = "0.1.0" @@ -2922,6 +2776,27 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-dkg" +version = "0.1.0" +dependencies = [ + "bitvec", + "bls12_381", + "bs58", + "ff", + "group", + "lazy_static", + "nym-pemstore", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + [[package]] name = "nym-execute" version = "0.1.0" @@ -2930,6 +2805,15 @@ dependencies = [ "syn", ] +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +dependencies = [ + "cw4", + "schemars", + "serde", +] + [[package]] name = "nym-mixnet-contract-common" version = "0.2.0" @@ -2944,16 +2828,30 @@ dependencies = [ "serde_json", "serde_repr", "thiserror", - "time 0.3.17", + "time", "ts-rs", ] +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw3-fixed-multisig", + "cw4", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "nym-network-defaults" version = "0.1.0" dependencies = [ "cfg-if", - "dotenv", + "dotenvy", "hex-literal", "once_cell", "serde", @@ -2972,12 +2870,12 @@ dependencies = [ name = "nym-types" version = "1.0.0" dependencies = [ - "coconut-interface", "cosmrs", "cosmwasm-std", "eyre", "itertools", "log", + "nym-coconut-interface", "nym-config", "nym-mixnet-contract-common", "nym-vesting-contract", @@ -2995,14 +2893,16 @@ dependencies = [ [[package]] name = "nym-vesting-contract" -version = "1.1.4" +version = "1.2.0-pre.1" dependencies = [ "cosmwasm-std", "cw-storage-plus", + "cw2", "nym-contracts-common", "nym-mixnet-contract-common", "nym-vesting-contract-common", "schemars", + "semver 1.0.16", "serde", "thiserror", "vergen", @@ -3057,26 +2957,25 @@ dependencies = [ [[package]] name = "nym_wallet" -version = "1.1.10" +version = "1.1.11" dependencies = [ "aes-gcm", "argon2 0.3.4", "base64 0.13.1", "bip39", "cfg-if", - "chrono", - "coconut-interface", "colored 2.0.0", "cosmrs", "cosmwasm-std", "dirs", - "dotenv", + "dotenvy", "eyre", "fern", "futures", "itertools", "k256", "log", + "nym-coconut-interface", "nym-config", "nym-mixnet-contract-common", "nym-types", @@ -3098,6 +2997,7 @@ dependencies = [ "tempfile", "tendermint-rpc", "thiserror", + "time", "tokio", "toml", "ts-rs", @@ -3113,11 +3013,11 @@ dependencies = [ "bls12_381", "bs58", "digest 0.9.0", - "dkg", "ff", "getrandom 0.2.8", "group", "itertools", + "nym-dkg", "nym-pemstore", "rand 0.8.5", "serde", @@ -3590,7 +3490,7 @@ dependencies = [ "line-wrap", "quick-xml", "serde", - "time 0.3.17", + "time", ] [[package]] @@ -4245,12 +4145,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" - [[package]] name = "sct" version = "0.6.1" @@ -4885,7 +4779,7 @@ dependencies = [ "tauri-utils", "tempfile", "thiserror", - "time 0.3.17", + "time", "tokio", "url", "uuid 1.3.0", @@ -4932,7 +4826,7 @@ dependencies = [ "sha2 0.10.6", "tauri-utils", "thiserror", - "time 0.3.17", + "time", "uuid 1.3.0", "walkdir", ] @@ -5060,7 +4954,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.17", + "time", "zeroize", ] @@ -5093,7 +4987,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.17", + "time", ] [[package]] @@ -5121,7 +5015,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.17", + "time", "tokio", "tracing", "url", @@ -5185,17 +5079,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.17" @@ -5203,6 +5086,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa 1.0.5", + "libc", + "num_threads", "serde", "time-core", "time-macros", @@ -5472,12 +5357,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - [[package]] name = "unicode-xid" version = "0.2.4" @@ -5540,9 +5419,6 @@ dependencies = [ "async-trait", "base64 0.13.1", "bip39", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", - "coconut-interface", "colored 2.0.0", "cosmrs", "cosmwasm-std", @@ -5550,15 +5426,18 @@ dependencies = [ "cw4", "flate2", "futures", - "group-contract-common", "itertools", "log", - "multisig-contract-common", "nym-api-requests", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", "nym-config", "nym-contracts-common", "nym-execute", + "nym-group-contract-common", "nym-mixnet-contract-common", + "nym-multisig-contract-common", "nym-network-defaults", "nym-vesting-contract", "nym-vesting-contract-common", @@ -5570,6 +5449,7 @@ dependencies = [ "thiserror", "tokio", "url", + "zeroize", ] [[package]] @@ -5598,7 +5478,7 @@ dependencies = [ "rustc_version 0.4.0", "rustversion", "thiserror", - "time 0.3.17", + "time", ] [[package]] @@ -5646,12 +5526,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/nym-wallet/package.json b/nym-wallet/package.json index f44d127d96..05d6195526 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nym-wallet-app", - "version": "1.1.11", + "version": "1.1.12", "main": "index.js", "license": "MIT", "scripts": { diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 0c0deb5384..81b001f0bf 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym_wallet" -version = "1.1.11" +version = "1.1.12" description = "Nym Native Wallet" authors = ["Nym Technologies SA"] license = "" @@ -21,10 +21,9 @@ tauri-macros = "=1.2.1" [dependencies] bip39 = "1.0" cfg-if = "1.0.0" -chrono = "0.4" colored = "2.0" dirs = "4.0" -dotenv = "0.15.0" +dotenvy = "0.15.6" eyre = "0.6.5" fern = { version = "0.6.1", features = ["colored"] } futures = "0.3.15" @@ -40,6 +39,7 @@ serde_repr = "0.1" strum = { version = "0.23", features = ["derive"] } tauri = { version = "=1.2.3", features = ["clipboard-all", "shell-open", "updater", "window-maximize"] } tendermint-rpc = "0.23.0" +time = { version = "0.3.17", features = ["local-offset"] } thiserror = "1.0" tokio = { version = "1.10", features = ["full"] } toml = "0.5.8" @@ -60,7 +60,7 @@ validator-client = { path = "../../common/client-libs/validator-client", feature nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } nym-config = { path = "../../common/config" } -coconut-interface = { path = "../../common/coconut-interface" } +nym-coconut-interface = { path = "../../common/coconut-interface" } # Used for Type conversion, can be extracted but its a lot of work nym-vesting-contract = { path = "../../contracts/vesting" } nym-types = { path = "../../common/types" } diff --git a/nym-wallet/src-tauri/src/log.rs b/nym-wallet/src-tauri/src/log.rs index 626200b469..8ffc883485 100644 --- a/nym-wallet/src-tauri/src/log.rs +++ b/nym-wallet/src-tauri/src/log.rs @@ -4,6 +4,24 @@ use fern::colors::{Color, ColoredLevelConfig}; use serde::Serialize; use serde_repr::{Deserialize_repr, Serialize_repr}; use tauri::Manager; +use time::{format_description, OffsetDateTime}; + +fn formatted_time() -> String { + // if we fail to obtain local time according to local offset, fallback to utc + let _now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); + + // if we're running it in the unit test, use the hardcoded value + #[cfg(test)] + let _now = OffsetDateTime::from_unix_timestamp(1666666666).unwrap(); + + // the unwraps are fine as we know this description is correct + // note: the reason for this very particular format is a very simple one + // it's what we've always been using since we copied it from the example, + // so feel free to update it to whatever + let format = + format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]").unwrap(); + _now.format(&format).unwrap() +} pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> { let colors = ColoredLevelConfig::new() @@ -21,7 +39,7 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE .format(move |out, message, record| { out.finish(format_args!( "{}[{}][{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + formatted_time(), record.target(), colors.color(record.level()), message, @@ -33,7 +51,7 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE .format(move |out, message, record| { out.finish(format_args!( "{}[{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + formatted_time(), record.target(), message, )) @@ -116,3 +134,15 @@ impl From<log::Level> for LogLevel { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn log_formatting() { + let expected_chrono_formated = "[2022-10-25][02:57:46]".to_string(); + let new_time_based = formatted_time(); + assert_eq!(new_time_based, expected_chrono_formated) + } +} diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 70f43c3c96..4677e46e69 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -29,7 +29,7 @@ mod wallet_storage; #[allow(clippy::too_many_lines)] fn main() { - dotenv::dotenv().ok(); + dotenvy::dotenv().ok(); let context = tauri::generate_context!(); tauri::Builder::default() @@ -126,6 +126,8 @@ fn main() { vesting::queries::locked_coins, vesting::queries::original_vesting, vesting::queries::spendable_coins, + vesting::queries::spendable_vested_coins, + vesting::queries::spendable_reward_coins, vesting::queries::get_historical_vesting_staking_reward, vesting::queries::get_spendable_vested_coins, vesting::queries::get_spendable_reward_coins, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 51c53b8f95..fb145749ba 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -2,7 +2,8 @@ use crate::config::{Config, CUSTOM_SIMULATED_GAS_MULTIPLIER}; use crate::error::BackendError; use crate::network_config; use crate::state::{WalletAccountIds, WalletState}; -use crate::wallet_storage::{self, DEFAULT_LOGIN_ID}; +use crate::utils::{SensitiveStringWrapper, ZeroizeMnemonicWrapper}; +use crate::wallet_storage::{self, UserPassword, DEFAULT_LOGIN_ID}; use bip39::{Language, Mnemonic}; use cosmrs::bip32::DerivationPath; use itertools::Itertools; @@ -11,7 +12,6 @@ use nym_types::account::{Account, AccountEntry, Balance}; use nym_wallet_types::network::Network as WalletNetwork; use rand::seq::SliceRandom; use std::collections::HashMap; -use std::str::FromStr; use strum::IntoEnumIterator; use url::Url; use validator_client::nyxd::wallet::{AccountData, DirectSecp256k1HdWallet}; @@ -19,10 +19,10 @@ use validator_client::{nyxd::SigningNyxdClient, Client}; #[tauri::command] pub async fn connect_with_mnemonic( - mnemonic: String, + mnemonic: SensitiveStringWrapper, state: tauri::State<'_, WalletState>, ) -> Result<Account, BackendError> { - let mnemonic = Mnemonic::from_str(&mnemonic)?; + let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?; _connect_with_mnemonic(mnemonic, state).await } @@ -48,13 +48,13 @@ pub async fn get_balance(state: tauri::State<'_, WalletState>) -> Result<Balance } #[tauri::command] -pub fn create_new_mnemonic() -> String { - random_mnemonic().to_string() +pub fn create_new_mnemonic() -> SensitiveStringWrapper { + random_mnemonic().into_string() } #[tauri::command] -pub fn validate_mnemonic(mnemonic: &str) -> bool { - Mnemonic::from_str(mnemonic).is_ok() +pub fn validate_mnemonic(mnemonic: SensitiveStringWrapper) -> bool { + ZeroizeMnemonicWrapper::try_from_string(mnemonic).is_ok() } #[tauri::command] @@ -82,13 +82,15 @@ pub async fn logout(state: tauri::State<'_, WalletState>) -> Result<(), BackendE Ok(()) } -fn random_mnemonic() -> Mnemonic { +fn random_mnemonic() -> ZeroizeMnemonicWrapper { let mut rng = rand::thread_rng(); - Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap() + Mnemonic::generate_in_with(&mut rng, Language::English, 24) + .unwrap() + .into() } async fn _connect_with_mnemonic( - mnemonic: Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, state: tauri::State<'_, WalletState>, ) -> Result<Account, BackendError> { { @@ -139,7 +141,7 @@ async fn _connect_with_mnemonic( &default_nyxd_urls, &default_api_urls, &config, - &mnemonic, + mnemonic.as_ref(), )?; // Set the default account @@ -296,30 +298,31 @@ pub fn does_password_file_exist() -> Result<bool, BackendError> { } #[tauri::command] -pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendError> { +pub fn create_password( + mnemonic: SensitiveStringWrapper, + password: UserPassword, +) -> Result<(), BackendError> { if does_password_file_exist()? { return Err(BackendError::WalletFileAlreadyExists); } log::info!("Creating password"); - let mnemonic = Mnemonic::from_str(mnemonic)?; + let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, login id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let password = wallet_storage::UserPassword::new(password); wallet_storage::store_login_with_multiple_accounts(mnemonic, hd_path, login_id, &password) } #[tauri::command] pub async fn sign_in_with_password( - password: String, + password: UserPassword, state: tauri::State<'_, WalletState>, ) -> Result<Account, BackendError> { log::info!("Signing in with password"); // Currently we only support a single, default, id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let password = wallet_storage::UserPassword::new(password); let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; let mnemonic = extract_first_mnemonic(&stored_login)?; @@ -327,7 +330,7 @@ pub async fn sign_in_with_password( set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()) .await?; - _connect_with_mnemonic(mnemonic, state).await + _connect_with_mnemonic(mnemonic.into(), state).await } fn extract_first_mnemonic( @@ -352,7 +355,7 @@ fn extract_first_mnemonic( #[tauri::command] pub async fn sign_in_with_password_and_account_id( account_id: &str, - password: &str, + password: UserPassword, state: tauri::State<'_, WalletState>, ) -> Result<Account, BackendError> { log::info!("Signing in with password"); @@ -360,7 +363,6 @@ pub async fn sign_in_with_password_and_account_id( // Currently we only support a single, default, id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; let mnemonic = extract_mnemonic(&stored_login, &account_id)?; @@ -368,7 +370,7 @@ pub async fn sign_in_with_password_and_account_id( set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()) .await?; - _connect_with_mnemonic(mnemonic, state).await + _connect_with_mnemonic(mnemonic.into(), state).await } fn extract_mnemonic( @@ -402,18 +404,17 @@ pub fn archive_wallet_file() -> Result<(), BackendError> { #[tauri::command] pub async fn add_account_for_password( - mnemonic: &str, - password: &str, + mnemonic: SensitiveStringWrapper, + password: UserPassword, account_id: &str, state: tauri::State<'_, WalletState>, ) -> Result<AccountEntry, BackendError> { log::info!("Adding account for the current password: {account_id}"); - let mnemonic = Mnemonic::from_str(mnemonic)?; + let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, login id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); wallet_storage::append_account_to_login( mnemonic.clone(), @@ -426,7 +427,13 @@ pub async fn add_account_for_password( let address = { let state = state.read().await; let network: NymNetworkDetails = state.current_network().into(); - derive_address(mnemonic, &network.chain_details.bech32_account_prefix)?.to_string() + // safety: the call to `clone_inner` is fine here as the raw mnemonic will get immediately + // passed to `DirectSecp256k1HdWallet` which will zeroize it on drop + derive_address( + mnemonic.into_cloned_inner(), + &network.chain_details.bech32_account_prefix, + )? + .to_string() }; // Re-read all the acccounts from the wallet to reset the state, rather than updating it @@ -450,12 +457,10 @@ async fn set_state_with_all_accounts( state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::trace!("Set state with accounts:"); - let all_accounts: Vec<_> = stored_login - .unwrap_into_multiple_accounts(first_id_when_converting) - .into_accounts() - .collect(); + let stored = stored_login.unwrap_into_multiple_accounts(first_id_when_converting); + let all_accounts = stored.inner(); - for account in &all_accounts { + for account in all_accounts { log::trace!("account: {:?}", account.id()); } @@ -490,7 +495,7 @@ async fn set_state_with_all_accounts( #[tauri::command] pub async fn remove_account_for_password( - password: &str, + password: UserPassword, account_id: &str, state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { @@ -498,7 +503,6 @@ pub async fn remove_account_for_password( // Currently we only support a single, default, id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); wallet_storage::remove_account_from_login(&login_id, &account_id, &password)?; // Load to reset the internal state @@ -513,7 +517,8 @@ fn derive_address( mnemonic: bip39::Mnemonic, prefix: &str, ) -> Result<cosmrs::AccountId, BackendError> { - DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)? + // note: the ephemeral wallet will zeroize the mnemonic on drop + DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic) .try_derive_accounts()? .first() .map(AccountData::address) @@ -547,21 +552,20 @@ pub async fn list_accounts( #[tauri::command] pub fn show_mnemonic_for_account_in_password( account_id: String, - password: String, -) -> Result<String, BackendError> { + password: UserPassword, +) -> Result<SensitiveStringWrapper, BackendError> { log::info!("Getting mnemonic for: {account_id}"); let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id); - let password = wallet_storage::UserPassword::new(password); let mnemonic = _show_mnemonic_for_account_in_password(&login_id, &account_id, &password)?; - Ok(mnemonic.to_string()) + Ok(mnemonic.into_string()) } fn _show_mnemonic_for_account_in_password( login_id: &wallet_storage::LoginId, account_id: &wallet_storage::AccountId, password: &wallet_storage::UserPassword, -) -> Result<bip39::Mnemonic, BackendError> { +) -> Result<ZeroizeMnemonicWrapper, BackendError> { let stored_account = wallet_storage::load_existing_login(login_id, password)?; let mnemonic = match stored_account { wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), @@ -571,12 +575,13 @@ fn _show_mnemonic_for_account_in_password( .mnemonic() .clone(), }; - Ok(mnemonic) + Ok(mnemonic.into()) } #[cfg(test)] mod tests { use std::path::PathBuf; + use std::str::FromStr; use crate::wallet_storage::{ self, @@ -606,8 +611,8 @@ mod tests { let all_accounts: Vec<_> = stored_login .unwrap_into_multiple_accounts(account_id.clone()) - .into_accounts() - .collect(); + .inner() + .to_vec(); assert_eq!( all_accounts, @@ -639,8 +644,8 @@ mod tests { let all_accounts: Vec<_> = stored_login .unwrap_into_multiple_accounts(account_id) - .into_accounts() - .collect(); + .inner() + .to_vec(); let expected_mn2 = bip39::Mnemonic::from_str("border hurt skull lunar goddess second danger game dismiss exhaust oven thumb dog drama onion false orchard spice tent next predict invite cherry green").unwrap(); let expected_mn3 = bip39::Mnemonic::from_str("gentle crowd rule snap girl urge flat jump winner cluster night sand museum stock grunt quick tree acquire traffic major awake tag rack peasant").unwrap(); diff --git a/nym-wallet/src-tauri/src/operations/vesting/queries.rs b/nym-wallet/src-tauri/src/operations/vesting/queries.rs index afef1d3dac..72ce47e9ef 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/queries.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/queries.rs @@ -28,7 +28,7 @@ pub(crate) async fn locked_coins( ) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< locked coins = {}", display); + log::info!("<<< locked coins = {display}"); Ok(display) } @@ -50,7 +50,43 @@ pub(crate) async fn spendable_coins( .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< spendable coins = {}", display); + log::info!("<<< spendable coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn spendable_vested_coins( + state: tauri::State<'_, WalletState>, +) -> Result<DecCoin, BackendError> { + log::info!(">>> Query spendable vested coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_spendable_vested_coins(client.nyxd.address().as_ref()) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< spendable vested coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn spendable_reward_coins( + state: tauri::State<'_, WalletState>, +) -> Result<DecCoin, BackendError> { + log::info!(">>> Query spendable reward coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_spendable_reward_coins(client.nyxd.address().as_ref()) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< spendable reward coins = {display}"); Ok(display) } @@ -73,7 +109,7 @@ pub(crate) async fn vested_coins( .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< vested coins = {}", display); + log::info!("<<< vested coins = {display}"); Ok(display) } @@ -96,7 +132,7 @@ pub(crate) async fn vesting_coins( .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< vesting coins = {}", display); + log::info!("<<< vesting coins = {display}"); Ok(display) } @@ -161,7 +197,7 @@ pub(crate) async fn get_historical_vesting_staking_reward( .get_historical_vesting_staking_reward(client.nyxd.address().as_ref()) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< historical vesting staking reward coins = {}", display); + log::info!("<<< historical vesting staking reward coins = {display}"); Ok(display) } @@ -178,7 +214,7 @@ pub(crate) async fn get_spendable_vested_coins( .get_spendable_vested_coins(client.nyxd.address().as_ref()) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< spendable vested coins = {}", display); + log::info!("<<< spendable vested coins = {display}"); Ok(display) } @@ -195,7 +231,7 @@ pub(crate) async fn get_spendable_reward_coins( .get_spendable_reward_coins(client.nyxd.address().as_ref()) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< spendable reward coins = {}", display); + log::info!("<<< spendable reward coins = {display}"); Ok(display) } @@ -212,7 +248,7 @@ pub(crate) async fn get_delegated_coins( .get_delegated_coins(client.nyxd.address().as_ref()) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< delegated coins = {}", display); + log::info!("<<< delegated coins = {display}"); Ok(display) } @@ -229,7 +265,7 @@ pub(crate) async fn get_pledged_coins( .get_pledged_coins(client.nyxd.address().as_ref()) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< pledged coins = {}", display); + log::info!("<<< pledged coins = {display}"); Ok(display) } @@ -246,7 +282,7 @@ pub(crate) async fn get_staked_coins( .get_staked_coins(client.nyxd.address().as_ref()) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< staked coins = {}", display); + log::info!("<<< staked coins = {display}"); Ok(display) } @@ -263,7 +299,7 @@ pub(crate) async fn get_withdrawn_coins( .get_withdrawn_coins(client.nyxd.address().as_ref()) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< pledged coins = {}", display); + log::info!("<<< pledged coins = {display}"); Ok(display) } diff --git a/nym-wallet/src-tauri/src/utils.rs b/nym-wallet/src-tauri/src/utils.rs index fee3a1b727..c2a22fcb2f 100644 --- a/nym-wallet/src-tauri/src/utils.rs +++ b/nym-wallet/src-tauri/src/utils.rs @@ -4,14 +4,16 @@ use crate::error::BackendError; use crate::nyxd_client; use crate::state::WalletState; +use bip39::Mnemonic; use cosmwasm_std::Decimal; use nym_mixnet_contract_common::{IdentityKey, MixId, Percent}; use nym_types::currency::DecCoin; use nym_types::mixnode::MixNodeCostParams; use nym_wallet_types::app::AppEnv; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use validator_client::nyxd::traits::MixnetQueryClient; use validator_client::nyxd::{tx, Coin, CosmosCoin, Gas, GasPrice}; +use zeroize::Zeroize; fn get_env_as_option(key: &str) -> Option<String> { match ::std::env::var(key) { @@ -192,3 +194,94 @@ pub async fn get_old_and_incorrect_hardcoded_fee( log::info!("hardcoded fee for {:?} is {:?}", operation, coin); guard.attempt_convert_to_display_dec_coin(coin) } + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SensitiveStringWrapper(String); + +impl<'de> Deserialize<'de> for SensitiveStringWrapper { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + Ok(SensitiveStringWrapper(String::deserialize(deserializer)?)) + } +} + +impl Serialize for SensitiveStringWrapper { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + // unfortunately this serialized value will live on... + self.0.serialize(serializer) + } +} + +impl From<String> for SensitiveStringWrapper { + fn from(value: String) -> Self { + SensitiveStringWrapper(value) + } +} + +impl AsRef<str> for SensitiveStringWrapper { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +#[derive(Clone)] +// can't do it natively until https://github.com/rust-bitcoin/rust-bip39/pull/32 gets merged and released... +pub(crate) struct ZeroizeMnemonicWrapper(bip39::Mnemonic); + +impl From<bip39::Mnemonic> for ZeroizeMnemonicWrapper { + fn from(value: Mnemonic) -> Self { + ZeroizeMnemonicWrapper(value) + } +} + +impl Zeroize for ZeroizeMnemonicWrapper { + fn zeroize(&mut self) { + // overwrite the mnemonic value with a completely random one + // (a poor man's zeroize until bip39 crate does it properly...) + self.0 = Mnemonic::generate(self.0.word_count()).unwrap(); + } +} + +impl Drop for ZeroizeMnemonicWrapper { + fn drop(&mut self) { + self.zeroize() + } +} + +impl AsRef<bip39::Mnemonic> for ZeroizeMnemonicWrapper { + fn as_ref(&self) -> &Mnemonic { + &self.0 + } +} + +impl ZeroizeMnemonicWrapper { + pub(crate) fn into_string(self) -> SensitiveStringWrapper { + SensitiveStringWrapper(self.0.to_string()) + } + + pub(crate) fn try_from_string(string: SensitiveStringWrapper) -> Result<Self, bip39::Error> { + let res = string.as_ref().parse()?; + Ok(ZeroizeMnemonicWrapper(res)) + } + + // special care must be taken when calling this method as the mnemonic will no longer get zeroized! + pub(crate) fn into_cloned_inner(self) -> bip39::Mnemonic { + self.0.clone() + } + + #[cfg(test)] + pub(crate) fn unchecked_clone_inner(&self) -> bip39::Mnemonic { + self.0.clone() + } + + #[cfg(test)] + pub(crate) fn generate_random() -> Self { + ZeroizeMnemonicWrapper(Mnemonic::generate(24).unwrap()) + } +} diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index f5c7296442..9451c065b7 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -19,6 +19,7 @@ use validator_client::nyxd::bip32::DerivationPath; use zeroize::Zeroize; use crate::error::BackendError; +use crate::utils::ZeroizeMnemonicWrapper; use super::encryption::EncryptedData; use super::password::{AccountId, LoginId}; @@ -193,6 +194,7 @@ impl StoredLogin { /// Multiple stored accounts, each entry having an id and a data field. #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] +#[zeroize(drop)] pub(crate) struct MultipleAccounts { accounts: Vec<WalletAccount>, } @@ -220,8 +222,8 @@ impl MultipleAccounts { .find(|account| account.mnemonic() == mnemonic) } - pub(crate) fn into_accounts(self) -> impl Iterator<Item = WalletAccount> { - self.accounts.into_iter() + pub(crate) fn inner(&self) -> &[WalletAccount] { + &self.accounts } #[allow(unused)] @@ -236,17 +238,19 @@ impl MultipleAccounts { pub(crate) fn add( &mut self, id: AccountId, - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, ) -> Result<(), BackendError> { if self.get_account(&id).is_some() { Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin) - } else if self.get_account_with_mnemonic(&mnemonic).is_some() { + } else if self.get_account_with_mnemonic(mnemonic.as_ref()).is_some() { Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin) } else { self.accounts.push(WalletAccount::new( id, - MnemonicAccount::new(mnemonic, hd_path), + // safety: the call to `clone_inner` is fine here as the raw mnemonic will get immediately + // passed to `MnemonicAccount` which will zeroize it on drop + MnemonicAccount::new(mnemonic.into_cloned_inner(), hd_path), )); Ok(()) } @@ -269,6 +273,7 @@ impl From<Vec<WalletAccount>> for MultipleAccounts { /// An entry in the list of stored accounts #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] +#[zeroize(drop)] pub(crate) struct WalletAccount { id: AccountId, account: AccountData, diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 0db4e87e92..57464eda49 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -14,10 +14,11 @@ pub(crate) use crate::wallet_storage::password::{AccountId, LoginId, UserPasswor use crate::error::BackendError; use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME}; -use chrono::Local; +use crate::utils::ZeroizeMnemonicWrapper; use std::ffi::OsString; use std::fs::{self, create_dir_all, OpenOptions}; use std::path::{Path, PathBuf}; +use time::OffsetDateTime; use validator_client::nyxd::bip32::DerivationPath; #[cfg(test)] @@ -138,7 +139,7 @@ fn store_login_at_file( } pub(crate) fn store_login_with_multiple_accounts( - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, id: LoginId, password: &UserPassword, @@ -153,7 +154,7 @@ pub(crate) fn store_login_with_multiple_accounts( fn store_login_with_multiple_accounts_at_file( filepath: &Path, - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, id: LoginId, password: &UserPassword, @@ -184,7 +185,7 @@ fn store_login_with_multiple_accounts_at_file( /// account in the list of accounts associated with the encrypted entry. The inner id for this /// entry will be set to the same as the outer, unencrypted, id. pub(crate) fn append_account_to_login( - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, id: LoginId, inner_id: AccountId, @@ -200,7 +201,7 @@ pub(crate) fn append_account_to_login( fn append_account_to_login_at_file( filepath: &Path, - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, id: LoginId, inner_id: AccountId, @@ -283,7 +284,10 @@ fn append_timestamp_to_filename( } fn _archive_wallet_file(path: &Path) -> Result<(), BackendError> { - let timestamp: OsString = Local::now().timestamp_millis().to_string().into(); + let timestamp: OsString = OffsetDateTime::now_utc() + .unix_timestamp() + .to_string() + .into(); let mut additional_number = 0; let mut new_path = append_timestamp_to_filename(path, timestamp.clone(), None)?; @@ -418,12 +422,19 @@ mod tests { fn store_single_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1.clone(), + &password, + ) + .unwrap(); let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); assert_eq!(stored_wallet.len(), 1); @@ -439,7 +450,7 @@ mod tests { fn store_single_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -467,7 +478,7 @@ mod tests { fn store_twice_for_the_same_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -475,7 +486,7 @@ mod tests { // Store the first login store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -484,7 +495,13 @@ mod tests { // and storing the same id again fails assert!(matches!( - store_login_at_file(&wallet_file, account1, hd_path, id1, &password,), + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1, + &password, + ), Err(BackendError::WalletLoginIdAlreadyExists), )); } @@ -493,7 +510,7 @@ mod tests { fn store_twice_for_the_same_id_fails_with_multiple() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -525,13 +542,20 @@ mod tests { fn load_with_wrong_password_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); let id1 = LoginId::new("first".to_string()); - store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1.clone(), + &password, + ) + .unwrap(); // Trying to load it with wrong password now fails assert!(matches!( @@ -544,7 +568,7 @@ mod tests { fn load_with_wrong_password_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); @@ -570,13 +594,20 @@ mod tests { fn load_with_wrong_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1, + &password, + ) + .unwrap(); // Trying to load with the wrong id assert!(matches!( @@ -589,7 +620,7 @@ mod tests { fn load_with_wrong_id_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -609,14 +640,14 @@ mod tests { fn store_and_load_a_single_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -625,7 +656,7 @@ mod tests { let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); + assert_eq!(account1.as_ref(), acc.mnemonic()); assert_eq!(&hd_path, acc.hd_path()); } @@ -633,7 +664,7 @@ mod tests { fn store_and_load_a_single_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let acc1 = bip39::Mnemonic::generate(24).unwrap(); + let acc1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -654,7 +685,7 @@ mod tests { .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); - assert_eq!(account.mnemonic(), &acc1); + assert_eq!(account.mnemonic(), acc1.as_ref()); assert_eq!(account.hd_path(), &hd_path); } @@ -662,8 +693,8 @@ mod tests { fn store_a_second_login_with_a_different_password_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); @@ -672,7 +703,7 @@ mod tests { store_login_at_file( &wallet_file, - account1, + account1.into_cloned_inner(), cosmos_hd_path.clone(), id1, &password, @@ -681,7 +712,13 @@ mod tests { // Can't store a second login if you use different password assert!(matches!( - store_login_at_file(&wallet_file, account2, cosmos_hd_path, id2, &bad_password), + store_login_at_file( + &wallet_file, + account2.into_cloned_inner(), + cosmos_hd_path, + id2, + &bad_password + ), Err(BackendError::WalletDifferentPasswordDetected), )); } @@ -690,8 +727,8 @@ mod tests { fn store_a_second_login_with_a_different_password_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); @@ -724,8 +761,8 @@ mod tests { fn store_two_mnemonic_accounts_gives_different_salts_and_iv() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -733,7 +770,14 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1, + &password, + ) + .unwrap(); let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); let encrypted_blob = &stored_wallet @@ -746,7 +790,14 @@ mod tests { let original_salt = encrypted_blob.salt().to_vec(); // Add an extra account - store_login_at_file(&wallet_file, account2, different_hd_path, id2, &password).unwrap(); + store_login_at_file( + &wallet_file, + account2.into_cloned_inner(), + different_hd_path, + id2, + &password, + ) + .unwrap(); let loaded_accounts = load_existing_wallet_at_file(&wallet_file).unwrap(); assert_eq!(loaded_accounts.len(), 2); @@ -764,8 +815,8 @@ mod tests { fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -775,7 +826,7 @@ mod tests { // Store the first account store_login_at_file( &wallet, - account1.clone(), + account1.unchecked_clone_inner(), cosmos_hd_path.clone(), id1.clone(), &password, @@ -784,13 +835,13 @@ mod tests { let login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let acc = login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); + assert_eq!(account1.as_ref(), acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); // Add an extra account store_login_at_file( &wallet, - account2.clone(), + account2.unchecked_clone_inner(), different_hd_path.clone(), id2.clone(), &password, @@ -800,12 +851,12 @@ mod tests { // first account should be unchanged let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(account1.as_ref(), acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account2, acc2.mnemonic()); + assert_eq!(account2.as_ref(), acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); } @@ -813,8 +864,8 @@ mod tests { fn store_one_mnemonic_account_and_one_multi_account() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -824,7 +875,7 @@ mod tests { // Store the first account store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -833,7 +884,7 @@ mod tests { let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); + assert_eq!(account1.as_ref(), acc.mnemonic()); assert_eq!(&hd_path, acc.hd_path()); // Add an extra account @@ -849,7 +900,7 @@ mod tests { // first account should be unchanged let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(account1.as_ref(), acc1.mnemonic()); assert_eq!(&hd_path, acc1.hd_path()); let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); @@ -859,7 +910,7 @@ mod tests { .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); - assert_eq!(account.mnemonic(), &account2); + assert_eq!(account.mnemonic(), account2.as_ref()); assert_eq!(account.hd_path(), &different_hd_path); } @@ -867,7 +918,7 @@ mod tests { fn remove_non_existent_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -887,8 +938,8 @@ mod tests { fn store_and_remove_wallet_login_information() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -898,7 +949,7 @@ mod tests { // Store two accounts with two different passwords store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), cosmos_hd_path.clone(), id1.clone(), &password, @@ -906,7 +957,7 @@ mod tests { .unwrap(); store_login_at_file( &wallet_file, - account2.clone(), + account2.unchecked_clone_inner(), different_hd_path.clone(), id2.clone(), &password, @@ -916,12 +967,12 @@ mod tests { // Load and compare let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(account1.as_ref(), acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account2, acc2.mnemonic()); + assert_eq!(account2.as_ref(), acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); // Delete the second account @@ -930,7 +981,7 @@ mod tests { // The first account should be unchanged let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(account1.as_ref(), acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); // And we can't load the second one anymore @@ -957,8 +1008,8 @@ mod tests { fn append_account_converts_the_type() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -966,7 +1017,7 @@ mod tests { store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -976,7 +1027,7 @@ mod tests { // Check that it's there as the correct non-multiple type let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc.mnemonic(), &account1); + assert_eq!(acc.mnemonic(), account1.as_ref()); assert_eq!(acc.hd_path(), &hd_path); append_account_to_login_at_file( @@ -993,8 +1044,14 @@ mod tests { let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new(id1.into(), MnemonicAccount::new(account1, hd_path.clone())), - WalletAccount::new(id2, MnemonicAccount::new(account2, hd_path)), + WalletAccount::new( + id1.into(), + MnemonicAccount::new(account1.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id2, + MnemonicAccount::new(account2.into_cloned_inner(), hd_path), + ), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1004,10 +1061,10 @@ mod tests { fn append_accounts_to_existing_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); - let account4 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); + let account3 = ZeroizeMnemonicWrapper::generate_random(); + let account4 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1017,7 +1074,7 @@ mod tests { store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -1026,7 +1083,7 @@ mod tests { store_login_at_file( &wallet_file, - account2.clone(), + account2.unchecked_clone_inner(), hd_path.clone(), id2.clone(), &password, @@ -1036,7 +1093,7 @@ mod tests { // Check that it's there as the correct non-multiple type let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc2.mnemonic(), &account2); + assert_eq!(acc2.mnemonic(), account2.as_ref()); assert_eq!(acc2.hd_path(), &hd_path); // Add a third and fourth mnenonic grouped together with the second one @@ -1062,15 +1119,24 @@ mod tests { // Check that we can load all four let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &account1); + assert_eq!(acc1.mnemonic(), account1.as_ref()); assert_eq!(acc1.hd_path(), &hd_path); let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), - WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), + WalletAccount::new( + id2.into(), + MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id3, + MnemonicAccount::new(account3.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id4, + MnemonicAccount::new(account4.into_cloned_inner(), hd_path), + ), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1080,10 +1146,10 @@ mod tests { fn append_accounts_to_existing_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); - let account4 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); + let account3 = ZeroizeMnemonicWrapper::generate_random(); + let account4 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1134,7 +1200,7 @@ mod tests { let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account1, hd_path.clone()), + MnemonicAccount::new(account1.into_cloned_inner(), hd_path.clone()), )] .into(); assert_eq!(loaded_accounts, &expected); @@ -1144,10 +1210,16 @@ mod tests { let expected = vec![ WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account2, hd_path.clone()), + MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id3, + MnemonicAccount::new(account3.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id4, + MnemonicAccount::new(account4.into_cloned_inner(), hd_path), ), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), - WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1157,7 +1229,7 @@ mod tests { fn append_the_same_mnemonic_twice_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1182,14 +1254,21 @@ mod tests { fn delete_the_same_account_twice_for_a_login_fails() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_login_at_file(&wallet, account1, hd_path.clone(), id1.clone(), &password).unwrap(); + store_login_at_file( + &wallet, + account1.into_cloned_inner(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); append_account_to_login_at_file( &wallet, @@ -1213,8 +1292,8 @@ mod tests { fn delete_the_same_account_twice_for_a_login_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1251,9 +1330,9 @@ mod tests { fn delete_appended_account_doesnt_affect_others() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); + let account3 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1262,7 +1341,7 @@ mod tests { store_login_at_file( &wallet_file, - account1, + account1.into_cloned_inner(), hd_path.clone(), id1.clone(), &password, @@ -1271,7 +1350,7 @@ mod tests { store_login_at_file( &wallet_file, - account2.clone(), + account2.unchecked_clone_inner(), hd_path.clone(), id2.clone(), &password, @@ -1294,8 +1373,14 @@ mod tests { let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path)), + WalletAccount::new( + id2.into(), + MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id3, + MnemonicAccount::new(account3.into_cloned_inner(), hd_path), + ), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1305,8 +1390,8 @@ mod tests { fn remove_all_accounts_for_a_login_removes_the_file_when_empty() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1354,9 +1439,9 @@ mod tests { fn remove_all_accounts_for_a_login_removes_that_login() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); + let account3 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1411,7 +1496,7 @@ mod tests { let acc3 = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account3, hd_path), + MnemonicAccount::new(account3.into_cloned_inner(), hd_path), )] .into(); assert_eq!(acc3, &expected); @@ -1421,10 +1506,10 @@ mod tests { fn append_accounts_and_remove_appended_accounts() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let acc1 = bip39::Mnemonic::generate(24).unwrap(); - let acc2 = bip39::Mnemonic::generate(24).unwrap(); - let acc3 = bip39::Mnemonic::generate(24).unwrap(); - let acc4 = bip39::Mnemonic::generate(24).unwrap(); + let acc1 = ZeroizeMnemonicWrapper::generate_random(); + let acc2 = ZeroizeMnemonicWrapper::generate_random(); + let acc3 = ZeroizeMnemonicWrapper::generate_random(); + let acc4 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1434,7 +1519,7 @@ mod tests { store_login_at_file( &wallet, - acc1.clone(), + acc1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -1443,7 +1528,7 @@ mod tests { store_login_at_file( &wallet, - acc2.clone(), + acc2.unchecked_clone_inner(), hd_path.clone(), id2.clone(), &password, @@ -1479,9 +1564,12 @@ mod tests { let expected = vec![ WalletAccount::new( id2.clone().into(), - MnemonicAccount::new(acc2, hd_path.clone()), + MnemonicAccount::new(acc2.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id4.clone(), + MnemonicAccount::new(acc4.into_cloned_inner(), hd_path.clone()), ), - WalletAccount::new(id4.clone(), MnemonicAccount::new(acc4, hd_path.clone())), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1497,7 +1585,7 @@ mod tests { // The first login is still available let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let account = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(account.mnemonic(), &acc1); + assert_eq!(account.mnemonic(), acc1.as_ref()); assert_eq!(account.hd_path(), &hd_path); } diff --git a/nym-wallet/src-tauri/src/wallet_storage/password.rs b/nym-wallet/src-tauri/src/wallet_storage/password.rs index dfeacf33a8..d523c87496 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/password.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/password.rs @@ -3,7 +3,7 @@ use std::fmt; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use zeroize::Zeroize; // The `LoginId` is the top level id in the wallet file, and is not stored encrypted @@ -83,11 +83,12 @@ impl fmt::Display for AccountId { // simple wrapper for String that will get zeroized on drop #[derive(Zeroize)] #[zeroize(drop)] -pub(crate) struct UserPassword(String); +pub struct UserPassword(String); impl UserPassword { - pub(crate) fn new(pass: String) -> UserPassword { - UserPassword(pass) + #[cfg(test)] + pub(crate) fn new(inner: String) -> Self { + UserPassword(inner) } pub(crate) fn as_bytes(&self) -> &[u8] { @@ -95,8 +96,11 @@ impl UserPassword { } } -impl AsRef<str> for UserPassword { - fn as_ref(&self) -> &str { - self.0.as_ref() +impl<'de> Deserialize<'de> for UserPassword { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + Ok(UserPassword(String::deserialize(deserializer)?)) } } diff --git a/nym-wallet/src-tauri/tauri.conf.json b/nym-wallet/src-tauri/tauri.conf.json index 607ecd1673..c6374a010f 100644 --- a/nym-wallet/src-tauri/tauri.conf.json +++ b/nym-wallet/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-wallet", - "version": "1.1.11" + "version": "1.1.12" }, "build": { "distDir": "../dist", diff --git a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx b/nym-wallet/src/components/Balance/VestingTimeline.tsx similarity index 51% rename from nym-wallet/src/pages/balance/components/vesting-timeline.tsx rename to nym-wallet/src/components/Balance/VestingTimeline.tsx index b1a0f663ce..cfb489056d 100644 --- a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx +++ b/nym-wallet/src/components/Balance/VestingTimeline.tsx @@ -1,9 +1,9 @@ /* eslint-disable react/no-array-index-key */ import React, { useContext } from 'react'; import { useTheme } from '@mui/material/styles'; -import { Box, Tooltip, Typography } from '@mui/material'; +import { Box, Stack, Tooltip, Typography } from '@mui/material'; import { format } from 'date-fns'; -import { AppContext } from '../../../context'; +import { AppContext } from 'src/context'; const calculateMarkerPosition = (arrLength: number, index: number) => (1 / arrLength) * 100 * index; @@ -30,30 +30,33 @@ export const VestingTimeline: FCWithChildren<{ percentageComplete: number }> = ( : undefined; return ( - <Box display="flex" flexDirection="column" gap={1} position="relative" width="100%"> - <svg width="100%" height="12"> - <rect y="2" width="100%" height="6" rx="0" fill="#E6E6E6" /> - <rect y="2" width={`${percentageComplete}%`} height="6" rx="0" fill={theme.palette.success.main} /> - {vestingAccountInfo?.periods.map((period, i, arr) => ( + <Box> + <Stack direction="row" gap={1} alignItems="center"> + <Typography variant="body2">{percentageComplete}%</Typography> + <svg width="100%" height="12"> + <rect y="2" width="100%" height="6" rx="0" fill="#E6E6E6" /> + <rect y="2" width={`${percentageComplete}%`} height="6" rx="0" fill={theme.palette.success.main} /> + {vestingAccountInfo?.periods.map((period, i, arr) => ( + <Marker + position={`${calculateMarkerPosition(arr.length, i)}%`} + color={ + Math.ceil(+percentageComplete) >= calculateMarkerPosition(arr.length, i) + ? theme.palette.success.main + : '#B9B9B9' + } + tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')} + key={i} + /> + ))} <Marker - position={`${calculateMarkerPosition(arr.length, i)}%`} - color={ - Math.ceil(+percentageComplete) >= calculateMarkerPosition(arr.length, i) - ? theme.palette.success.main - : '#B9B9B9' - } - tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')} - key={i} + position="calc(100% - 4px)" + color={percentageComplete === 100 ? theme.palette.success.main : '#B9B9B9'} + tooltipText="End of vesting schedule" /> - ))} - <Marker - position="calc(100% - 4px)" - color={percentageComplete === 100 ? theme.palette.success.main : '#B9B9B9'} - tooltipText="End of vesting schedule" - /> - </svg> + </svg> + </Stack> {!!nextPeriod && ( - <Typography variant="caption" sx={{ color: 'nym.text.muted', position: 'absolute', top: 15, left: 0 }}> + <Typography variant="caption" sx={{ color: 'nym.text.muted', ml: 6 }}> Next vesting period: {format(new Date(nextPeriod * 1000), 'HH:mm do MMM yyyy')} </Typography> )} diff --git a/nym-wallet/src/components/Balance/cards/TokenTransfer.tsx b/nym-wallet/src/components/Balance/cards/TokenTransfer.tsx new file mode 100644 index 0000000000..1600a9b54e --- /dev/null +++ b/nym-wallet/src/components/Balance/cards/TokenTransfer.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Card, Stack, Button } from '@mui/material'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; + +export const TokenTransfer = ({ + onTransfer, + unlockedTokens, + unlockedRewards, + unlockedTransferable, +}: { + unlockedTokens?: string; + unlockedRewards?: string; + unlockedTransferable?: string; + onTransfer: () => void; +}) => ( + <Card variant="outlined" sx={{ p: 3, height: '100%' }}> + <Stack justifyContent="space-between" sx={{ height: '100%' }}> + <Stack gap={1} sx={{ mb: 2 }}> + <ModalListItem label="Unlocked tokens" value={unlockedTokens} /> + <ModalListItem label="Unlocked rewards" value={unlockedRewards} divider /> + <ModalListItem fontSize={16} label="Transferable tokens" value={unlockedTransferable} fontWeight={600} /> + </Stack> + <Button size="large" fullWidth variant="contained" onClick={onTransfer} disableElevation> + Transfer + </Button> + </Stack> + </Card> +); diff --git a/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx b/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx new file mode 100644 index 0000000000..b26b999970 --- /dev/null +++ b/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx @@ -0,0 +1,109 @@ +import React, { useContext, useState, useEffect } from 'react'; +import { + TableContainer, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + Typography, + TableCellProps, + Card, +} from '@mui/material'; +import { Period } from '@nymproject/types'; +import { AppContext } from 'src/context'; +import { VestingTimeline } from '../VestingTimeline'; + +const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [ + { title: 'Locked', align: 'left' }, + { title: 'Period', align: 'left' }, + { title: 'Unlocked', align: 'right' }, +]; + +const vestingPeriod = (current?: Period, original?: number) => { + if (current === 'After') return 'Complete'; + + if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`; + + return 'N/A'; +}; + +export const VestingSchedule = () => { + const { userBalance, clientDetails } = useContext(AppContext); + const [vestedPercentage, setVestedPercentage] = useState(0); + + const calculatePercentage = () => { + const { tokenAllocation, originalVesting } = userBalance; + if (tokenAllocation?.vesting && tokenAllocation.vested && tokenAllocation.vested !== '0' && originalVesting) { + const percentage = (+tokenAllocation.vested / +originalVesting.amount.amount) * 100; + const rounded = percentage.toFixed(2); + setVestedPercentage(+rounded); + } else { + setVestedPercentage(0); + } + }; + + useEffect(() => { + calculatePercentage(); + }, [userBalance.tokenAllocation, calculatePercentage]); + + return ( + <Card variant="outlined" sx={{ p: 3, height: '100%' }}> + <TableContainer sx={{ mb: 2 }}> + <Table> + <TableHead> + <TableRow> + {columnsHeaders.map((header) => ( + <TableCell + key={header.title} + sx={{ color: 'nym.text.muted', pt: 0, border: 'none', pb: 0 }} + align={header.align} + > + {header.title} + </TableCell> + ))} + </TableRow> + </TableHead> + <TableBody> + <TableRow> + <TableCell + sx={{ + color: 'text.primary', + borderBottom: 'none', + textTransform: 'uppercase', + }} + > + {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} + {clientDetails?.display_mix_denom.toUpperCase()} + </TableCell> + <TableCell + align="left" + sx={{ + color: 'text.primary', + borderBottom: 'none', + }} + > + {vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)} + </TableCell> + <TableCell + sx={{ + color: 'text.primary', + borderBottom: 'none', + textTransform: 'uppercase', + }} + align="right" + > + {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} + {clientDetails?.display_mix_denom.toUpperCase()} + </TableCell> + </TableRow> + </TableBody> + </Table> + </TableContainer> + <Typography variant="body2" sx={{ color: 'nym.text.muted', mb: 3 }}> + Percentage + </Typography> + <VestingTimeline percentageComplete={vestedPercentage} /> + </Card> + ); +}; diff --git a/nym-wallet/src/pages/balance/components/TransferModal.tsx b/nym-wallet/src/components/Balance/modals/TransferModal.tsx similarity index 77% rename from nym-wallet/src/pages/balance/components/TransferModal.tsx rename to nym-wallet/src/components/Balance/modals/TransferModal.tsx index 02a8318064..1d3713b184 100644 --- a/nym-wallet/src/pages/balance/components/TransferModal.tsx +++ b/nym-wallet/src/components/Balance/modals/TransferModal.tsx @@ -1,6 +1,5 @@ import React, { useContext, useEffect, useState } from 'react'; import { Alert, Box, CircularProgress } from '@mui/material'; -import { CurrencyDenom, FeeDetails } from '@nymproject/types'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; import { AppContext, urls } from 'src/context'; @@ -8,36 +7,22 @@ import { FeeWarning } from 'src/components/FeeWarning'; import { withdrawVestedCoins } from 'src/requests'; import { Console } from 'src/utils/console'; import { simulateWithdrawVestedCoins } from 'src/requests/simulate'; -import { SuccessModal } from './TransferModalSuccess'; -import { TResponseState, TTransactionDetails } from '../types'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { SuccessModal, TTransactionDetails } from './TransferModalSuccess'; +import { TResponseState } from '../../../pages/balance/types'; export const TransferModal = ({ onClose }: { onClose: () => void }) => { const [state, setState] = useState<TResponseState>(); - const [fee, setFee] = useState<FeeDetails>(); + const [tx, setTx] = useState<TTransactionDetails>(); const { userBalance, clientDetails, network } = useContext(AppContext); - - const getFee = async () => { - if (userBalance.tokenAllocation?.spendable && clientDetails?.display_mix_denom) { - try { - const simulatedFee = await simulateWithdrawVestedCoins({ - amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.display_mix_denom }, - }); - setFee(simulatedFee); - await userBalance.refreshBalances(); - } catch (e) { - setFee({ - amount: { amount: 'n/a', denom: clientDetails?.display_mix_denom.toUpperCase() as CurrencyDenom }, - fee: { Auto: null }, - }); - Console.error(e); - } - } - }; + const { fee, getFee } = useGetFee(); useEffect(() => { - getFee(); + getFee(simulateWithdrawVestedCoins, { + amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.display_mix_denom }, + }); }, []); const handleTransfer = async () => { diff --git a/nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx b/nym-wallet/src/components/Balance/modals/TransferModalSuccess.tsx similarity index 92% rename from nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx rename to nym-wallet/src/components/Balance/modals/TransferModalSuccess.tsx index b27c679d5a..13b762bcb5 100644 --- a/nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx +++ b/nym-wallet/src/components/Balance/modals/TransferModalSuccess.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { Stack, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; import { ConfirmationModal } from 'src/components'; -import { TTransactionDetails } from '../types'; + +export type TTransactionDetails = { amount: string; url: string }; export const SuccessModal = ({ tx, onClose }: { tx?: TTransactionDetails; onClose: () => void }) => ( <ConfirmationModal diff --git a/nym-wallet/src/components/Delegation/DelegationItem.tsx b/nym-wallet/src/components/Delegation/DelegationItem.tsx index 188901902f..c405c18d83 100644 --- a/nym-wallet/src/components/Delegation/DelegationItem.tsx +++ b/nym-wallet/src/components/Delegation/DelegationItem.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Chip, IconButton, TableCell, TableRow, Tooltip, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; import { decimalToPercentage, DelegationWithEverything } from '@nymproject/types'; +import { LockOutlined } from '@mui/icons-material'; import { isDelegation } from 'src/context/delegations'; import { toPercentIntegerString } from 'src/utils'; import { format } from 'date-fns'; @@ -34,9 +35,6 @@ export const DelegationItem = ({ if (nodeIsUnbonded) { return 'This node has unbonded and it does not exist anymore. You need to undelegate from it to get your stake and outstanding rewards (if any) back.'; } - if (item.uses_vesting_contract_tokens) { - return 'Delegation made with locked tockens'; - } return ''; }; @@ -80,6 +78,13 @@ export const DelegationItem = ({ </Typography> </TableCell> <TableCell sx={{ textTransform: 'uppercase', color: 'inherit' }}>{getRewardValue(item)}</TableCell> + <TableCell> + {item.uses_vesting_contract_tokens && ( + <Tooltip title="Delegation uses locked tokens"> + <LockOutlined sx={{ color: 'grey.800' }} fontSize="small" /> + </Tooltip> + )} + </TableCell> <TableCell align="right" sx={{ color: 'inherit' }}> {!item.pending_events.length && !nodeIsUnbonded && ( <DelegationsActionsMenu diff --git a/nym-wallet/src/components/Delegation/DelegationList.tsx b/nym-wallet/src/components/Delegation/DelegationList.tsx index 42f4a5e3a3..e2a66822d7 100644 --- a/nym-wallet/src/components/Delegation/DelegationList.tsx +++ b/nym-wallet/src/components/Delegation/DelegationList.tsx @@ -46,6 +46,7 @@ const headCells: HeadCell[] = [ { id: 'delegated_on_iso_datetime', label: 'Delegated on', sortable: true, align: 'left' }, { id: 'amount', label: 'Delegation', sortable: true, align: 'left' }, { id: 'unclaimed_rewards', label: 'Reward', sortable: true, align: 'left' }, + { id: 'uses_locked_tokens', label: '', sortable: false, align: 'left' }, ]; const EnhancedTableHead: FCWithChildren<EnhancedTableProps> = ({ order, orderBy, onRequestSort }) => { diff --git a/nym-wallet/src/components/Modals/ModalListItem.tsx b/nym-wallet/src/components/Modals/ModalListItem.tsx index 916603e011..830c25705e 100644 --- a/nym-wallet/src/components/Modals/ModalListItem.tsx +++ b/nym-wallet/src/components/Modals/ModalListItem.tsx @@ -7,17 +7,22 @@ export const ModalListItem: FCWithChildren<{ divider?: boolean; hidden?: boolean; fontWeight?: TypographyProps['fontWeight']; + fontSize?: TypographyProps['fontSize']; light?: boolean; value?: React.ReactNode; sxValue?: SxProps; -}> = ({ label, value, hidden, fontWeight, divider, sxValue }) => ( +}> = ({ label, value, hidden, fontWeight, fontSize, divider, sxValue }) => ( <Box sx={{ display: hidden ? 'none' : 'block' }}> - <Stack direction="row" justifyContent="space-between"> + <Stack direction="row" justifyContent="space-between" alignItems="center"> <Typography fontSize="smaller" fontWeight={fontWeight} sx={{ color: 'text.primary', fontSize: 14 }}> {label} </Typography> {value && ( - <Typography fontSize="smaller" fontWeight={fontWeight} sx={{ color: 'text.primary', fontSize: 14, ...sxValue }}> + <Typography + fontSize="smaller" + fontWeight={fontWeight} + sx={{ color: 'text.primary', fontSize: fontSize || 14, ...sxValue }} + > {value} </Typography> )} diff --git a/nym-wallet/src/hooks/useGetBalance.ts b/nym-wallet/src/hooks/useGetBalance.ts index 0da3ca7476..240910e493 100644 --- a/nym-wallet/src/hooks/useGetBalance.ts +++ b/nym-wallet/src/hooks/useGetBalance.ts @@ -9,11 +9,19 @@ import { getOriginalVesting, getCurrentVestingPeriod, getVestingAccountInfo, + getSpendableRewardCoins, + getSpendableVestedCoins, } from '../requests'; import { Console } from '../utils/console'; type TTokenAllocation = { - [key in 'vesting' | 'vested' | 'locked' | 'spendable']: DecCoin['amount']; + [key in + | 'vesting' + | 'vested' + | 'locked' + | 'spendable' + | 'spendableRewardCoins' + | 'spendableVestedCoins']: DecCoin['amount']; }; export type TUseuserBalance = { @@ -54,6 +62,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { vestedCoins, lockedCoins, spendableCoins, + spendableVestedCoins, + spendableRewardCoins, currentPeriod, vestingAccountDetail, ] = await Promise.all([ @@ -62,6 +72,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { getVestedCoins(clientDetails?.client_address), getLockedCoins(), getSpendableCoins(), + getSpendableVestedCoins(), + getSpendableRewardCoins(), getCurrentVestingPeriod(clientDetails?.client_address), getVestingAccountInfo(clientDetails?.client_address), ]); @@ -72,6 +84,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { vested: vestedCoins.amount, locked: lockedCoins.amount, spendable: spendableCoins.amount, + spendableVestedCoins: spendableVestedCoins.amount, + spendableRewardCoins: spendableRewardCoins.amount, }); setVestingAccountInfo(vestingAccountDetail); } catch (e) { diff --git a/nym-wallet/src/pages/balance/Balance.tsx b/nym-wallet/src/pages/balance/Balance.tsx new file mode 100644 index 0000000000..1125076ee5 --- /dev/null +++ b/nym-wallet/src/pages/balance/Balance.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Alert, Grid, Typography } from '@mui/material'; +import { Link } from '@nymproject/react/link/Link'; +import { Network } from 'src/types'; +import { Balance } from '@nymproject/types'; +import { NymCard, ClientAddress } from '../../components'; +import { urls } from '../../context/main'; + +export const BalanceCard = ({ + userBalance, + userBalanceError, + network, + clientAddress, +}: { + userBalance?: Balance; + userBalanceError?: string; + network?: Network; + clientAddress?: string; +}) => ( + <NymCard title="Balance" data-testid="check-balance" borderless Action={<ClientAddress withCopy showEntireAddress />}> + <Grid container direction="column" spacing={2}> + <Grid item> + {userBalanceError && ( + <Alert severity="error" data-testid="error-refresh" sx={{ p: 2 }}> + {userBalanceError} + </Alert> + )} + {!userBalanceError && ( + <Typography + data-testid="refresh-success" + sx={{ + color: 'text.primary', + textTransform: 'uppercase', + fontWeight: '600', + fontSize: 28, + }} + variant="h5" + > + {userBalance?.printable_balance} + </Typography> + )} + </Grid> + {network && ( + <Grid item> + <Link + href={`${urls(network).mixnetExplorer}/account/${clientAddress}`} + target="_blank" + text="Last transactions" + fontSize={14} + /> + </Grid> + )} + </Grid> + </NymCard> +); diff --git a/nym-wallet/src/pages/balance/Vesting.tsx b/nym-wallet/src/pages/balance/Vesting.tsx new file mode 100644 index 0000000000..23e018438c --- /dev/null +++ b/nym-wallet/src/pages/balance/Vesting.tsx @@ -0,0 +1,77 @@ +import React, { useEffect } from 'react'; +import { Refresh } from '@mui/icons-material'; +import { Grid, IconButton, Typography } from '@mui/material'; +import { useSnackbar } from 'notistack'; +import { NymCard } from 'src/components'; +import { TokenTransfer } from 'src/components/Balance/cards/TokenTransfer'; +import { OriginalVestingResponse } from '@nymproject/types'; +import { VestingSchedule } from 'src/components/Balance/cards/VestingSchedule'; + +export const VestingCard = ({ + unlockedTokens, + unlockedRewards, + unlockedTransferable, + originalVesting, + onTransfer, + fetchBalance, + fetchTokenAllocation, +}: { + unlockedTokens?: string; + unlockedRewards?: string; + unlockedTransferable?: string; + originalVesting?: OriginalVestingResponse; + fetchTokenAllocation: () => Promise<void>; + fetchBalance: () => Promise<void>; + onTransfer: () => Promise<void>; +}) => { + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + + const refreshBalances = async () => { + await fetchBalance(); + await fetchTokenAllocation(); + }; + + useEffect(() => { + closeSnackbar(); + fetchTokenAllocation(); + }, []); + + if (!originalVesting) return null; + + return ( + <NymCard + title="Vesting Schedule" + subheader={ + <Typography variant="caption" sx={{ color: 'nym.text.muted' }}> + You can use up to 10% of your locked tokens for bonding and delegating + </Typography> + } + borderless + data-testid="check-unvested-tokens" + Action={ + <IconButton + onClick={async () => { + await refreshBalances(); + enqueueSnackbar('Balances updated', { variant: 'success', preventDuplicate: true }); + }} + > + <Refresh /> + </IconButton> + } + > + <Grid container spacing={3}> + <Grid item xs={12} xl={8}> + <VestingSchedule /> + </Grid> + <Grid item xs={12} xl={4}> + <TokenTransfer + onTransfer={onTransfer} + unlockedTokens={unlockedTokens} + unlockedRewards={unlockedRewards} + unlockedTransferable={unlockedTransferable} + /> + </Grid> + </Grid> + </NymCard> + ); +}; diff --git a/nym-wallet/src/pages/balance/balance.tsx b/nym-wallet/src/pages/balance/balance.tsx deleted file mode 100644 index 12183ef987..0000000000 --- a/nym-wallet/src/pages/balance/balance.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useContext, useEffect } from 'react'; -import { Alert, Grid, Typography } from '@mui/material'; -import { Link } from '@nymproject/react/link/Link'; -import { NymCard, ClientAddress } from '../../components'; -import { AppContext, urls } from '../../context/main'; - -export const BalanceCard = () => { - const { userBalance, clientDetails, network } = useContext(AppContext); - - useEffect(() => { - userBalance.fetchBalance(); - }, []); - - return ( - <NymCard - title="Balance" - data-testid="check-balance" - borderless - Action={<ClientAddress withCopy showEntireAddress />} - > - <Grid container direction="column" spacing={2}> - <Grid item> - {userBalance.error && ( - <Alert severity="error" data-testid="error-refresh" sx={{ p: 2 }}> - {userBalance.error} - </Alert> - )} - {!userBalance.error && ( - <Typography - data-testid="refresh-success" - sx={{ - color: 'text.primary', - textTransform: 'uppercase', - fontWeight: '600', - fontSize: 28, - }} - variant="h5" - > - {userBalance.balance?.printable_balance} - </Typography> - )} - </Grid> - {network && ( - <Grid item> - <Link - href={`${urls(network).mixnetExplorer}/account/${clientDetails?.client_address}`} - target="_blank" - text="Last transactions" - fontSize={14} - /> - </Grid> - )} - </Grid> - </NymCard> - ); -}; diff --git a/nym-wallet/src/pages/balance/index.tsx b/nym-wallet/src/pages/balance/index.tsx index e214d1bddf..89767f370e 100644 --- a/nym-wallet/src/pages/balance/index.tsx +++ b/nym-wallet/src/pages/balance/index.tsx @@ -1,27 +1,46 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Box } from '@mui/material'; import { AppContext } from '../../context/main'; -import { BalanceCard } from './balance'; -import { VestingCard } from './vesting'; +import { BalanceCard } from './Balance'; +import { VestingCard } from './Vesting'; import { PageLayout } from '../../layouts'; -import { TransferModal } from './components/TransferModal'; +import { TransferModal } from '../../components/Balance/modals/TransferModal'; export const Balance = () => { const [showTransferModal, setShowTransferModal] = useState(false); - const { userBalance } = useContext(AppContext); + const { userBalance, clientDetails, network } = useContext(AppContext); + + useEffect(() => { + userBalance.fetchBalance(); + }, []); const handleShowTransferModal = async () => { await userBalance.refreshBalances(); setShowTransferModal(true); }; + const appendDenom = (value: string = '') => `${value} ${clientDetails?.display_mix_denom.toUpperCase()}`; + return ( <PageLayout> <Box display="flex" flexDirection="column" gap={4}> - <BalanceCard /> - <VestingCard onTransfer={handleShowTransferModal} /> + <BalanceCard + userBalance={userBalance.balance} + userBalanceError={userBalance.error} + clientAddress={clientDetails?.client_address} + network={network} + /> + <VestingCard + unlockedTokens={appendDenom(userBalance.tokenAllocation?.spendableVestedCoins)} + unlockedRewards={appendDenom(userBalance.tokenAllocation?.spendableRewardCoins)} + unlockedTransferable={appendDenom(userBalance.tokenAllocation?.spendable)} + originalVesting={userBalance.originalVesting} + onTransfer={handleShowTransferModal} + fetchBalance={userBalance.fetchBalance} + fetchTokenAllocation={userBalance.fetchTokenAllocation} + /> {showTransferModal && <TransferModal onClose={() => setShowTransferModal(false)} />} </Box> </PageLayout> diff --git a/nym-wallet/src/pages/balance/types.ts b/nym-wallet/src/pages/balance/types.ts index 5f88fe60b1..4c4ee67403 100644 --- a/nym-wallet/src/pages/balance/types.ts +++ b/nym-wallet/src/pages/balance/types.ts @@ -1,2 +1 @@ export type TResponseState = 'loading' | 'success' | 'fail'; -export type TTransactionDetails = { amount: string; url: string }; diff --git a/nym-wallet/src/pages/balance/vesting.tsx b/nym-wallet/src/pages/balance/vesting.tsx deleted file mode 100644 index d84accf898..0000000000 --- a/nym-wallet/src/pages/balance/vesting.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { Refresh } from '@mui/icons-material'; -import { - Box, - Button, - IconButton, - Table, - TableBody, - TableCell, - TableCellProps, - TableContainer, - TableHead, - TableRow, - Typography, -} from '@mui/material'; -import { useSnackbar } from 'notistack'; -import { NymCard } from 'src/components'; -import { AppContext } from 'src/context/main'; -import { Period } from 'src/types'; -import { VestingTimeline } from './components/vesting-timeline'; - -const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [ - { title: 'Locked', align: 'left' }, - { title: 'Period', align: 'left' }, - { title: 'Percentage Vested', align: 'left' }, - { title: 'Unlocked', align: 'right' }, -]; - -const vestingPeriod = (current?: Period, original?: number) => { - if (current === 'After') return 'Complete'; - - if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`; - - return 'N/A'; -}; - -const VestingSchedule = () => { - const { userBalance, clientDetails } = useContext(AppContext); - const [vestedPercentage, setVestedPercentage] = useState(0); - - const calculatePercentage = () => { - const { tokenAllocation, originalVesting } = userBalance; - if (tokenAllocation?.vesting && tokenAllocation.vested && tokenAllocation.vested !== '0' && originalVesting) { - const percentage = (+tokenAllocation.vested / +originalVesting.amount.amount) * 100; - const rounded = percentage.toFixed(2); - setVestedPercentage(+rounded); - } else { - setVestedPercentage(0); - } - }; - - useEffect(() => { - calculatePercentage(); - }, [userBalance.tokenAllocation, calculatePercentage]); - - return ( - <TableContainer sx={{ py: 1 }}> - <Table> - <TableHead> - <TableRow> - {columnsHeaders.map((header) => ( - <TableCell key={header.title} sx={{ color: (t) => t.palette.nym.text.muted }} align={header.align}> - {header.title} - </TableCell> - ))} - </TableRow> - </TableHead> - <TableBody> - <TableRow> - <TableCell - sx={{ - color: 'text.primary', - borderBottom: 'none', - textTransform: 'uppercase', - }} - > - {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} - {clientDetails?.display_mix_denom.toUpperCase()} - </TableCell> - <TableCell - align="left" - sx={{ - color: 'text.primary', - borderBottom: 'none', - }} - > - {vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)} - </TableCell> - <TableCell - sx={{ - color: 'text.primary', - borderBottom: 'none', - }} - > - <Box display="flex" alignItems="center" gap={1}> - <Typography variant="body2">{`${vestedPercentage}%`}</Typography> - <VestingTimeline percentageComplete={vestedPercentage} /> - </Box> - </TableCell> - <TableCell - sx={{ - color: 'text.primary', - borderBottom: 'none', - textTransform: 'uppercase', - }} - align="right" - > - {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} - {clientDetails?.display_mix_denom.toUpperCase()} - </TableCell> - </TableRow> - </TableBody> - </Table> - </TableContainer> - ); -}; - -const TokenTransfer = () => { - const { userBalance, clientDetails } = useContext(AppContext); - - return ( - <Box sx={{ my: 3 }}> - <Typography variant="subtitle2" sx={{ mb: 3, fontWeight: '600' }}> - Unlocked transferable tokens - </Typography> - - <Typography - data-testid="refresh-success" - sx={{ color: 'text.primary', fontWeight: '600', fontSize: 28 }} - variant="h5" - textTransform="uppercase" - > - {userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()} - </Typography> - </Box> - ); -}; - -export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise<void> }) => { - const { userBalance } = useContext(AppContext); - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - - const refreshBalances = async () => { - await userBalance.fetchBalance(); - await userBalance.fetchTokenAllocation(); - }; - - useEffect(() => { - closeSnackbar(); - userBalance.fetchTokenAllocation(); - }, []); - - if (!userBalance.originalVesting) return null; - - return ( - <NymCard - title="Vesting Schedule" - subheader={ - <Typography variant="caption" sx={{ color: 'nym.text.muted' }}> - You can use up to 10% of your locked tokens for bonding and delegating - </Typography> - } - borderless - data-testid="check-unvested-tokens" - Action={ - <IconButton - onClick={async () => { - await refreshBalances(); - enqueueSnackbar('Balances updated', { variant: 'success', preventDuplicate: true }); - }} - > - <Refresh /> - </IconButton> - } - > - <VestingSchedule /> - <TokenTransfer /> - <Box display="flex" justifyContent="end" alignItems="center"> - <Button size="large" variant="contained" onClick={onTransfer} disableElevation> - Transfer - </Button> - </Box> - </NymCard> - ); -}; diff --git a/nym-wallet/src/requests/vesting.ts b/nym-wallet/src/requests/vesting.ts index 993910dd14..4c64e3f63d 100644 --- a/nym-wallet/src/requests/vesting.ts +++ b/nym-wallet/src/requests/vesting.ts @@ -18,6 +18,10 @@ export const getLockedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoi export const getSpendableCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_coins'); +export const getSpendableVestedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_vested_coins'); + +export const getSpendableRewardCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_reward_coins'); + export const getVestingCoins = async (vestingAccountAddress: string): Promise<DecCoin> => invokeWrapper<DecCoin>('vesting_coins', { vestingAccountAddress }); diff --git a/service-providers/common/Cargo.toml b/service-providers/common/Cargo.toml index e180849ccb..887526563a 100644 --- a/service-providers/common/Cargo.toml +++ b/service-providers/common/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "service-providers-common" +name = "nym-service-providers-common" version = "0.1.0" edition = "2021" diff --git a/service-providers/common/examples/control_requests.rs b/service-providers/common/examples/control_requests.rs index 5cf418da13..37f472d9f0 100644 --- a/service-providers/common/examples/control_requests.rs +++ b/service-providers/common/examples/control_requests.rs @@ -4,7 +4,7 @@ // use nym_client::client::config::{BaseConfig, Config, GatewayEndpointConfig}; // use nym_client::client::{DirectClient, KeyManager, Recipient, ReconstructedMessage, SocketClient}; use nym_sdk::mixnet::{IncludedSurbs, MixnetClient, Recipient, ReconstructedMessage}; -use service_providers_common::interface::{ +use nym_service_providers_common::interface::{ ControlRequest, ControlResponse, ProviderInterfaceVersion, Request, Response, ResponseContent, }; diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 6a779787cd..88f8502564 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-network-requester" -version = "1.1.12" +version = "1.1.13" authors.workspace = true edition.workspace = true rust-version = "1.65" @@ -38,10 +38,10 @@ nym-bin-common = { path = "../../common/bin-common"} nym-network-defaults = { path = "../../common/network-defaults" } nym-sdk = { path = "../../sdk/rust/nym-sdk" } nym-sphinx = { path = "../../common/nymsphinx" } -ordered-buffer = {path = "../../common/socks5/ordered-buffer"} -proxy-helpers = { path = "../../common/socks5/proxy-helpers" } -service-providers-common = { path = "../common" } -socks5-requests = { path = "../../common/socks5/requests" } +nym-ordered-buffer = {path = "../../common/socks5/ordered-buffer"} +nym-socks5-proxy-helpers = { path = "../../common/socks5/proxy-helpers" } +nym-service-providers-common = { path = "../common" } +nym-socks5-requests = { path = "../../common/socks5/requests" } nym-statistics-common = { path = "../../common/statistics" } nym-task = { path = "../../common/task" } websocket-requests = { path = "../../clients/native/websocket-requests" } diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index 7ebf5c186a..2ce61e6c62 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -12,21 +12,23 @@ use async_trait::async_trait; use futures::channel::mpsc; use log::warn; use nym_bin_common::build_information::BinaryBuildInformation; +use nym_service_providers_common::interface::{ + BinaryInformation, ProviderInterfaceVersion, Request, RequestVersion, +}; +use nym_service_providers_common::ServiceProvider; +use nym_socks5_proxy_helpers::connection_controller::{ + Controller, ControllerCommand, ControllerSender, +}; +use nym_socks5_proxy_helpers::proxy_runner::{MixProxyReader, MixProxySender}; +use nym_socks5_requests::{ + ConnectRequest, ConnectionId, NetworkData, SendRequest, Socks5ProtocolVersion, + Socks5ProviderRequest, Socks5Request, Socks5RequestContent, Socks5Response, +}; use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_statistics_common::collector::StatisticsSender; use nym_task::connections::LaneQueueLengths; use nym_task::{TaskClient, TaskManager}; -use proxy_helpers::connection_controller::{Controller, ControllerCommand, ControllerSender}; -use proxy_helpers::proxy_runner::{MixProxyReader, MixProxySender}; -use service_providers_common::interface::{ - BinaryInformation, ProviderInterfaceVersion, Request, RequestVersion, -}; -use service_providers_common::ServiceProvider; -use socks5_requests::{ - ConnectRequest, ConnectionId, NetworkData, SendRequest, Socks5ProtocolVersion, - Socks5ProviderRequest, Socks5Request, Socks5RequestContent, Socks5Response, -}; use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering}; diff --git a/service-providers/network-requester/src/error.rs b/service-providers/network-requester/src/error.rs index 251c19d4c9..e303cb3be2 100644 --- a/service-providers/network-requester/src/error.rs +++ b/service-providers/network-requester/src/error.rs @@ -1,5 +1,5 @@ use client_core::error::ClientCoreError; -use socks5_requests::Socks5RequestError; +use nym_socks5_requests::Socks5RequestError; #[derive(thiserror::Error, Debug)] pub enum NetworkRequesterError { diff --git a/service-providers/network-requester/src/reply.rs b/service-providers/network-requester/src/reply.rs index 6f49263694..6fe313e30b 100644 --- a/service-providers/network-requester/src/reply.rs +++ b/service-providers/network-requester/src/reply.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use nym_sdk::mixnet::InputMessage; -use nym_sphinx::addressing::clients::Recipient; -use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; -use nym_task::connections::TransmissionLane; -use service_providers_common::interface::{ +use nym_service_providers_common::interface::{ ControlRequest, ControlResponse, ProviderInterfaceVersion, RequestVersion, }; -use socks5_requests::{ +use nym_socks5_requests::{ ConnectionId, NetworkData, Socks5ProviderRequest, Socks5ProviderResponse, Socks5Request, Socks5RequestContent, Socks5Response, Socks5ResponseContent, }; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_task::connections::TransmissionLane; use std::fmt::{Debug, Formatter}; /// Generic data this service provider will send back to the mixnet via its connected native client. diff --git a/service-providers/network-requester/src/socks5/tcp.rs b/service-providers/network-requester/src/socks5/tcp.rs index 32c6a8c241..18281b0fb7 100644 --- a/service-providers/network-requester/src/socks5/tcp.rs +++ b/service-providers/network-requester/src/socks5/tcp.rs @@ -3,12 +3,12 @@ use crate::reply; use crate::reply::MixnetMessage; +use nym_service_providers_common::interface::RequestVersion; +use nym_socks5_proxy_helpers::connection_controller::ConnectionReceiver; +use nym_socks5_proxy_helpers::proxy_runner::{MixProxySender, ProxyRunner}; +use nym_socks5_requests::{ConnectionId, RemoteAddress, Socks5Request}; use nym_task::connections::LaneQueueLengths; use nym_task::TaskClient; -use proxy_helpers::connection_controller::ConnectionReceiver; -use proxy_helpers::proxy_runner::{MixProxySender, ProxyRunner}; -use service_providers_common::interface::RequestVersion; -use socks5_requests::{ConnectionId, RemoteAddress, Socks5Request}; use std::io; use tokio::net::TcpStream; diff --git a/service-providers/network-requester/src/statistics/collector.rs b/service-providers/network-requester/src/statistics/collector.rs index 9ccb18f01a..d728db8df6 100644 --- a/service-providers/network-requester/src/statistics/collector.rs +++ b/service-providers/network-requester/src/statistics/collector.rs @@ -6,6 +6,10 @@ use crate::core::new_legacy_request_version; use crate::reply::MixnetMessage; use async_trait::async_trait; use log::*; +use nym_ordered_buffer::OrderedMessageSender; +use nym_service_providers_common::interface::RequestVersion; +use nym_socks5_proxy_helpers::proxy_runner::MixProxySender; +use nym_socks5_requests::{ConnectionId, RemoteAddress, Socks5Request, Socks5RequestContent}; use nym_sphinx::addressing::clients::Recipient; use nym_statistics_common::api::{ build_statistics_request_bytes, DEFAULT_STATISTICS_SERVICE_ADDRESS, @@ -15,12 +19,8 @@ use nym_statistics_common::{ collector::StatisticsCollector, error::StatsError as CommonStatsError, StatsMessage, StatsServiceData, }; -use ordered_buffer::OrderedMessageSender; -use proxy_helpers::proxy_runner::MixProxySender; use rand::RngCore; use serde::Deserialize; -use service_providers_common::interface::RequestVersion; -use socks5_requests::{ConnectionId, RemoteAddress, Socks5Request, Socks5RequestContent}; use sqlx::types::chrono::{DateTime, Utc}; use std::collections::{HashMap, HashSet}; use std::sync::Arc; diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index 929c8fda8e..ee6cb15703 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-statistics" -version = "1.1.12" +version = "1.1.13" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index 5684ff3a5e..4b796278e7 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.12" +version = "1.1.13" authors.workspace = true edition = "2021" @@ -10,7 +10,7 @@ bs58 = "0.4" clap = { version = "4.0", features = ["derive"] } clap_complete = "4.0" clap_complete_fig = "4.0" -dotenv = "0.15.0" +dotenvy = "0.15.6" log = { workspace = true } pretty_env_logger = "0.4" serde = { version = "1", features = ["derive"] } diff --git a/ts-packages/mock-nym-api/.env b/ts-packages/mock-nym-api/.env new file mode 100644 index 0000000000..2b528d6467 --- /dev/null +++ b/ts-packages/mock-nym-api/.env @@ -0,0 +1 @@ +NYM_API_URL=https://validator.nymtech.net diff --git a/ts-packages/mock-nym-api/Caddyfile b/ts-packages/mock-nym-api/Caddyfile new file mode 100644 index 0000000000..097a0b9fbb --- /dev/null +++ b/ts-packages/mock-nym-api/Caddyfile @@ -0,0 +1,4 @@ +localhost:8001 { + reverse_proxy 127.0.0.1:8000 + tls internal +} diff --git a/ts-packages/mock-nym-api/README.md b/ts-packages/mock-nym-api/README.md new file mode 100644 index 0000000000..8936d8ff7b --- /dev/null +++ b/ts-packages/mock-nym-api/README.md @@ -0,0 +1,63 @@ +# NYM API mock + +This package provides a mock server that allows you to modify parts of the Nym API by: + +- modifying live responses from a running Nym API +- providing a static response + +## How to run it? + +Adjust the [.env](./.env) to use the Nym API you want to proxy. The defaults are for mainnet. + +From this directory run: + +``` +yarn +yarn start +``` + +When you modify files in `src` and `mocks` the process will be restarted automatically. + +## How can I find out what methods I can override? + +Look in the swagger docs for the Nym API, e.g. https://validator.nymtech.net/api/swagger/index.html. + +Then write a handler to override it, for example, to return custom gateways: + +```ts +app.get('/api/v1/gateways', (req, res) => { + const customGateways = JSON.parse(fs.readFileSync('./mocks/custom-gateway.json').toString()); + + // modify custom gateway + customGateways[0].gateway.sphinx_key += '-ccc'; + customGateways[0].gateway.identity_key += '-ddd'; + + res.json(customGateways); +}); +``` + +## How to get seed data? + +You can get seed data from a running Nym API with: + +``` +curl https://validator.nymtech.net/api/v1/mixnodes | jq . > mocks/mixnodes.json +``` + +If you don't have `jq` installed, you can just do `curl https://validator.nymtech.net/api/v1/mixnodes > mocks/mixnodes.json` to store the unformatted response. + +## HTTPS + +If you need HTTPS then install `caddy` (see https://caddyserver.com/ or `brew install caddy` on MacOS) and run: + +``` +yarn +yarn start:https +``` + +The API will available on `https://localhost:8001` e.g. https://localhost:8001/api/v1/mixnodes with a self-signed certificate. + +Modify the [Caddyfile](Caddyfile) to set the domain for the certificate (NB: you will need to set up the DNS and control the domain records). + +If you need the local certificate to be valid, look at https://github.com/FiloSottile/mkcert and https://dev.to/josuebustos/https-localhost-for-node-js-1p1k. + diff --git a/ts-packages/mock-nym-api/mocks/custom-gateway.json b/ts-packages/mock-nym-api/mocks/custom-gateway.json new file mode 100644 index 0000000000..f1eaf99514 --- /dev/null +++ b/ts-packages/mock-nym-api/mocks/custom-gateway.json @@ -0,0 +1,20 @@ +[ + { + "pledge_amount": { + "denom": "unym", + "amount": "100000000" + }, + "owner": "n1...", + "block_height": 5900000, + "gateway": { + "host": "127.0.0.1", + "mix_port": 1789, + "clients_port": 9000, + "location": "My local computer", + "sphinx_key": "aaa", + "identity_key": "bbb", + "version": "1.1.11" + }, + "proxy": null + } +] diff --git a/ts-packages/mock-nym-api/mocks/gateways.json b/ts-packages/mock-nym-api/mocks/gateways.json new file mode 100644 index 0000000000..ed7945bbe4 --- /dev/null +++ b/ts-packages/mock-nym-api/mocks/gateways.json @@ -0,0 +1,20 @@ +[ + { + "pledge_amount": { + "denom": "unym", + "amount": "100000000" + }, + "owner": "n1ecaqmk4ajcc80mtgpvvqf6zun7eq8k64uz6f40", + "block_height": 5908500, + "gateway": { + "host": "194.182.191.207", + "mix_port": 1789, + "clients_port": 9000, + "location": "Switzerland", + "sphinx_key": "2az9QVu6o4ZEVe72HQaxcDEdSFSxXiFvH3gWiU33Vrmn", + "identity_key": "2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh", + "version": "1.1.11" + }, + "proxy": null + } +] diff --git a/ts-packages/mock-nym-api/mocks/mixnodes.json b/ts-packages/mock-nym-api/mocks/mixnodes.json new file mode 100644 index 0000000000..9afcf3b4ac --- /dev/null +++ b/ts-packages/mock-nym-api/mocks/mixnodes.json @@ -0,0 +1,116 @@ +[ + { + "bond_information": { + "mix_id": 1, + "owner": "n1cg6lgel27zmq8yutxgnu2tdzx3hf0m8uemfpwp", + "original_pledge": { + "denom": "unym", + "amount": "164186215" + }, + "layer": 2, + "mix_node": { + "host": "116.203.115.86", + "mix_port": 1789, + "verloc_port": 1790, + "http_api_port": 8000, + "sphinx_key": "82MfLZ4NtBmuZXL45XC1x9HNwsK3HqW7iNNpd4YdgTdY", + "identity_key": "12Vbu59dESdcQEfPKvbqt3r6XAhvn5xAiLB3Ke4L42Nn", + "version": "1.1.4" + }, + "proxy": null, + "bonding_height": 2622578, + "is_unbonding": false + }, + "rewarding_details": { + "cost_params": { + "profit_margin_percent": "0.05", + "interval_operating_cost": { + "denom": "unym", + "amount": "40000000" + } + }, + "operator": "177630956.71827075154157932", + "delegates": "2002077254.831772380804187632", + "total_unit_reward": "923810.444666272288950945", + "unit_delegation": "1000000000", + "last_rewarded_epoch": 2580, + "unique_delegations": 2 + } + }, + { + "bond_information": { + "mix_id": 2, + "owner": "n1vfdtzh9vzaaxxv9n9w4ywdzle9m2pejhx0d5r7", + "original_pledge": { + "denom": "unym", + "amount": "1691956412" + }, + "layer": 1, + "mix_node": { + "host": "95.179.226.75", + "mix_port": 1789, + "verloc_port": 1790, + "http_api_port": 8000, + "sphinx_key": "HomUqYe7hyC1cBNbMUKWguLfq7aFdoxoQDyQsa2fGQa1", + "identity_key": "14Qzy15U7uA8pVwvs66MuRvABSEK5C62JaDYD18WbkN7", + "version": "1.1.10" + }, + "proxy": null, + "bonding_height": 2944467, + "is_unbonding": false + }, + "rewarding_details": { + "cost_params": { + "profit_margin_percent": "0.05", + "interval_operating_cost": { + "denom": "unym", + "amount": "500000000" + } + }, + "operator": "1774161955.223791723839539477", + "delegates": "637943739106.672292940395867727", + "total_unit_reward": "67206510.581661315274414946", + "unit_delegation": "1000000000", + "last_rewarded_epoch": 2583, + "unique_delegations": 31 + } + }, + { + "bond_information": { + "mix_id": 5, + "owner": "n1v7nrjw6pnkrdxmmuk7pjtxrzrxrtztgh33pszu", + "original_pledge": { + "denom": "unym", + "amount": "28752403445" + }, + "layer": 3, + "mix_node": { + "host": "194.163.191.29", + "mix_port": 1789, + "verloc_port": 1790, + "http_api_port": 8000, + "sphinx_key": "2BjwQw2txBofh7QvRAt3d8mUDLZHZgwTm3cuxpkngiWX", + "identity_key": "26WHDRFM5QU7whg1hnxKk4iVY7DnXyZknPcNMitX6GXp", + "version": "1.1.10" + }, + "proxy": null, + "bonding_height": 2935258, + "is_unbonding": false + }, + "rewarding_details": { + "cost_params": { + "profit_margin_percent": "0.05", + "interval_operating_cost": { + "denom": "unym", + "amount": "40000000" + } + }, + "operator": "31540882828.329440044410277309", + "delegates": "700258477245.036486032804830664", + "total_unit_reward": "73511253.913602285045142911", + "unit_delegation": "1000000000", + "last_rewarded_epoch": 2583, + "unique_delegations": 126 + } + } +] diff --git a/ts-packages/mock-nym-api/package.json b/ts-packages/mock-nym-api/package.json new file mode 100644 index 0000000000..a712fb99b9 --- /dev/null +++ b/ts-packages/mock-nym-api/package.json @@ -0,0 +1,49 @@ +{ + "name": "mock-nym-api", + "version": "1.0.0", + "license": "Apache-2.0", + "main": "index.js", + "type": "module", + "dependencies": { + "express": "^4.18.2", + "http-proxy-middleware": "^2.0.6", + "dotenv": "^16.0.3" + }, + "devDependencies": { + "@babel/core": "^7.17.5", + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.13.0", + "babel-loader": "^8.2.3", + "babel-plugin-root-import": "^5.1.0", + "eslint": "^8.10.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^16.1.0", + "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-root-import": "^1.0.4", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^26.1.1", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.29.2", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-storybook": "^0.5.12", + "jest": "^27.1.0", + "prettier": "^2.5.1", + "rimraf": "^3.0.2", + "ts-jest": "^27.0.5", + "typescript": "^4.6.2", + "ts-node": "10", + "nodemon": "^2.0.21", + "npm-run-all": "^4.1.5" + }, + "scripts": { + "clean": "rimraf dist", + "start": "nodemon --watch src --watch mocks -e ts,json --exec 'ts-node --esm' src/index.ts", + "start:https": "run-p start run:caddy", + "run:caddy": "caddy run", + "build": "tsc --noEmit false", + "watch": "tsc --noEmit false -w", + "lint": "eslint src", + "lint:fix": "eslint src --fix" + } +} diff --git a/ts-packages/mock-nym-api/src/index.ts b/ts-packages/mock-nym-api/src/index.ts new file mode 100644 index 0000000000..89ab9ad245 --- /dev/null +++ b/ts-packages/mock-nym-api/src/index.ts @@ -0,0 +1,48 @@ +import express from 'express'; +import dotenv from 'dotenv'; +import { createProxyMiddleware } from 'http-proxy-middleware'; +import fs from 'fs'; + +dotenv.config(); + +const app = express(); +const port = process.env.PORT || 8000; + +const { NYM_API_URL } = process.env; + +if (!NYM_API_URL || NYM_API_URL.trim().length < 1) { + throw new Error('Please specify a valid NYM_API_URL in `.env` or as an environment variable'); +} + +// proxy the Nym API and only override some routes +const proxy = createProxyMiddleware(['/api/**', '/swagger/**'], { + target: NYM_API_URL, + changeOrigin: true, +}); + +/** + * Return a single custom gateway, from a static file and modify some the fields + */ +app.get('/api/v1/gateways', (req, res) => { + const customGateways = JSON.parse(fs.readFileSync('./mocks/custom-gateway.json').toString()); + + // modify custom gateway + customGateways[0].gateway.sphinx_key += '-ccc'; + customGateways[0].gateway.identity_key += '-ddd'; + + res.json(customGateways); +}); + +/** + * Returns only 3 mixnodes from a static file + */ +app.get('/api/v1/mixnodes', (req, res) => { + const customMixnodes = JSON.parse(fs.readFileSync('./mocks/mixnodes.json').toString()); + res.json(customMixnodes); +}); + +// start the Express server +app.use(proxy); +app.listen(port, () => { + console.log(`[API] Nym API mock is running at http://localhost:${port} and proxying ${NYM_API_URL}`); +}); diff --git a/ts-packages/mock-nym-api/tsconfig.json b/ts-packages/mock-nym-api/tsconfig.json new file mode 100644 index 0000000000..47d23cadec --- /dev/null +++ b/ts-packages/mock-nym-api/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "noEmit": true + }, + "include": [ + "src/**/*.ts", + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/ts-packages/types/src/types/global.ts b/ts-packages/types/src/types/global.ts index 1ec545138c..c949456494 100644 --- a/ts-packages/types/src/types/global.ts +++ b/ts-packages/types/src/types/global.ts @@ -1,4 +1,4 @@ -import { MixNode, DecCoin } from './rust'; +import { MixNode, DecCoin, GatewayBond, MixNodeBond, MixNodeDetails, Delegation, Coin, UnbondedMixnode } from './rust'; export type TNodeType = 'mixnode' | 'gateway'; @@ -29,3 +29,124 @@ export type TMixnodeBondDetails = { mix_node: MixNode; proxy: any; }; + +export type MixnetContractVersion = { + build_timestamp: string; + build_version: string; + commit_sha: string; + commit_timestamp: string; + commit_branch: string; + rustc_version: string; +}; + +export type PagedMixNodeBondResponse = { + nodes: MixNodeBond[]; + per_page: number; + start_next_after?: string; +}; + +export type PagedMixNodeDetailsResponse = { + nodes: MixNodeDetails[]; + per_page: number; + start_next_after?: string; +}; + +export type PagedGatewayResponse = { + nodes: GatewayBond[]; + per_page: number; + start_next_after?: string; +}; + +export type MixOwnershipResponse = { + address: string; + mixnode?: MixNodeBond; +}; + +export type GatewayOwnershipResponse = { + address: string; + gateway?: GatewayBond; +}; + +export type ContractStateParams = { + // ideally I'd want to define those as `number` rather than `string`, but + // rust-side they are defined as Uint128 and that don't have + // native javascript representations and therefore are interpreted as strings after deserialization + minimum_mixnode_pledge: string; + minimum_gateway_pledge: string; + mixnode_rewarded_set_size: number; + mixnode_active_set_size: number; +}; + +export type PagedMixDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: string; +}; + +export type PagedDelegatorDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: string; +}; + +export type PagedAllDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: [string, string]; +}; + +export type RewardingResult = { + operator_reward: string; + total_delegator_reward: string; +}; + +export type NodeRewardParams = { + period_reward_pool: string; + k: string; + reward_blockstamp: number; + circulating_supply: string; + uptime: string; + sybil_resistance_percent: number; +}; + +export type DelegatorRewardParams = { + node_reward_params: NodeRewardParams; + sigma: number; + profit_margin: number; + node_profit: number; +}; + +export type PendingDelegatorRewarding = { + running_results: RewardingResult; + next_start: string; + rewarding_params: DelegatorRewardParams; +}; + +export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding }; + +export type MixnodeRewardingStatusResponse = { + status?: RewardingStatus; +}; + +export type SendRequest = { + senderAddress: string; + recipientAddress: string; + transferAmount: readonly Coin[]; +}; + +export type UnbondedMixnodeResponse = [mix_id: number, unbonded_info?: UnbondedMixnode]; + +export type PagedUnbondedMixnodesResponse = { + nodes: UnbondedMixnodeResponse[]; + per_page: number; + start_next_after: string; +}; + +export type MappedCoin = { + readonly denom: string; + readonly fractionalDigits: number; +}; + +export type LayerDistribution = { + gateways: number; + layer1: number; + layer2: number; + layer3: number; +}; diff --git a/yarn.lock b/yarn.lock index 72e807cd97..2fa4ae7e18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1205,16 +1205,6 @@ "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@0.28.13": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.28.13.tgz#b51417a23c1ff8ef8b85a6862eba8492c6c44f38" - integrity sha512-IHnH2zGwaY69qT4mVAavr/pfzx6YE+ud1NHJbvVePlbGiz68CXTi5LHR+K0lrKB5mQ7E+ZErWz2mw5U/x+V1wQ== - dependencies: - "@cosmjs/crypto" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/utils" "0.28.13" - "@cosmjs/amino@^0.25.6": version "0.25.6" resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.25.6.tgz#cdf9632253bfab7b1d2ef967124953d7bf16351f" @@ -1225,6 +1215,16 @@ "@cosmjs/math" "^0.25.6" "@cosmjs/utils" "^0.25.6" +"@cosmjs/amino@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.5.tgz#053b4739a90b15b9e2b781ccd484faf64bd49aec" + integrity sha512-Qo8jpC0BiziTSUqpkNatBcwtKNhCovUnFul9SlT/74JUCdLYaeG5hxr3q1cssQt++l4LvlcpF+OUXL48XjNjLw== + dependencies: + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + "@cosmjs/cosmwasm-launchpad@^0.25.6": version "0.25.6" resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-launchpad/-/cosmwasm-launchpad-0.25.6.tgz#b03a96c8d5b5b4742ec4c56b4a01b1f4f33f7f57" @@ -1255,36 +1255,23 @@ pako "^2.0.2" protobufjs "~6.10.2" -"@cosmjs/cosmwasm-stargate@^0.28.0": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.13.tgz#bea77bc999aaafdb677f446465f648cd000c5b4a" - integrity sha512-dVZNOiRd8btQreRUabncGhVXGCS2wToXqxi9l3KEHwCJQ2RWTshuqV+EZAdCaYHE5W6823s2Ol2W/ukA9AXJPw== +"@cosmjs/cosmwasm-stargate@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.5.tgz#3f257da682658833e0f4eb9e8ff758e4d927663a" + integrity sha512-TNdSvm2tEE3XMCuxHxquzls56t40hC8qnLeYJWHsY2ECZmRK3KrnpRReEr7N7bLtODToK7X/riYrV0JaYxjrYA== dependencies: - "@cosmjs/amino" "0.28.13" - "@cosmjs/crypto" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/proto-signing" "0.28.13" - "@cosmjs/stargate" "0.28.13" - "@cosmjs/tendermint-rpc" "0.28.13" - "@cosmjs/utils" "0.28.13" - cosmjs-types "^0.4.0" + "@cosmjs/amino" "^0.29.5" + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/proto-signing" "^0.29.5" + "@cosmjs/stargate" "^0.29.5" + "@cosmjs/tendermint-rpc" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + cosmjs-types "^0.5.2" long "^4.0.0" pako "^2.0.2" -"@cosmjs/crypto@0.28.13", "@cosmjs/crypto@^0.28.0": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.28.13.tgz#541b6a36f616b2da5a568ead46d4e83841ceb412" - integrity sha512-ynKfM0q/tMBQMHJby6ad8lR3gkgBKaelQhIsCZTjClsnuC7oYT9y3ThSZCUWr7Pa9h0J8ahU2YV2oFWFVWJQzQ== - dependencies: - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/utils" "0.28.13" - "@noble/hashes" "^1" - bn.js "^5.2.0" - elliptic "^6.5.3" - libsodium-wrappers "^0.7.6" - "@cosmjs/crypto@^0.25.6": version "0.25.6" resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.25.6.tgz#695d2d0d2195bdbdd5825d415385646244900bbb" @@ -1301,14 +1288,18 @@ ripemd160 "^2.0.2" sha.js "^2.4.11" -"@cosmjs/encoding@0.28.13": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.28.13.tgz#7994e8e2c435beaf0690296ffb0f7f3eaec8150b" - integrity sha512-jtXbAYtV77rLHxoIrjGFsvgGjeTKttuHRv6cvuy3toCZzY7JzTclKH5O2g36IIE4lXwD9xwuhGJ2aa6A3dhNkA== +"@cosmjs/crypto@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.5.tgz#ab99fc382b93d8a8db075780cf07487a0f9519fd" + integrity sha512-2bKkaLGictaNL0UipQCL6C1afaisv6k8Wr/GCLx9FqiyFkh9ZgRHDyetD64ZsjnWV/N/D44s/esI+k6oPREaiQ== dependencies: - base64-js "^1.3.0" - bech32 "^1.1.4" - readonly-date "^1.0.0" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + "@noble/hashes" "^1" + bn.js "^5.2.0" + elliptic "^6.5.4" + libsodium-wrappers "^0.7.6" "@cosmjs/encoding@^0.25.6": version "0.25.6" @@ -1319,13 +1310,14 @@ bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/json-rpc@0.28.13": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.28.13.tgz#ff3f0c4a2f363b1a2c6779f8624a897e217fe297" - integrity sha512-fInSvg7x9P6p+GWqet+TMhrMTM3OWWdLJOGS5w2ryubMjgpR1rLiAx77MdTNkArW+/6sUwku0sN4veM4ENQu6A== +"@cosmjs/encoding@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.5.tgz#009a4b1c596cdfd326f30ccfa79f5e56daa264f2" + integrity sha512-G4rGl/Jg4dMCw5u6PEZHZcoHnUBlukZODHbm/wcL4Uu91fkn5jVo5cXXZcvs4VCkArVGrEj/52eUgTZCmOBGWQ== dependencies: - "@cosmjs/stream" "0.28.13" - xstream "^11.14.0" + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" "@cosmjs/json-rpc@^0.25.6": version "0.25.6" @@ -1335,6 +1327,14 @@ "@cosmjs/stream" "^0.25.6" xstream "^11.14.0" +"@cosmjs/json-rpc@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.5.tgz#5e483a9bd98a6270f935adf0dfd8a1e7eb777fe4" + integrity sha512-C78+X06l+r9xwdM1yFWIpGl03LhB9NdM1xvZpQHwgCOl0Ir/WV8pw48y3Ez2awAoUBRfTeejPe4KvrE6NoIi/w== + dependencies: + "@cosmjs/stream" "^0.29.5" + xstream "^11.14.0" + "@cosmjs/launchpad@^0.25.6": version "0.25.6" resolved "https://registry.yarnpkg.com/@cosmjs/launchpad/-/launchpad-0.25.6.tgz#c75f5d21be57af55fcb892f929520fa97f2d5bcc" @@ -1348,13 +1348,6 @@ axios "^0.21.1" fast-deep-equal "^3.1.3" -"@cosmjs/math@0.28.13", "@cosmjs/math@^0.28.0": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.28.13.tgz#50c05bc67007a04216f7f5e0c93f57270f8cc077" - integrity sha512-PDpL8W/kbyeWi0mQ2OruyqE8ZUAdxPs1xCbDX3WXJwy2oU+X2UTbkuweJHVpS9CIqmZulBoWQAmlf6t6zr1N/g== - dependencies: - bn.js "^5.2.0" - "@cosmjs/math@^0.25.5", "@cosmjs/math@^0.25.6": version "0.25.6" resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.25.6.tgz#25c7b106aaded889a5b80784693caa9e654b0c28" @@ -1369,18 +1362,12 @@ dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@0.28.13", "@cosmjs/proto-signing@^0.28.0": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.28.13.tgz#95ac12f0da0f0814f348f5ae996c3e96d015df61" - integrity sha512-nSl/2ZLsUJYz3Ad0RY3ihZUgRHIow2OnYqKsESMu+3RA/jTi9bDYhiBu8mNMHI0xrEJry918B2CyI56pOUHdPQ== +"@cosmjs/math@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.5.tgz#722c96e080d6c2b62215ce9f4c70da7625b241b6" + integrity sha512-2GjKcv+A9f86MAWYLUkjhw1/WpRl2R1BTb3m9qPG7lzMA7ioYff9jY5SPCfafKdxM4TIQGxXQlYGewQL16O68Q== dependencies: - "@cosmjs/amino" "0.28.13" - "@cosmjs/crypto" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/utils" "0.28.13" - cosmjs-types "^0.4.0" - long "^4.0.0" + bn.js "^5.2.0" "@cosmjs/proto-signing@^0.25.5", "@cosmjs/proto-signing@^0.25.6": version "0.25.6" @@ -1391,15 +1378,18 @@ long "^4.0.0" protobufjs "~6.10.2" -"@cosmjs/socket@0.28.13": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.28.13.tgz#d8443ad6e91d080fc6b80a7e9cf297a56b1f6833" - integrity sha512-lavwGxQ5VdeltyhpFtwCRVfxeWjH5D5mmN7jgx9nuCf3XSFbTcOYxrk2pQ4usenu1Q1KZdL4Yl5RCNrJuHD9Ug== +"@cosmjs/proto-signing@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.5.tgz#af3b62a46c2c2f1d2327d678b13b7262db1fe87c" + integrity sha512-QRrS7CiKaoETdgIqvi/7JC2qCwCR7lnWaUsTzh/XfRy3McLkEd+cXbKAW3cygykv7IN0VAEIhZd2lyIfT8KwNA== dependencies: - "@cosmjs/stream" "0.28.13" - isomorphic-ws "^4.0.1" - ws "^7" - xstream "^11.14.0" + "@cosmjs/amino" "^0.29.5" + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + cosmjs-types "^0.5.2" + long "^4.0.0" "@cosmjs/socket@^0.25.6": version "0.25.6" @@ -1411,22 +1401,14 @@ ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@0.28.13", "@cosmjs/stargate@^0.28.0": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.28.13.tgz#a73d837a46ee8944e6eafe162f2ff6943c14350e" - integrity sha512-dVBMazDz8/eActHsRcZjDHHptOBMqvibj5CFgEtZBp22gP6ASzoAUXTlkSVk5FBf4sfuUHoff6st134/+PGMAg== +"@cosmjs/socket@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.5.tgz#a48df6b4c45dc6a6ef8e47232725dd4aa556ac2d" + integrity sha512-5VYDupIWbIXq3ftPV1LkS5Ya/T7Ol/AzWVhNxZ79hPe/mBfv1bGau/LqIYOm2zxGlgm9hBHOTmWGqNYDwr9LNQ== dependencies: - "@confio/ics23" "^0.6.8" - "@cosmjs/amino" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/proto-signing" "0.28.13" - "@cosmjs/stream" "0.28.13" - "@cosmjs/tendermint-rpc" "0.28.13" - "@cosmjs/utils" "0.28.13" - cosmjs-types "^0.4.0" - long "^4.0.0" - protobufjs "~6.11.3" + "@cosmjs/stream" "^0.29.5" + isomorphic-ws "^4.0.1" + ws "^7" xstream "^11.14.0" "@cosmjs/stargate@^0.25.5", "@cosmjs/stargate@^0.25.6": @@ -1445,11 +1427,22 @@ long "^4.0.0" protobufjs "~6.10.2" -"@cosmjs/stream@0.28.13": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.28.13.tgz#1e79d1116fda1e63e5ecddbd9d803d403942b1fa" - integrity sha512-AnjtfwT8NwPPkd3lhZhjOlOzT0Kn9bgEu2IPOZjQ1nmG2bplsr6TJmnwn0dJxHT7UGtex17h6whKB5N4wU37Wg== +"@cosmjs/stargate@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.5.tgz#d597af1c85a3c2af7b5bdbec34d5d40692cc09e4" + integrity sha512-hjEv8UUlJruLrYGJcUZXM/CziaINOKwfVm2BoSdUnNTMxGvY/jC1ABHKeZUYt9oXHxEJ1n9+pDqzbKc8pT0nBw== dependencies: + "@confio/ics23" "^0.6.8" + "@cosmjs/amino" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/proto-signing" "^0.29.5" + "@cosmjs/stream" "^0.29.5" + "@cosmjs/tendermint-rpc" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + cosmjs-types "^0.5.2" + long "^4.0.0" + protobufjs "~6.11.3" xstream "^11.14.0" "@cosmjs/stream@^0.25.6": @@ -1459,20 +1452,11 @@ dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@0.28.13", "@cosmjs/tendermint-rpc@^0.28.0": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.13.tgz#0bf587ae66fa3f88319edbd258492d28e73f9f29" - integrity sha512-GB+ZmfuJIGQm0hsRtLYjeR3lOxF7Z6XyCBR0cX5AAYOZzSEBJjevPgUHD6tLn8zIhvzxaW3/VKnMB+WmlxdH4w== +"@cosmjs/stream@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.5.tgz#350981cac496d04939b92ee793b9b19f44bc1d4e" + integrity sha512-TToTDWyH1p05GBtF0Y8jFw2C+4783ueDCmDyxOMM6EU82IqpmIbfwcdMOCAm0JhnyMh+ocdebbFvnX/sGKzRAA== dependencies: - "@cosmjs/crypto" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/json-rpc" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/socket" "0.28.13" - "@cosmjs/stream" "0.28.13" - "@cosmjs/utils" "0.28.13" - axios "^0.21.2" - readonly-date "^1.0.0" xstream "^11.14.0" "@cosmjs/tendermint-rpc@^0.25.6": @@ -1490,16 +1474,39 @@ readonly-date "^1.0.0" xstream "^11.14.0" -"@cosmjs/utils@0.28.13": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.28.13.tgz#2fd2844ec832d7833811e2ae1691305d09791a08" - integrity sha512-dVeMBiyg+46x7XBZEfJK8yTihphbCFpjVYmLJVqmTsHfJwymQ65cpyW/C+V/LgWARGK8hWQ/aX9HM5Ao8QmMSg== +"@cosmjs/tendermint-rpc@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.5.tgz#f205c10464212bdf843f91bb2e4a093b618cb5c2" + integrity sha512-ar80twieuAxsy0x2za/aO3kBr2DFPAXDmk2ikDbmkda+qqfXgl35l9CVAAjKRqd9d+cRvbQyb5M4wy6XQpEV6w== + dependencies: + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/json-rpc" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/socket" "^0.29.5" + "@cosmjs/stream" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + axios "^0.21.2" + readonly-date "^1.0.0" + xstream "^11.14.0" "@cosmjs/utils@^0.25.6": version "0.25.6" resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.25.6.tgz#934d9a967180baa66163847616a74358732227ca" integrity sha512-ofOYiuxVKNo238vCPPlaDzqPXy2AQ/5/nashBo5rvPZJkxt9LciGfUEQWPCOb1BIJDNx2Dzu0z4XCf/dwzl0Dg== +"@cosmjs/utils@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.5.tgz#3fed1b3528ae8c5f1eb5d29b68755bebfd3294ee" + integrity sha512-m7h+RXDUxOzEOGt4P+3OVPX7PuakZT3GBmaM/Y2u+abN3xZkziykD/NvedYFvvCCdQo714XcGl33bwifS9FZPQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@design-systems/utils@2.12.0": version "2.12.0" resolved "https://registry.yarnpkg.com/@design-systems/utils/-/utils-2.12.0.tgz#955c108be07cb8f01532207cbfea8f848fa760c9" @@ -1689,6 +1696,19 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@favware/rollup-type-bundler@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@favware/rollup-type-bundler/-/rollup-type-bundler-2.0.0.tgz#5594ee64b40dc64eb02eb68a30943ff4255d25dd" + integrity sha512-RTMvx6v3b4D9V9iXHTogYjHa5fCmcUR3Bu0pFreo+80v2tD+7MSJm24nR1P/SkeTCy+zHKC1JOFQa04fcWZozg== + dependencies: + "@sapphire/utilities" "^3.11.0" + colorette "^2.0.19" + commander "^9.4.1" + js-yaml "^4.1.0" + rollup "^3.2.1" + rollup-plugin-dts "^5.0.0" + typescript "^4.8.4" + "@floating-ui/core@^1.0.5": version "1.1.0" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.1.0.tgz#0a1dee4bbce87ff71602625d33f711cafd8afc08" @@ -2030,7 +2050,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@3.1.0": +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== @@ -2053,6 +2073,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": version "0.3.17" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" @@ -3333,6 +3361,14 @@ "@rollup/pluginutils" "^5.0.1" resolve "^1.22.1" +"@rollup/plugin-typescript@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-11.0.0.tgz#f136272d1df5209daca0cb6f171c574b1d505545" + integrity sha512-goPyCWBiimk1iJgSTgsehFD5OOFHiAknrRJjqFCudcW8JtWiBlK284Xnn4flqMqg6YAjVG/EE+3aVzrL5qNSzQ== + dependencies: + "@rollup/pluginutils" "^5.0.1" + resolve "^1.22.1" + "@rollup/plugin-wasm@^6.1.1": version "6.1.2" resolved "https://registry.yarnpkg.com/@rollup/plugin-wasm/-/plugin-wasm-6.1.2.tgz#faf57f8e2ed12b9e0e898ba67963c52e1cd5f4c3" @@ -3356,6 +3392,11 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@sapphire/utilities@^3.11.0": + version "3.11.0" + resolved "https://registry.yarnpkg.com/@sapphire/utilities/-/utilities-3.11.0.tgz#2dccfb332dc5c119e1425cce6b2c64160b770bad" + integrity sha512-ich7J+329UTEgWxgk8b871rMhbFW/hvXdabdiKaUKd6g10eIMkIakWf+EGkDQsiDSiebIXll9TIPPmWtN3cVSw== + "@sinclair/typebox@^0.24.1": version "0.24.51" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" @@ -4621,6 +4662,26 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + "@types/acorn@^4.0.0": version "4.0.6" resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.6.tgz#d61ca5480300ac41a7d973dd5b84d0a591154a22" @@ -5775,6 +5836,11 @@ acorn-walk@^7.1.1, acorn-walk@^7.2.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^6.4.1: version "6.4.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" @@ -5785,7 +5851,7 @@ acorn@^7.1.1, acorn@^7.4.0, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.0, acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: +acorn@^8.0.0, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== @@ -6008,6 +6074,11 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -6290,12 +6361,14 @@ axios@^0.21.1, axios@^0.21.2: dependencies: follow-redirects "^1.14.0" -axios@^0.26.1: - version "0.26.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" - integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== +axios@^1.3.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024" + integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ== dependencies: - follow-redirects "^1.14.8" + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" axobject-query@^3.1.1: version "3.1.1" @@ -7118,7 +7191,7 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@3.5.3, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.3: +chokidar@3.5.3, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -7409,7 +7482,7 @@ colorette@^1.2.2: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== -colorette@^2.0.10, colorette@^2.0.14: +colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.19: version "2.0.19" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== @@ -7479,6 +7552,11 @@ commander@^8.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.4.1: + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + common-path-prefix@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" @@ -7755,7 +7833,7 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cosmjs-types@^0.4.0, cosmjs-types@^0.4.1: +cosmjs-types@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.4.1.tgz#3b2a53ba60d33159dd075596ce8267cfa7027063" integrity sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog== @@ -7763,6 +7841,14 @@ cosmjs-types@^0.4.0, cosmjs-types@^0.4.1: long "^4.0.0" protobufjs "~6.11.2" +cosmjs-types@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.5.2.tgz#2d42b354946f330dfb5c90a87fdc2a36f97b965d" + integrity sha512-zxCtIJj8v3Di7s39uN4LNcN3HIE1z0B9Z0SPE8ZNQR0oSzsuSe1ACgxoFkvhkS7WBasCAFcglS11G2hyfd5tPg== + dependencies: + long "^4.0.0" + protobufjs "~6.11.2" + cp-file@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-7.0.0.tgz#b9454cfd07fe3b974ab9ea0e5f29655791a9b8cd" @@ -7819,6 +7905,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -8562,6 +8653,11 @@ diff@^3.1.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + diff@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" @@ -8731,6 +8827,11 @@ dotenv-webpack@^7.0.3: dependencies: dotenv-defaults "^2.0.2" +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + dotenv@^8.0.0, dotenv@^8.2.0: version "8.6.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" @@ -8769,7 +8870,7 @@ electron-to-chromium@^1.4.251: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== -elliptic@^6.5.3: +elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -9560,7 +9661,7 @@ expect@^29.0.0: jest-message-util "^29.3.1" jest-util "^29.3.1" -express@^4.17.1, express@^4.17.3: +express@^4.17.1, express@^4.17.3, express@^4.18.2: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== @@ -9937,7 +10038,7 @@ focus-lock@^0.8.0: dependencies: tslib "^1.9.3" -follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.8: +follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== @@ -10026,6 +10127,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -10892,7 +11002,7 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-proxy-middleware@^2.0.3: +http-proxy-middleware@^2.0.3, http-proxy-middleware@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== @@ -10994,6 +11104,11 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA== +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + ignore-walk@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" @@ -13015,6 +13130,13 @@ magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" +magic-string@^0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.29.0.tgz#f034f79f8c43dba4ae1730ffb5e8c4e084b16cf3" + integrity sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.13" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -13995,6 +14117,13 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== +moq.ts@^7.3.4: + version "7.4.1" + resolved "https://registry.yarnpkg.com/moq.ts/-/moq.ts-7.4.1.tgz#1bb7e808324dc5b0143fa8b1a94f539203d475cb" + integrity sha512-P97MjGUkkMKQrAHbNScyJvSv3S7pMxnuxd3HgaOrPuoC6okwPqru3ZX8W/mTdU7Ms74622MeeWHqoDaGyYKonw== + dependencies: + tslib "2.1.0" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -14249,6 +14378,22 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== +nodemon@^2.0.21: + version "2.0.21" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.21.tgz#267edff25578da91075d6aa54346ef77ecb7b302" + integrity sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^5.7.1" + simple-update-notifier "^1.0.7" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + nopt@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" @@ -14264,6 +14409,13 @@ nopt@^5.0.0: dependencies: abbrev "1" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -15774,6 +15926,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -15784,6 +15941,11 @@ psl@^1.1.28, psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -16785,11 +16947,27 @@ rollup-plugin-base64@^1.0.1: dependencies: "@rollup/pluginutils" "^3.1.0" +rollup-plugin-dts@^5.0.0, rollup-plugin-dts@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-5.2.0.tgz#1970edb69cfa3ead285ff4289d8fe273b87a5af3" + integrity sha512-B68T/haEu2MKcz4kNUhXB8/h5sq4gpplHAJIYNHbh8cp4ZkvzDvNca/11KQdFrB9ZeKucegQIotzo5T0JUtM8w== + dependencies: + magic-string "^0.29.0" + optionalDependencies: + "@babel/code-frame" "^7.18.6" + rollup-plugin-web-worker-loader@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/rollup-plugin-web-worker-loader/-/rollup-plugin-web-worker-loader-1.6.1.tgz#9d7a27575b64b0780fe4e8b3bc87470d217e485f" integrity sha512-4QywQSz1NXFHKdyiou16mH3ijpcfLtLGOrAqvAqu1Gx+P8+zj+3gwC2BSL/VW1d+LW4nIHC8F7d7OXhs9UdR2A== +rollup@^3.17.2, rollup@^3.2.1: + version "3.17.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.17.2.tgz#a4ecd29c488672a0606e41ef57474fad715750a9" + integrity sha512-qMNZdlQPCkWodrAZ3qnJtvCAl4vpQ8q77uEujVCCbC/6CLB7Lcmvjq7HyiOSnf4fxTT9XgsE36oLHJBH49xjqA== + optionalDependencies: + fsevents "~2.3.2" + rollup@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.10.1.tgz#56278901ed11fc2898421e8e3e2c8155bc7b40b4" @@ -16980,6 +17158,11 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -17207,6 +17390,13 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" + integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== + dependencies: + semver "~7.0.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -17826,7 +18016,7 @@ supports-color@8.1.1, supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -18204,6 +18394,13 @@ toposort@^2.0.2: resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" @@ -18307,6 +18504,25 @@ ts-mocha@^10.0.0: optionalDependencies: tsconfig-paths "^3.5.0" +ts-node@10: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + ts-node@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" @@ -18345,6 +18561,11 @@ tsconfig-paths@^3.14.1, tsconfig-paths@^3.5.0, tsconfig-paths@^3.9.0: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -18508,6 +18729,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + unfetch@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" @@ -18890,6 +19116,11 @@ uvu@^0.5.0: kleur "^4.0.3" sade "^1.7.3" +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -19656,6 +19887,11 @@ yargs@16.2.0, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yn@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a"