Compare commits

..

43 Commits

Author SHA1 Message Date
Rachyandco 2bc7448b08 correct link 2023-06-07 13:45:24 +02:00
Tommy Verrall 42acbfe806 Update build-and-upload-binaries-ci.yml
temp use rust version 1.69.0 for ci build uploads, will need to switch and use: cosmwasm/rust-optimizer in the near future
2023-06-07 11:24:53 +02:00
farbanas 7c55483585 Merge branch 'master' into develop 2023-06-07 10:43:11 +02:00
farbanas c04cc9a4cf Merge branch 'release/v1.1.20' 2023-06-06 10:54:17 +02:00
farbanas 17258d1445 updated locks and versions bumped 2023-06-06 10:35:42 +02:00
farbanas 8f3d7606f5 bump crate versions 2023-06-06 10:07:06 +02:00
farbanas fd97f0e8ca update changelogs and version for release 2023-06-06 10:00:11 +02:00
Jędrzej Stuczyński d4ce1635a8 Fixed incorrect assertion when sending replies (#3515) 2023-06-05 14:36:44 +01:00
Jędrzej Stuczyński 2bc564ad01 updating managed keys after gateway registration (#3514) 2023-06-05 14:09:28 +01:00
Tommy Verrall 5910bcbc02 Update build-and-upload-binaries-ci.yml
add the service provider and name service contract to build output
2023-06-05 14:49:49 +02:00
Tommy Verrall 8c63fe9d0d Merge pull request #3507 from nymtech/jon/feat/reduce-shutdown-timeout-in-socks5
Reduce SHUTDOWN_TIMEOUT to 3 sec
2023-06-05 10:30:34 +01:00
Pierre Dommerc 6e5a1973da fix(wallet): fix bonding data refresh (#3499) 2023-06-05 11:07:23 +02:00
Pierre Dommerc 1aa11887aa fix(wallet): fix bonding data refresh (#3499) 2023-06-05 11:05:25 +02:00
Jon Häggblad 07740cbf08 Reduce SHUTDOWN_TIMEOUT to 3 sec
Can't think of a scenario where we don't want to close one at the same
time as the other, but let's be conservative and keep it a very low
number for now.
2023-06-05 10:42:38 +02:00
Jon Häggblad 87cb8a6b20 Sign when announcing service providers to the directory contract (#3459)
* create_payload and call from nym-cli

* Remove some commented out code

* wip

* Service announce now compiles

* Fix other compilation issues

* Move ServiceDetails into Service

* Move service_id inside Service type

* wip: start sorting out tests

* wip: sorting out testing

* wip: first announce test now works

* wip: more work on announce test

* Move nonce

* Add check for nonce

* Extract out some helpers to separate files

* reenable state::services tests

* wip: start going through the integration tests

* All integration tests reenabled

* Remove some unused stuff

* Iterate on integration tests

* More iteration on test setup

* Rename to test_setup.rs

* Add more tests specific to signing

* Tweak

* Another nonce test and reorg

* Rename to announce.rs and delete.rs

* Tidy

* Make some inner modules private

* Use IdentityKey alias

* Update nym-api contract cache

* Fix that nym-cli was asking for signing nonce from wrong contract

* Add sign comment to network-requester

* Uploaded updated service provider contract to qwerty

* Allow large enum variant

* lock files

* Remove dbg

* Move error.rs to service-provider common

* Update code for moving errors.rs to common crate

* Rename to SpContractError

* constants module not pub

* lock file

* rustfmt

* Move IdentityKey type to contract-common

* clippy
2023-06-05 10:32:58 +02:00
Jon Häggblad 2977b8f25f Fix clippy for 1.70 (#3505) 2023-06-05 08:54:18 +01:00
pierre 9ae4fd04ac ci(ns5-android): fix workflow 2023-06-02 15:44:39 +02:00
pierre 4470969bec ci(ns5-android): update workflow to create GH release 2023-06-02 14:49:14 +02:00
pierre 1a4c3a7709 chore(ns5-android): add app metadata for listing 2023-06-02 14:19:17 +02:00
Pierre Dommerc 99b31920d5 fix(ns5-android): make lib calling callbacks (#3496) 2023-06-02 13:59:10 +02:00
mx 019b3299f2 Merge pull request #3435 from Pawnflake/release/v1.1.19
mixnode documentation update
2023-06-02 09:40:50 +00:00
mx d684957423 Update documentation/docs/src/nodes/mix-node-setup.md
Co-authored-by: ️2FakTor️ <twofaktor@protonmail.com>
2023-06-02 09:20:56 +00:00
mx cb4eda4c62 Update documentation/docs/src/nodes/mix-node-setup.md
Co-authored-by: ️2FakTor️ <twofaktor@protonmail.com>
2023-06-02 09:20:42 +00:00
mx ac5f380ee2 Merge pull request #3485 from nymtech/feature/docs-1-1-20
removing hardcoded version numbers
2023-06-02 09:20:10 +00:00
mx 4e278ca07d reintroduced hardcoding for links for moment 2023-06-02 10:52:28 +02:00
mx cd6a725875 Merge pull request #3493 from twofaktor/patch-1
[BUG] network requester documentation update
2023-06-02 08:42:20 +00:00
pierre 62ccb6b4cd build(ns5-android): add product flavors config 2023-06-01 19:20:32 +02:00
pierre 365e0134b4 build(nc-native-android): clean build script 2023-05-31 17:31:24 +02:00
⚡️2FakTor⚡️ d5514a060c Update network-requester-setup.md 2023-05-31 15:22:11 +02:00
pierre 8432c30f6c build(nc-native-android): add gradle build universal apk 2023-05-31 14:20:49 +02:00
pierre c2764f90b3 ci(nc-native-android): update github workflow to build unsigned apks 2023-05-31 13:14:44 +02:00
mx 958b6d37ee Merge pull request #3481 from twofaktor/patch-1
[BUG] network requester documentation update
2023-05-30 14:40:33 +00:00
mx 5e36bb014c removing hardcoded versoin numbers 2023-05-30 16:10:15 +02:00
⚡️2FakTor⚡️ 8d821881ae Update network-requester-setup.md 2023-05-30 14:48:51 +02:00
mx fca9761145 Merge pull request #3141 from nymtech/chore/update-community-links-in-readme
updated readme with new developer chat links + new docs links
2023-05-30 12:05:14 +00:00
mx 11481e4d13 Merge branch 'release/v1.1.20' into chore/update-community-links-in-readme 2023-05-30 12:04:45 +00:00
pierre a6a39d1234 chore(nc-native-android): remove outdated todo 2023-05-30 13:58:30 +02:00
pierre 5f35d54fcb build(nc-native-android): fix script build paths 2023-05-30 13:40:52 +02:00
Pawnflake c8b82a9553 removed comment http and https 2023-05-20 07:23:13 +08:00
mx 00c2f5359c Merge pull request #3428 from nymtech/feature/updated-sandbox-docs
added sandbox sync docs
2023-05-16 14:53:51 +00:00
mx 1a4e0f4e08 added sandbox sync docs 2023-05-16 16:44:39 +02:00
Pawnflake 69230a10cb Added ufw restart after adding ports and removed port 80 and 443 from the command 2023-05-16 10:10:41 +08:00
mx 3a0c8f3f4e updated readme with new developer chat links + new docs links 2023-03-03 13:32:44 +01:00
124 changed files with 3295 additions and 1279 deletions
@@ -63,7 +63,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: 1.69.0
- name: Build all binaries
uses: actions-rs/cargo@v1
@@ -74,7 +74,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: 1.69.0
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
@@ -107,6 +107,8 @@ jobs:
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
cp contracts/target/wasm32-unknown-unknown/release/nym_service_provider_directory.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_name_service.wasm $OUTPUT_DIR
- name: Deploy branch to CI www
continue-on-error: true
-138
View File
@@ -1,138 +0,0 @@
name: Nym Connect - Android APK Build
on:
workflow_dispatch:
push:
branches:
- "release/nc-android-v[0-9].[0-9].[0-9]*"
jobs:
build:
name: Build APK
runs-on: custom-runner-linux
env:
ANDROID_HOME: ${{ github.workspace }}/android-sdk
NDK_VERSION: 25.1.8937393
NDK_HOME: ${{ github.workspace }}/android-sdk/ndk/25.1.8937393
SDK_PLATFORM_VERSION: android-33
SDK_BUILDTOOLS_VERSION: 33.0.1
steps:
- name: Install Dependencies (Linux)
# https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux/#1-system-dependencies
run: |
sudo apt-get update
sudo apt-get -y install \
build-essential \
unzip \
curl \
wget \
libssl-dev \
squashfs-tools \
librsvg2-dev
- name: Checkout
uses: actions/checkout@v3
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "17"
- name: Install Android SDK manager
# https://developer.android.com/studio/command-line/sdkmanager
run: |
curl -sS https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -o cmdline-tools.zip
unzip cmdline-tools.zip
mkdir -p $ANDROID_HOME/cmdline-tools/latest
mv cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest
rm -rf cmdline-tools
- name: Install Android S/NDK
run: |
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
"platforms;$SDK_PLATFORM_VERSION" \
"platform-tools" \
"ndk;$NDK_VERSION" \
"build-tools;$SDK_BUILDTOOLS_VERSION"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
# TODO this step takes a considerable amount of time
# We could avoid to compile from source tauri-cli and use instead
# pre-compiled binary provided by the node package `@tauri-apps/cli`
# But when using the later the build fails for some reason
# so keep installing and using tauri-cli
- name: Install tauri cli
run: cargo install tauri-cli --version "^2.0.0-alpha.2"
- name: Install rust android targets
run: |
rustup target add aarch64-linux-android \
armv7-linux-androideabi \
i686-linux-android \
x86_64-linux-android
- name: Setup Nodejs
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install yarn
run: |
npm i -g yarn
yarn --version
- name: Build frontend code
run: |
yarn install --frozen-lockfile
yarn build
yarn workspace @nym/nym-connect-mobile webpack:prod
- name: Build APK
working-directory: nym-connect/mobile
env:
# NODE_TAURI_CLI=${{ github.workspace }}/nym-connect/mobile/node_modules/.bin/tauri
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
WRY_ANDROID_PACKAGE: net.nymtech.nym_connect
WRY_ANDROID_LIBRARY: nym_connect
# TODO build with release profile (--release), it will requires
# to sign the APK. For now build with debug profile to avoid that
# TODO build using `yarn tauri`, provide NODE_TAURI_CLI, see TODO notes above
run: cargo tauri android build --debug --apk --split-per-abi -t aarch64
# TODO add the version number to APK name
- name: Rename APK artifact
run: |
mkdir apk/
mv nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build/outputs/apk/arm64/debug/app-arm64-debug.apk \
apk/nym-connect-arm64-debug.apk
mv nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build/outputs/apk/x86_64/debug/app-x86_64-debug.apk \
apk/nym-connect-x86_64-debug.apk
- name: Upload APK artifact
uses: actions/upload-artifact@v3
with:
name: nc-apk-debug
path: |
apk/nym-connect-arm64-debug.apk
apk/nym-connect-x86_64-debug.apk
# publish:
# name: Publish APK
# needs: build
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v3
# - name: Download binary artifact
# uses: actions/download-artifact@v3
# with:
# name: nc-apk-debug
# path: apk
# # TODO add a step to upload the APK somewhere
# - name: Publish
# uses: ???
+102
View File
@@ -0,0 +1,102 @@
name: Nyms5 Android
# unsigned APKs only, supported archs:
# - arm64-v8a (arm64)
# - x86_64
on:
workflow_dispatch:
push:
tags:
- nyms5-android-v*
jobs:
build:
name: Build APK
runs-on: custom-runner-linux
env:
ANDROID_HOME: ${{ github.workspace }}/android-sdk
NDK_VERSION: 25.2.9519653
NDK_HOME: ${{ github.workspace }}/android-sdk/ndk/25.2.9519653
SDK_PLATFORM_VERSION: android-33
SDK_BUILDTOOLS_VERSION: 33.0.2
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "17"
- name: Install Android SDK manager
# https://developer.android.com/studio/command-line/sdkmanager
run: |
curl -sS https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -o cmdline-tools.zip
unzip cmdline-tools.zip
mkdir -p $ANDROID_HOME/cmdline-tools/latest
mv cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest
rm -rf cmdline-tools
- name: Install Android S/NDK
run: |
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
"platforms;$SDK_PLATFORM_VERSION" \
"platform-tools" \
"ndk;$NDK_VERSION" \
"build-tools;$SDK_BUILDTOOLS_VERSION"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install rust android targets
run: |
rustup target add aarch64-linux-android \
x86_64-linux-android
- name: Build lib nym-socks5-listener
working-directory: sdk/lib/socks5-listener/
env:
RELEASE: true
# build for arm64 and x86_64
run: ./build-android.sh aarch64 x86_64
- name: Build APKs (unsigned)
working-directory: nym-connect/native/android
env:
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
# build for arm64 and x86_64
run: ./gradlew :app:assembleArch64Release
- name: Prepare APKs
run: |
mkdir apk
mv nym-connect/native/android/app/build/outputs/apk/arch64/release/app-arch64-release-unsigned.apk \
apk/nyms5-arch64-release.apk
- name: Upload APKs
uses: actions/upload-artifact@v3
with:
name: nyms5-apk-arch64-release
path: |
apk/nyms5-arch64-release.apk
gh-release:
name: Publish APK (GH release)
needs: build
runs-on: custom-runner-linux
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download binary artifact
uses: actions/download-artifact@v3
with:
name: nyms5-apk-arch64-release
path: apk
- name: Release
uses: softprops/action-gh-release@v1
with:
files: apk/nyms5-arch64-release.apk
+30
View File
@@ -4,6 +4,36 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.20] (2023-06-06)
- Explorer - Fix SP supported apps list ([#3458])
- Investigate if we need to lower `SHUTDOWN_TIMEOUT` in socks5 to zero (or almost zero) ([#3438])
- Explorer - show all gateways in the default view regardless of the version number ([#3427])
- service-provider-directory: add signature check when announcing ([#3360])
- Support functionality for nym-name-service (nym-api, nym-cli, etc) ([#3355])
- Edit the nym-network-requester to support the enabled-credentials-mode flag ([#3101])
- [BUG] network requester documentation update ([#3493])
- removing hardcoded version numbers ([#3485])
- [BUG] network requester documentation update ([#3481])
- [BUG] network requester documentation update ([#3469])
- Sign when announcing service providers to the directory contract ([#3459])
- mixnode documentation update ([#3435])
- updated readme with new developer chat links + new docs links ([#3141])
[#3458]: https://github.com/nymtech/nym/issues/3458
[#3438]: https://github.com/nymtech/nym/issues/3438
[#3427]: https://github.com/nymtech/nym/issues/3427
[#3360]: https://github.com/nymtech/nym/issues/3360
[#3355]: https://github.com/nymtech/nym/issues/3355
[#3101]: https://github.com/nymtech/nym/issues/3101
[#3493]: https://github.com/nymtech/nym/pull/3493
[#3485]: https://github.com/nymtech/nym/pull/3485
[#3481]: https://github.com/nymtech/nym/pull/3481
[#3469]: https://github.com/nymtech/nym/pull/3469
[#3459]: https://github.com/nymtech/nym/pull/3459
[#3435]: https://github.com/nymtech/nym/pull/3435
[#3141]: https://github.com/nymtech/nym/pull/3141
## [v1.1.19] (2023-05-16)
- nym-name-service endpoint in nym-api ([#3403])
Generated
+36 -14
View File
@@ -1243,6 +1243,20 @@ dependencies = [
"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-storage-plus"
version = "0.13.4"
@@ -1632,7 +1646,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.19"
version = "1.1.20"
dependencies = [
"chrono",
"clap 4.2.7",
@@ -3176,7 +3190,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.20"
version = "1.1.21"
dependencies = [
"anyhow",
"async-trait",
@@ -3310,7 +3324,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.19"
version = "1.1.20"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -3359,6 +3373,7 @@ dependencies = [
"nym-name-service-common",
"nym-network-defaults",
"nym-service-provider-directory-common",
"nym-sphinx",
"nym-validator-client",
"nym-vesting-contract-common",
"rand 0.6.5",
@@ -3373,7 +3388,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.19"
version = "1.1.20"
dependencies = [
"clap 4.2.7",
"dirs",
@@ -3531,7 +3546,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3587,7 +3602,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"aes 0.8.2",
"blake3",
@@ -3644,7 +3659,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.19"
version = "1.1.20"
dependencies = [
"anyhow",
"async-trait",
@@ -3776,7 +3791,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3795,7 +3810,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.20"
version = "1.1.21"
dependencies = [
"anyhow",
"bs58",
@@ -3905,10 +3920,11 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.19"
version = "1.1.20"
dependencies = [
"async-file-watcher",
"async-trait",
"bs58",
"clap 4.2.7",
"dirs",
"futures",
@@ -3931,11 +3947,13 @@ dependencies = [
"nym-sphinx",
"nym-statistics-common",
"nym-task",
"nym-types",
"pretty_env_logger",
"publicsuffix",
"rand 0.7.3",
"reqwest",
"serde",
"serde_json",
"sqlx 0.6.3",
"tap",
"tempfile",
@@ -3947,7 +3965,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.19"
version = "1.1.20"
dependencies = [
"dirs",
"log",
@@ -4022,7 +4040,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"pem",
]
@@ -4063,8 +4081,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -4085,7 +4107,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.19"
version = "1.1.20"
dependencies = [
"clap 4.2.7",
"lazy_static",
@@ -4481,7 +4503,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
+2 -1
View File
@@ -120,8 +120,9 @@ 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-controllers = "=0.13.4"
cw-storage-plus = "=0.13.4"
cw-utils = "=0.13.4"
cw2 = { version = "=0.13.4" }
cw3 = { version = "=0.13.4" }
cw3-fixed-multisig = { version = "=0.13.4" }
+6 -2
View File
@@ -22,7 +22,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
### Building
Platform build instructions are available on [our docs site](https://nymtech.net/docs/binaries/building-nym.html).
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/stable/nym-apps/wallet#for-developers).
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/wallet/desktop-wallet.html).
### Developing
@@ -32,7 +32,11 @@ For Typescript components, please see [ts-packages](./ts-packages).
### Developer chat
You can chat with us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes place in the **#dev** channel. Node operators should be in the **#node-operators** channel.
> We used to use Keybase for developer chats, but we have since migrated to Matrix and Discord. We no longer check the old **nymtech.friends** Keybase team.
You can chat to us in two places:
* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat)
* The various developer channels on [Discord](https://discord.gg/nym)
### Rewards
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.19"
version = "1.1.20"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.19"
version = "1.1.20"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+23 -5
View File
@@ -865,6 +865,20 @@ dependencies = [
"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-storage-plus"
version = "0.13.4"
@@ -2367,7 +2381,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -2402,7 +2416,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"aes 0.8.2",
"blake3",
@@ -2509,7 +2523,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -2609,7 +2623,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"pem",
]
@@ -2619,8 +2633,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -2873,7 +2891,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
@@ -565,7 +565,7 @@ where
fragments: Vec<Fragment>,
reply_surbs: Vec<ReplySurb>,
) -> Result<Vec<PreparedFragment>, SurbWrappedPreparationError> {
debug_assert_ne!(
debug_assert_eq!(
fragments.len(),
reply_surbs.len(),
"attempted to send {} fragments with {} reply surbs",
+13 -4
View File
@@ -155,7 +155,7 @@ pub async fn get_registered_gateway<S>(
key_store: &S::KeyStore,
setup: GatewaySetup,
overwrite_keys: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
) -> Result<(GatewayEndpointConfig, ManagedKeys), ClientCoreError>
where
S: MixnetClientStorage,
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
@@ -164,11 +164,11 @@ where
// try load keys
let mut managed_keys = match ManagedKeys::try_load(key_store).await {
Ok(_) => {
Ok(loaded_keys) => {
// if we loaded something and we don't have full gateway details, check if we can overwrite the data
if let GatewaySetup::Predefined { config } = setup {
// we already have defined gateway details AND a shared key, so nothing more for us to do
return Ok(config);
return Ok((config, loaded_keys));
} else if overwrite_keys {
ManagedKeys::generate_new(&mut rng)
} else {
@@ -196,7 +196,7 @@ where
// TODO: here we should be probably persisting gateway details as opposed to returning them
Ok(gateway_details)
Ok((gateway_details, managed_keys))
}
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the
@@ -299,6 +299,15 @@ pub fn get_client_address(
)
}
pub fn load_identity_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
Ok(identity_keypair)
}
/// Get the client address by loading the keys from stored files.
// TODO: rethink that sucker
pub fn get_client_address_from_stored_ondisk_keys<T>(
@@ -1,12 +1,12 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_contracts_common::{signing::Nonce, ContractBuildInformation};
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQueryMsg,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, ServiceId, ServiceInfo,
NymAddress, Service, ServiceId,
};
use serde::Deserialize;
@@ -63,17 +63,14 @@ pub trait SpDirectoryQueryClient {
.await
}
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
async fn get_all_services(&self) -> Result<Vec<Service>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
let last_id = paged_response.services.last().map(|serv| serv.service_id);
services.append(&mut paged_response.services);
if let Some(start_after_res) = last_id {
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
@@ -82,6 +79,13 @@ pub trait SpDirectoryQueryClient {
Ok(services)
}
async fn get_service_signing_nonce(&self, address: &AccountId) -> Result<Nonce, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::SigningNonce {
address: address.to_string(),
})
.await
}
}
#[async_trait]
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_contracts_common::signing::MessageSignature;
use nym_service_provider_directory_common::{
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceDetails, ServiceId,
};
use crate::nyxd::{
@@ -22,16 +23,16 @@ pub trait SpDirectorySigningClient {
async fn announce_service_provider(
&self,
nym_address: NymAddress,
service_type: ServiceType,
service: ServiceDetails,
owner_signature: MessageSignature,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
nym_address,
service_type,
service,
owner_signature,
},
vec![deposit],
)
+1
View File
@@ -40,3 +40,4 @@ nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
nym-sphinx = { path = "../../common/nymsphinx" }
@@ -14,6 +14,7 @@ pub struct Mixnet {
pub command: MixnetCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetCommands {
/// Query the mixnet directory
@@ -15,6 +15,7 @@ pub struct MixnetOperators {
pub command: MixnetOperatorsCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsCommands {
/// Manage your mixnode
@@ -1,6 +1,7 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
use nym_contracts_common::signing::MessageSignature;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceDetails, ServiceType};
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
@@ -10,9 +11,15 @@ pub struct Args {
#[clap(long)]
pub nym_address: String,
#[clap(long)]
pub signature: MessageSignature,
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
#[clap(long)]
pub deposit: u128,
#[clap(long)]
pub identity_key: String,
}
pub async fn announce(args: Args, client: SigningClient) {
@@ -20,12 +27,17 @@ pub async fn announce(args: Args, client: SigningClient) {
let nym_address = NymAddress::Address(args.nym_address);
let service_type = ServiceType::NetworkRequester;
let service = ServiceDetails {
nym_address,
service_type,
identity_key: args.identity_key,
};
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.deposit, denom);
let res = client
.announce_service_provider(nym_address, service_type, deposit.into(), None)
.announce_service_provider(service, args.signature, deposit.into(), None)
.await
.expect("Failed to announce service provider");
@@ -0,0 +1,61 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
context::SigningClient,
utils::{account_id_to_cw_addr, DataWrapper},
};
use clap::Parser;
use cosmwasm_std::Coin;
use nym_bin_common::output_format::OutputFormat;
use nym_service_provider_directory_common::{
signing_types::construct_service_provider_announce_sign_payload, NymAddress,
ServiceType::NetworkRequester,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub nym_address: Recipient,
#[clap(long)]
pub amount: u128,
#[clap(long)]
pub identity_key: String,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
pub async fn create_payload(args: Args, client: SigningClient) {
let service = nym_service_provider_directory_common::ServiceDetails {
nym_address: NymAddress::new(&args.nym_address.to_string()),
service_type: NetworkRequester,
identity_key: args.identity_key,
};
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.amount, denom);
let nonce = match client.get_service_signing_nonce(client.address()).await {
Ok(nonce) => nonce,
Err(err) => {
eprint!(
"failed to query for the signing nonce of {}: {err}",
client.address()
);
return;
}
};
let address = account_id_to_cw_addr(client.address());
let payload =
construct_service_provider_announce_sign_payload(nonce, address, deposit, service);
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -1,6 +1,7 @@
use clap::{Args, Subcommand};
pub mod announce;
pub mod announce_sign_payload;
pub mod delete;
#[derive(Debug, Args)]
@@ -10,10 +11,13 @@ pub struct MixnetOperatorsService {
pub command: MixnetOperatorsServiceCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsServiceCommands {
/// Announce service provider to the world
Announce(announce::Args),
/// Delete entry for service provider from the directory
Delete(delete::Args),
/// Create base58-encoded payload required for producing valid announce signature.
CreateServiceAnnounceSignPayload(announce_sign_payload::Args),
}
@@ -37,7 +37,7 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
for service in res.services {
table.add_row(vec![
service.service_id.to_string(),
service.service.announcer.to_string(),
service.announcer.to_string(),
service.service.service_type.to_string(),
service.service.nym_address.to_string(),
]);
@@ -1,6 +1,6 @@
[package]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
description = "Common library for Nym cosmwasm contracts"
edition = { workspace = true }
authors = { workspace = true }
@@ -11,6 +11,9 @@ use std::ops::Mul;
use std::str::FromStr;
use thiserror::Error;
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
amount * Uint128::new(1)
}
@@ -1,6 +1,6 @@
[package]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
description = "Common library for the Nym mixnet contract"
rust-version = "1.62"
edition = { workspace = true }
@@ -15,7 +15,7 @@ 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.4.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
# use 0.4.1 as that's the version used by cosmwasm-std 1.0.0
# (and ideally we don't want to pull the same dependency twice)
serde-json-wasm = "=0.4.1"
@@ -4,6 +4,7 @@
use crate::error::MixnetContractError;
use crate::families::{Family, FamilyHead};
use crate::{Layer, RewardedSetNodeStatus};
use contracts_common::IdentityKey;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
use schemars::JsonSchema;
@@ -11,8 +12,6 @@ use serde::{Deserialize, Serialize};
use std::ops::Index;
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
pub type SphinxKeyRef<'a> = &'a str;
pub type EpochId = u32;
@@ -7,5 +7,9 @@ edition = "2021"
[dependencies]
cosmwasm-std = { workspace = true }
nym-contracts-common = { path = "../contracts-common", version = "0.5.0" }
schemars = "0.8"
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
cw-utils = { workspace = true }
cw-controllers = { workspace = true }
@@ -1,10 +1,12 @@
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_service_provider_directory_common::{NymAddress, ServiceId};
use nym_contracts_common::signing::verifier::ApiVerifierError;
use thiserror::Error;
use crate::{NymAddress, ServiceId};
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
pub enum SpContractError {
#[error("{0}")]
Std(#[from] StdError),
@@ -46,6 +48,21 @@ pub enum ContractError {
value: String,
error_message: String,
},
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
#[error("Failed to recover ed25519 signature from its base58 representation - {0}")]
MalformedEd25519Signature(String),
#[error("Provided ed25519 signature did not verify correctly")]
InvalidEd25519Signature,
#[error("failed to verify message signature: {source}")]
SignatureVerificationFailure {
#[from]
source: ApiVerifierError,
},
}
pub(crate) type Result<T, E = ContractError> = std::result::Result<T, E>;
pub type Result<T, E = SpContractError> = std::result::Result<T, E>;
@@ -39,16 +39,16 @@ pub fn new_announce_event(service_id: ServiceId, service: Service) -> Event {
Event::new(ServiceProviderEventType::Announce)
.add_attribute(ACTION, ServiceProviderEventType::Announce)
.add_attribute(SERVICE_ID, service_id.to_string())
.add_attribute(SERVICE_TYPE, service.service_type.to_string())
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
.add_attribute(SERVICE_TYPE, service.service.service_type.to_string())
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
.add_attribute(OWNER, service.announcer.to_string())
}
pub fn new_delete_id_event(service_id: ServiceId, service: Service) -> Event {
pub fn new_delete_id_event(service: Service) -> Event {
Event::new(ServiceProviderEventType::DeleteId)
.add_attribute(ACTION, ServiceProviderEventType::DeleteId)
.add_attribute(SERVICE_ID, service_id.to_string())
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
.add_attribute(SERVICE_ID, service.service_id.to_string())
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
}
pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event {
@@ -1,6 +1,8 @@
pub mod error;
pub mod events;
pub mod msg;
pub mod response;
pub mod signing_types;
pub mod types;
// Re-export all types at the top-level
@@ -1,5 +1,6 @@
use crate::{NymAddress, ServiceId, ServiceType};
use crate::{NymAddress, ServiceDetails, ServiceId};
use cosmwasm_std::Coin;
use nym_contracts_common::signing::MessageSignature;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -22,8 +23,8 @@ pub struct MigrateMsg {}
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Announce {
nym_address: NymAddress,
service_type: ServiceType,
service: ServiceDetails,
owner_signature: MessageSignature,
},
DeleteId {
service_id: ServiceId,
@@ -44,9 +45,12 @@ impl ExecuteMsg {
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Announce {
nym_address,
service_type,
} => format!("announcing {nym_address} as type {service_type}"),
service,
owner_signature: _,
} => format!(
"announcing {} as type {}",
service.nym_address, service.service_type
),
ExecuteMsg::DeleteId { service_id } => {
format!("deleting service with service id {service_id}")
}
@@ -76,6 +80,9 @@ pub enum QueryMsg {
limit: Option<u32>,
start_after: Option<ServiceId>,
},
SigningNonce {
address: String,
},
Config {},
GetContractVersion {},
#[serde(rename = "get_cw2_contract_version")]
@@ -1,4 +1,4 @@
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
use crate::{Service, ServiceId};
use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -13,22 +13,17 @@ pub struct ServiceInfoResponse {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServicesListResponse {
pub services: Vec<ServiceInfo>,
pub services: Vec<Service>,
}
impl ServicesListResponse {
pub fn new(services: Vec<(ServiceId, Service)>) -> ServicesListResponse {
ServicesListResponse {
services: services
.into_iter()
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
.collect(),
}
pub fn new(services: Vec<Service>) -> ServicesListResponse {
ServicesListResponse { services }
}
}
impl From<&[ServiceInfo]> for ServicesListResponse {
fn from(services: &[ServiceInfo]) -> Self {
impl From<&[Service]> for ServicesListResponse {
fn from(services: &[Service]) -> Self {
Self {
services: services.to_vec(),
}
@@ -38,21 +33,17 @@ impl From<&[ServiceInfo]> for ServicesListResponse {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct PagedServicesListResponse {
pub services: Vec<ServiceInfo>,
pub services: Vec<Service>,
pub per_page: usize,
pub start_next_after: Option<ServiceId>,
}
impl PagedServicesListResponse {
pub fn new(
services: Vec<(ServiceId, Service)>,
services: Vec<Service>,
per_page: usize,
start_next_after: Option<ServiceId>,
) -> PagedServicesListResponse {
let services = services
.into_iter()
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
.collect();
PagedServicesListResponse {
services,
per_page,
@@ -66,12 +57,3 @@ impl PagedServicesListResponse {
pub struct ConfigResponse {
pub deposit_required: Coin,
}
impl From<Service> for ExecuteMsg {
fn from(service: Service) -> Self {
ExecuteMsg::Announce {
nym_address: service.nym_address,
service_type: service.service_type,
}
}
}
@@ -0,0 +1,33 @@
use cosmwasm_std::{Addr, Coin};
use nym_contracts_common::signing::{
ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose,
};
use serde::Serialize;
use crate::ServiceDetails;
pub type SignableServiceProviderAnnounceMsg =
SignableMessage<ContractMessageContent<ServiceProviderAnnounce>>;
#[derive(Serialize)]
pub struct ServiceProviderAnnounce {
service: ServiceDetails,
}
impl SigningPurpose for ServiceProviderAnnounce {
fn message_type() -> MessageType {
MessageType::new("service-provider-announce")
}
}
pub fn construct_service_provider_announce_sign_payload(
nonce: Nonce,
sender: Addr,
deposit: Coin,
service: ServiceDetails,
) -> SignableServiceProviderAnnounceMsg {
let payload = ServiceProviderAnnounce { service };
let proxy = None;
let content = ContractMessageContent::new(sender, proxy, vec![deposit], payload);
SignableMessage::new(nonce, content)
}
@@ -1,6 +1,7 @@
use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin};
use nym_contracts_common::IdentityKey;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -9,11 +10,11 @@ pub type ServiceId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct Service {
/// The address of the service.
pub nym_address: NymAddress,
/// The service type.
pub service_type: ServiceType,
/// Service owner.
/// Unique id assigned to the anounced service.
pub service_id: ServiceId,
/// The announced service.
pub service: ServiceDetails,
/// Address of the service owner.
pub announcer: Addr,
/// Block height at which the service was added.
pub block_height: u64,
@@ -21,6 +22,16 @@ pub struct Service {
pub deposit: Coin,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct ServiceDetails {
/// The address of the service.
pub nym_address: NymAddress,
/// The service type.
pub service_type: ServiceType,
/// The identity key of the service.
pub identity_key: IdentityKey,
}
/// The types of addresses supported.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -28,7 +39,7 @@ pub enum NymAddress {
/// String representation of a nym address, which is of the form
/// client_id.client_enc@gateway_id.
Address(String),
// For the future when we have a nym-dns contract
// String name that can looked up in the nym-name-service contract (once it exists)
//Name(String),
}
@@ -41,6 +52,7 @@ impl NymAddress {
pub fn as_str(&self) -> &str {
match self {
NymAddress::Address(address) => address,
//NymAddress::Name(name) => name,
}
}
}
@@ -66,19 +78,3 @@ impl std::fmt::Display for ServiceType {
write!(f, "{service_type}")
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServiceInfo {
pub service_id: ServiceId,
pub service: Service,
}
impl ServiceInfo {
pub fn new(service_id: ServiceId, service: Service) -> Self {
Self {
service_id,
service,
}
}
}
@@ -1,6 +1,6 @@
[package]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
description = "Common library for the Nym vesting contract"
edition = { workspace = true }
authors = { workspace = true }
@@ -9,8 +9,8 @@ repository = { workspace = true }
[dependencies]
cosmwasm-std = { workspace = true }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.5.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
ts-rs = {version = "6.1.2", optional = true}
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
description = "Crypto library for the nym mixnet"
edition = { workspace = true }
authors = { workspace = true }
@@ -28,7 +28,7 @@ zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
# internal
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
nym-pemstore = { path = "../../common/pemstore", version = "0.2.0" }
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
[dev-dependencies]
rand_chacha = "0.2"
+2 -2
View File
@@ -25,12 +25,12 @@ nym-sphinx-types = { path = "types" }
# those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved
# to separate crate?
nym-crypto = { path = "../crypto", version = "0.3.0" }
nym-crypto = { path = "../crypto", version = "0.4.0" }
nym-topology = { path = "../topology" }
[dev-dependencies]
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-crypto = { path = "../crypto", version = "0.3.0", features = ["asymmetric"] }
nym-crypto = { path = "../crypto", version = "0.4.0", features = ["asymmetric"] }
# do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require
# net2 via tokio-util -> tokio -> mio -> net2
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-pemstore"
description = "Store private-public keypairs in PEM format"
version = "0.2.0"
version = "0.3.0"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
@@ -13,7 +13,7 @@ mod inbound;
mod outbound;
// TODO: make this configurable
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(30);
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
// Send empty keepalive messages regurarly to keep the connection alive. This should be smaller
// than [`MIX_TTL`].
+13 -5
View File
@@ -1319,7 +1319,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -1330,7 +1330,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"bs58",
"ed25519-dalek",
@@ -1378,7 +1378,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -1455,7 +1455,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"pem",
]
@@ -1465,6 +1465,7 @@ name = "nym-service-provider-directory"
version = "0.1.0"
dependencies = [
"anyhow",
"bs58",
"cosmwasm-std",
"cw-controllers",
"cw-multi-test",
@@ -1472,7 +1473,10 @@ dependencies = [
"cw-utils",
"cw2",
"nym-contracts-common",
"nym-crypto",
"nym-service-provider-directory-common",
"rand_chacha 0.2.2",
"rstest",
"semver",
"serde",
"thiserror",
@@ -1484,8 +1488,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -1522,7 +1530,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
+3 -3
View File
@@ -22,9 +22,9 @@ 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.5.0" }
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.6.0" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.7.0" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
+1 -1
View File
@@ -14,7 +14,7 @@ mod mixnet_contract_settings;
mod mixnodes;
mod queued_migrations;
mod rewards;
mod signing;
pub mod signing;
mod support;
#[cfg(feature = "contract-testing")]
+1 -1
View File
@@ -12,7 +12,7 @@ cw-controllers = { workspace = true }
cw-storage-plus = { workspace = true }
cw-utils = { workspace = true }
cw2 = { workspace = true }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" }
semver = { version = "1.0.16", default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
+1 -1
View File
@@ -188,6 +188,7 @@ mod tests {
use super::*;
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
#[rstest::fixture]
fn deps() -> TestDeps {
instantiate_test_contract()
@@ -691,7 +692,6 @@ mod tests {
#[test]
#[ignore]
fn max_page_limit_is_applied() {
// WIP(JON)
todo!();
}
}
@@ -7,12 +7,13 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = { workspace = true }
cw-controllers = { workspace = true }
cw-storage-plus = { workspace = true }
cw-utils = { workspace = true }
cw2 = { workspace = true }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
nym-service-provider-directory-common = { path = "../../common/cosmwasm-smart-contracts/service-provider-directory" }
semver = { version = "1.0.16", default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
@@ -22,5 +23,8 @@ thiserror = "1.0.39"
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
[dev-dependencies]
cw-multi-test = { workspace = true }
anyhow = "1.0.40"
cw-multi-test = { workspace = true }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
rand_chacha = "0.2"
rstest = "0.17.0"
@@ -13,3 +13,5 @@ pub const SERVICE_ID_COUNTER_KEY: &str = "sidc";
pub const SERVICES_PK_NAMESPACE: &str = "sernames";
pub const SERVICES_ANNOUNCER_IDX_NAMESPACE: &str = "serown";
pub const SERVICES_NYM_ADDRESS_IDX_NAMESPACE: &str = "sernyma";
pub const SIGNING_NONCES_NAMESPACE: &str = "sn";
@@ -1,6 +1,6 @@
use crate::{
error::{ContractError, Result},
state::{self, Config},
Result, SpContractError,
};
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};
use nym_service_provider_directory_common::msg::{
@@ -34,22 +34,25 @@ pub fn instantiate(
.add_attribute("admin", info.sender))
}
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
pub fn migrate(
deps: DepsMut<'_>,
_env: Env,
_msg: MigrateMsg,
) -> Result<Response, SpContractError> {
// 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 version: Version = CONTRACT_VERSION.parse().map_err(|error: semver::Error| {
SpContractError::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 {
.map_err(|error: semver::Error| SpContractError::SemVerFailure {
value: storage_version_raw,
error_message: error.to_string(),
})?;
@@ -69,12 +72,12 @@ pub fn execute(
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
) -> Result<Response, SpContractError> {
match msg {
ExecuteMsg::Announce {
nym_address: client_address,
service_type,
} => execute::announce(deps, env, info, client_address, service_type),
service,
owner_signature,
} => execute::announce(deps, env, info, service, owner_signature),
ExecuteMsg::DeleteId { service_id } => execute::delete_id(deps, info, service_id),
ExecuteMsg::DeleteNymAddress { nym_address } => {
execute::delete_nym_address(deps, info, nym_address)
@@ -95,6 +98,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<Binary> {
QueryMsg::All { limit, start_after } => {
to_binary(&query::query_all_paged(deps, limit, start_after)?)
}
QueryMsg::SigningNonce { address } => {
to_binary(&query::query_current_signing_nonce(deps, address)?)
}
QueryMsg::Config {} => to_binary(&query::query_config(deps)?),
QueryMsg::GetContractVersion {} => to_binary(&query::query_contract_version()),
QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?),
@@ -107,16 +113,19 @@ mod tests {
use super::*;
use crate::test_helpers::{
assert::{assert_config, assert_empty, assert_not_found, assert_service, assert_services},
fixture::service_fixture,
helpers::{get_attribute, nyms},
assert::{
assert_config, assert_current_nonce, assert_empty, assert_not_found, assert_service,
assert_services,
},
fixture::new_service_details_with_sign,
helpers::{get_attribute, nyms, test_rng},
};
use cosmwasm_std::{
testing::{mock_dependencies, mock_env, mock_info},
Addr, Coin,
};
use nym_service_provider_directory_common::{msg::ExecuteMsg, ServiceId, ServiceInfo};
use nym_service_provider_directory_common::{msg::ExecuteMsg, Service, ServiceId};
const DENOM: &str = "unym";
@@ -140,27 +149,33 @@ mod tests {
}
#[test]
fn announce_fails_incorrect_deposit() {
fn announce_fails_incorrect_deposit_too_small() {
let mut rng = test_rng();
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(nyms(100));
let info = mock_info("creator", &[]);
let admin = info.sender.clone();
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Announce
let msg: ExecuteMsg = service_fixture().into();
let announcer = service_fixture().announcer.to_string();
// Setup service
let deposit = nyms(99);
let announcer = "steve";
let (service, owner_signature) =
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", announcer, deposit);
let msg = ExecuteMsg::Announce {
service,
owner_signature,
};
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(&announcer, &[nyms(99)]),
mock_info(announcer, &[nyms(99)]),
msg.clone()
)
.unwrap_err(),
ContractError::InsufficientDeposit {
SpContractError::InsufficientDeposit {
funds: 99u128.into(),
deposit_required: 100u128.into(),
}
@@ -170,11 +185,55 @@ mod tests {
execute(
deps.as_mut(),
mock_env(),
mock_info(&announcer, &[nyms(101)]),
msg
mock_info(announcer, &[nyms(100)]),
msg,
)
.unwrap_err(),
ContractError::TooLargeDeposit {
SpContractError::InvalidEd25519Signature,
);
}
// Announcing a service fails due to the signed deposit being different from the deposit in
// the message.
#[test]
fn announce_fails_incorrect_deposit_too_large() {
let mut rng = test_rng();
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(nyms(100));
let info = mock_info("creator", &[]);
let admin = info.sender.clone();
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Setup service
let deposit = nyms(101);
let announcer = "steve";
let (service, owner_signature) =
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", announcer, deposit);
let msg = ExecuteMsg::Announce {
service,
owner_signature,
};
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(announcer, &[nyms(100)]),
msg.clone()
)
.unwrap_err(),
SpContractError::InvalidEd25519Signature,
);
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(announcer, &[nyms(101)]),
msg,
)
.unwrap_err(),
SpContractError::TooLargeDeposit {
funds: 101u128.into(),
deposit_required: 100u128.into(),
}
@@ -186,15 +245,26 @@ mod tests {
#[test]
fn announce_success() {
let mut rng = test_rng();
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(nyms(100));
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
assert_current_nonce(deps.as_ref(), &Addr::unchecked("steve"), 0);
// Setup service
let deposit = nyms(100);
let owner = "steve";
let (service, owner_signature) =
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", owner, deposit.clone());
// Announce
let msg: ExecuteMsg = service_fixture().into();
let info = mock_info("steve", &[nyms(100)]);
let msg = ExecuteMsg::Announce {
service: service.clone(),
owner_signature,
};
let info = mock_info("steve", &[deposit.clone()]);
let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
// Check that the service has had service id assigned to it
@@ -208,10 +278,17 @@ mod tests {
"network_requester".to_string()
);
// Check that the nonce has been incremented, but only for the owner
assert_current_nonce(deps.as_ref(), &Addr::unchecked("steve"), 1);
assert_current_nonce(deps.as_ref(), &Addr::unchecked("timmy"), 0);
// The expected announced service
let expected_service = ServiceInfo {
let expected_service = Service {
service_id: expected_id,
service: service_fixture(),
service,
announcer: Addr::unchecked("steve"),
block_height: 12345,
deposit,
};
assert_services(deps.as_ref(), &[expected_service.clone()]);
assert_service(deps.as_ref(), &expected_service);
@@ -219,6 +296,7 @@ mod tests {
#[test]
fn delete() {
let mut rng = test_rng();
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(Coin::new(100, "unym"));
let info = mock_info("creator", &[]);
@@ -226,16 +304,25 @@ mod tests {
assert_eq!(res.messages.len(), 0);
// Announce
let msg: ExecuteMsg = service_fixture().into();
let info_steve = mock_info("steve", &[nyms(100)]);
assert_eq!(info_steve.sender, service_fixture().announcer);
execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap();
let deposit = nyms(100);
let steve = "steve";
let (service, owner_signature) =
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", steve, deposit.clone());
let msg = ExecuteMsg::Announce {
service: service.clone(),
owner_signature,
};
let info_steve = mock_info(steve, &[deposit.clone()]);
execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap();
// The expected announced service
let expected_id = 1;
let expected_service = ServiceInfo {
let expected_service = Service {
service_id: expected_id,
service: service_fixture(),
service,
announcer: Addr::unchecked(steve),
block_height: 12345,
deposit,
};
assert_services(deps.as_ref(), &[expected_service]);
@@ -244,27 +331,23 @@ mod tests {
let info_timmy = mock_info("timmy", &[]);
assert_eq!(
execute(deps.as_mut(), mock_env(), info_timmy, msg).unwrap_err(),
ContractError::Unauthorized {
SpContractError::Unauthorized {
sender: Addr::unchecked("timmy")
}
);
// Removing an non-existent service will fail
let msg = ExecuteMsg::delete_id(expected_id + 1);
let info_announcer = MessageInfo {
sender: service_fixture().announcer,
funds: vec![],
};
assert_eq!(
execute(deps.as_mut(), mock_env(), info_announcer.clone(), msg).unwrap_err(),
ContractError::NotFound {
execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap_err(),
SpContractError::NotFound {
service_id: expected_id + 1
}
);
// Remove as correct announcer succeeds
let msg = ExecuteMsg::delete_id(expected_id);
let res = execute(deps.as_mut(), mock_env(), info_announcer, msg).unwrap();
let res = execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap();
assert_eq!(
get_attribute(&res, "delete_id", "service_id"),
expected_id.to_string()
@@ -1,24 +1,28 @@
use crate::{
constants::{MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS, MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER},
error::{ContractError, Result},
state,
state, Result, SpContractError,
};
use cosmwasm_std::{Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Response, Uint128};
use nym_contracts_common::{
signing::{MessageSignature, Verifier},
IdentityKey,
};
use nym_service_provider_directory_common::{
events::{new_announce_event, new_delete_id_event, new_update_deposit_required_event},
NymAddress, Service, ServiceId, ServiceType,
signing_types::construct_service_provider_announce_sign_payload,
NymAddress, Service, ServiceDetails, ServiceId,
};
use super::query;
fn ensure_correct_deposit(will_deposit: Uint128, deposit_required: Uint128) -> Result<()> {
match will_deposit.cmp(&deposit_required) {
std::cmp::Ordering::Less => Err(ContractError::InsufficientDeposit {
std::cmp::Ordering::Less => Err(SpContractError::InsufficientDeposit {
funds: will_deposit,
deposit_required,
}),
std::cmp::Ordering::Equal => Ok(()),
std::cmp::Ordering::Greater => Err(ContractError::TooLargeDeposit {
std::cmp::Ordering::Greater => Err(SpContractError::TooLargeDeposit {
funds: will_deposit,
deposit_required,
}),
@@ -30,7 +34,7 @@ fn ensure_max_services_per_announcer(deps: Deps, announcer: Addr) -> Result<()>
if current_entries.services.len() < MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER as usize {
Ok(())
} else {
Err(ContractError::ReachedMaxProvidersForAdmin {
Err(SpContractError::ReachedMaxProvidersForAdmin {
max_providers: MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER,
announcer,
})
@@ -42,7 +46,7 @@ fn ensure_max_aliases_per_nym_address(deps: Deps, nym_address: NymAddress) -> Re
if current_entries.services.len() < MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS as usize {
Ok(())
} else {
Err(ContractError::ReachedMaxAliasesForNymAddress {
Err(SpContractError::ReachedMaxAliasesForNymAddress {
max_aliases: MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS,
nym_address,
})
@@ -50,10 +54,10 @@ fn ensure_max_aliases_per_nym_address(deps: Deps, nym_address: NymAddress) -> Re
}
fn ensure_service_exists(deps: Deps, service_id: ServiceId) -> Result<()> {
if state::services::has_service(deps.storage, service_id) {
if state::has_service(deps.storage, service_id) {
Ok(())
} else {
Err(ContractError::NotFound { service_id })
Err(SpContractError::NotFound { service_id })
}
}
@@ -61,7 +65,7 @@ fn ensure_sender_authorized(info: MessageInfo, service: &Service) -> Result<()>
if info.sender == service.announcer {
Ok(())
} else {
Err(ContractError::Unauthorized {
Err(SpContractError::Unauthorized {
sender: info.sender,
})
}
@@ -74,31 +78,82 @@ fn return_deposit(service_to_delete: &Service) -> BankMsg {
}
}
fn verify_announce_signature(
deps: Deps<'_>,
sender: Addr,
deposit: Coin,
service: ServiceDetails,
signature: MessageSignature,
) -> Result<()> {
// recover the public key
let public_key = decode_ed25519_identity_key(&service.identity_key)?;
// reconstruct the payload
let nonce = state::get_signing_nonce(deps.storage, sender.clone())?;
let msg = construct_service_provider_announce_sign_payload(nonce, sender, deposit, service);
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
} else {
Err(SpContractError::InvalidEd25519Signature)
}
}
fn decode_ed25519_identity_key(encoded: &IdentityKey) -> Result<[u8; 32]> {
let mut public_key = [0u8; 32];
let used = bs58::decode(encoded)
.into(&mut public_key)
.map_err(|err| SpContractError::MalformedEd25519IdentityKey(err.to_string()))?;
if used != 32 {
return Err(SpContractError::MalformedEd25519IdentityKey(
"Too few bytes provided for the public key".into(),
));
}
Ok(public_key)
}
/// Announce a new service. It will be assigned a new service provider id.
pub fn announce(
deps: DepsMut,
env: Env,
info: MessageInfo,
nym_address: NymAddress,
service_type: ServiceType,
service: ServiceDetails,
owner_signature: MessageSignature,
) -> Result<Response> {
ensure_max_services_per_announcer(deps.as_ref(), info.sender.clone())?;
ensure_max_aliases_per_nym_address(deps.as_ref(), nym_address.clone())?;
ensure_max_aliases_per_nym_address(deps.as_ref(), service.nym_address.clone())?;
let deposit_required = state::deposit_required(deps.storage)?;
let denom = deposit_required.denom.clone();
let will_deposit = cw_utils::must_pay(&info, &denom)
.map_err(|err| ContractError::DepositRequired { source: err })?;
.map_err(|err| SpContractError::DepositRequired { source: err })?;
ensure_correct_deposit(will_deposit, deposit_required.amount)?;
let deposit = Coin::new(will_deposit.u128(), denom);
// Check that the sender actually owns the service provider by checking the signature
verify_announce_signature(
deps.as_ref(),
info.sender.clone(),
deposit.clone(),
service.clone(),
owner_signature,
)?;
state::increment_signing_nonce(deps.storage, info.sender.clone())?;
let service_id = state::next_service_id_counter(deps.storage)?;
let new_service = Service {
nym_address,
service_type,
service_id,
service,
announcer: info.sender,
block_height: env.block.height,
deposit: Coin::new(will_deposit.u128(), denom),
deposit,
};
let service_id = state::services::save(deps.storage, &new_service)?;
state::save(deps.storage, &new_service)?;
Ok(Response::new().add_event(new_announce_event(service_id, new_service)))
}
@@ -106,15 +161,15 @@ pub fn announce(
/// Delete an exsisting service.
pub fn delete_id(deps: DepsMut, info: MessageInfo, service_id: ServiceId) -> Result<Response> {
ensure_service_exists(deps.as_ref(), service_id)?;
let service_to_delete = state::services::load_id(deps.storage, service_id)?;
let service_to_delete = state::load_id(deps.storage, service_id)?;
ensure_sender_authorized(info, &service_to_delete)?;
state::services::remove(deps.storage, service_id)?;
state::remove(deps.storage, service_id)?;
let return_deposit_msg = return_deposit(&service_to_delete);
Ok(Response::new()
.add_message(return_deposit_msg)
.add_event(new_delete_id_event(service_id, service_to_delete)))
.add_event(new_delete_id_event(service_to_delete)))
}
/// Delete an existing service by nym address. If there are multiple entries for a given nym
@@ -128,15 +183,12 @@ pub(crate) fn delete_nym_address(
let services_to_delete = query::query_nym_address(deps.as_ref(), nym_address)?.services;
for service_to_delete in services_to_delete {
if info.sender == service_to_delete.service.announcer {
state::services::remove(deps.storage, service_to_delete.service_id)?;
let return_deposit_msg = return_deposit(&service_to_delete.service);
if info.sender == service_to_delete.announcer {
state::remove(deps.storage, service_to_delete.service_id)?;
let return_deposit_msg = return_deposit(&service_to_delete);
response = response
.add_message(return_deposit_msg)
.add_event(new_delete_id_event(
service_to_delete.service_id,
service_to_delete.service,
));
.add_event(new_delete_id_event(service_to_delete));
}
}
Ok(response)
@@ -1,31 +1,27 @@
use cosmwasm_std::Deps;
use nym_contracts_common::ContractBuildInformation;
use nym_contracts_common::{signing::Nonce, ContractBuildInformation};
use nym_service_provider_directory_common::{
response::{ConfigResponse, PagedServicesListResponse, ServicesListResponse},
NymAddress, ServiceId, ServiceInfo,
NymAddress, Service, ServiceId,
};
use crate::{
error::Result,
state::{self, services::PagedLoad},
state::{self, PagedLoad},
Result,
};
pub fn query_id(deps: Deps, service_id: ServiceId) -> Result<ServiceInfo> {
let service = state::services::load_id(deps.storage, service_id)?;
Ok(ServiceInfo {
service_id,
service,
})
pub fn query_id(deps: Deps, service_id: ServiceId) -> Result<Service> {
state::load_id(deps.storage, service_id)
}
pub fn query_announcer(deps: Deps, announcer: String) -> Result<ServicesListResponse> {
let announcer = deps.api.addr_validate(&announcer)?;
let services = state::services::load_announcer(deps.storage, announcer)?;
let services = state::load_announcer(deps.storage, announcer)?;
Ok(ServicesListResponse::new(services))
}
pub fn query_nym_address(deps: Deps, nym_address: NymAddress) -> Result<ServicesListResponse> {
let services = state::services::load_nym_address(deps.storage, nym_address)?;
let services = state::load_nym_address(deps.storage, nym_address)?;
Ok(ServicesListResponse::new(services))
}
@@ -38,7 +34,7 @@ pub fn query_all_paged(
services,
limit,
start_next_after,
} = state::services::load_all_paged(deps.storage, limit, start_after)?;
} = state::load_all_paged(deps.storage, limit, start_after)?;
Ok(PagedServicesListResponse::new(
services,
limit,
@@ -46,6 +42,11 @@ pub fn query_all_paged(
))
}
pub fn query_current_signing_nonce(deps: Deps<'_>, address: String) -> Result<Nonce> {
let address = deps.api.addr_validate(&address)?;
state::get_signing_nonce(deps.storage, address)
}
pub fn query_config(deps: Deps) -> Result<ConfigResponse> {
let config = state::load_config(deps.storage)?;
Ok(config.into())
@@ -1,355 +0,0 @@
//! Integration tests using cw-multi-test.
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::{
response::{ConfigResponse, PagedServicesListResponse},
NymAddress, Service, ServiceInfo, ServiceType,
};
use crate::{
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
error::ContractError,
test_helpers::{fixture::service_info, helpers::nyms, test_setup::TestSetup},
};
#[test]
fn instantiate_contract() {
TestSetup::new();
}
#[test]
fn query_config() {
assert_eq!(
TestSetup::new().query_config(),
ConfigResponse {
deposit_required: nyms(100),
}
);
}
#[test]
fn announce_and_query_service() {
let mut setup = TestSetup::new();
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: None,
}
);
// Announce a first service
let announcer = Addr::unchecked("announcer");
let nym_address = NymAddress::new("nymAddress");
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&announcer), nyms(250));
setup.announce_net_req(nym_address.clone(), announcer.clone());
// Deposit is deposited to contract and deducted from announcers's balance
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance(&announcer), nyms(150));
// We can query the full service list
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![ServiceInfo {
service_id: 1,
service: Service {
nym_address: nym_address.clone(),
service_type: ServiceType::NetworkRequester,
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
},
}],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(1),
}
);
// ... and we can query by id
assert_eq!(
setup.query_id(1),
ServiceInfo {
service_id: 1,
service: Service {
nym_address: nym_address.clone(),
service_type: ServiceType::NetworkRequester,
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
},
}
);
// Announce a second service
let announcer2 = Addr::unchecked("announcer2");
let nym_address2 = NymAddress::new("nymAddress2");
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
assert_eq!(setup.contract_balance(), nyms(200));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
service_info(1, nym_address, announcer),
service_info(2, nym_address2, announcer2)
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(2),
}
);
}
#[test]
fn delete_service() {
let mut setup = TestSetup::new();
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance("announcer"), nyms(150));
assert!(!setup.query_all().services.is_empty());
setup.delete(1, Addr::unchecked("announcer"));
// Deleting the service returns the deposit to the announcer
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance("announcer"), nyms(250));
assert!(setup.query_all().services.is_empty());
}
#[test]
fn only_announcer_can_delete_service() {
let mut setup = TestSetup::new();
assert_eq!(setup.contract_balance(), nyms(0));
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().services.is_empty());
let delete_resp: ContractError = setup
.try_delete(1, Addr::unchecked("not_announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp,
ContractError::Unauthorized {
sender: Addr::unchecked("not_announcer")
}
);
}
#[test]
fn cant_delete_service_that_does_not_exist() {
let mut setup = TestSetup::new();
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().services.is_empty());
let delete_resp: ContractError = setup
.try_delete(0, Addr::unchecked("announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(delete_resp, ContractError::NotFound { service_id: 0 });
let delete_resp: ContractError = setup
.try_delete(2, Addr::unchecked("announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(delete_resp, ContractError::NotFound { service_id: 2 });
assert!(!setup.query_all().services.is_empty());
setup.delete(1, Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(0));
assert!(setup.query_all().services.is_empty());
}
#[test]
fn announce_multiple_services_and_deleting_by_name() {
let mut setup = TestSetup::new();
let announcer1 = Addr::unchecked("wealthy_announcer_1");
let announcer2 = Addr::unchecked("wealthy_announcer_2");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
// We announce the same address three times, but with different annoucers
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&announcer1), nyms(1000));
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
setup.announce_net_req(nym_address2.clone(), announcer1.clone());
setup.announce_net_req(nym_address1.clone(), announcer2.clone());
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
assert_eq!(setup.contract_balance(), nyms(500));
assert_eq!(setup.balance(&announcer1), nyms(700));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
service_info(1, nym_address1.clone(), announcer1.clone()),
service_info(2, nym_address1.clone(), announcer1.clone()),
service_info(3, nym_address2.clone(), announcer1.clone()),
service_info(4, nym_address1.clone(), announcer2.clone()),
service_info(5, nym_address2.clone(), announcer2.clone()),
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
// Even though multiple of them point to the same nym address, we only delete the ones we actually
// own.
setup.delete_nym_address(nym_address1.clone(), announcer1.clone());
assert_eq!(setup.contract_balance(), nyms(300));
assert_eq!(setup.balance(&announcer1), nyms(900));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
service_info(3, nym_address2.clone(), announcer1),
service_info(4, nym_address1, announcer2.clone()),
service_info(5, nym_address2, announcer2),
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
}
// add multiple services, then query all but with a paging limit less than the number of services
// added
#[test]
fn paging_works() {
let mut setup = TestSetup::new();
let announcer1 = Addr::unchecked("wealthy_announcer_1");
let announcer2 = Addr::unchecked("wealthy_announcer_2");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
// We announce the same address three times, but with different announcers
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
setup.announce_net_req(nym_address2.clone(), announcer1.clone());
setup.announce_net_req(nym_address1.clone(), announcer2.clone());
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
assert_eq!(
setup.query_all_with_limit(Some(10), None),
PagedServicesListResponse {
services: vec![
service_info(1, nym_address1.clone(), announcer1.clone()),
service_info(2, nym_address1.clone(), announcer1.clone()),
service_info(3, nym_address2.clone(), announcer1.clone()),
service_info(4, nym_address1.clone(), announcer2.clone()),
service_info(5, nym_address2.clone(), announcer2.clone()),
],
per_page: 10,
start_next_after: Some(5),
}
);
assert_eq!(
setup.query_all_with_limit(Some(3), None),
PagedServicesListResponse {
services: vec![
service_info(1, nym_address1.clone(), announcer1.clone()),
service_info(2, nym_address1.clone(), announcer1.clone()),
service_info(3, nym_address2.clone(), announcer1),
],
per_page: 3,
start_next_after: Some(3),
}
);
assert_eq!(
setup.query_all_with_limit(Some(3), Some(3)),
PagedServicesListResponse {
services: vec![
service_info(4, nym_address1, announcer2.clone()),
service_info(5, nym_address2, announcer2),
],
per_page: 3,
start_next_after: Some(5),
}
);
}
#[test]
fn service_id_increases_for_new_services() {
let mut setup = TestSetup::new();
setup.announce_net_req(
NymAddress::new("nymAddress1"),
Addr::unchecked("announcer1"),
);
setup.announce_net_req(
NymAddress::new("nymAddress2"),
Addr::unchecked("announcer2"),
);
assert_eq!(
setup
.query_all()
.services
.iter()
.map(|s| s.service_id)
.collect::<Vec<_>>(),
vec![1, 2],
);
}
#[test]
fn service_id_is_not_resused_when_deleting_and_then_adding_a_new_service() {
let mut setup = TestSetup::new();
setup.announce_net_req(
NymAddress::new("nymAddress1"),
Addr::unchecked("announcer1"),
);
setup.announce_net_req(
NymAddress::new("nymAddress2"),
Addr::unchecked("announcer2"),
);
setup.announce_net_req(
NymAddress::new("nymAddress3"),
Addr::unchecked("announcer3"),
);
setup.delete(1, Addr::unchecked("announcer1"));
setup.delete(3, Addr::unchecked("announcer3"));
assert_eq!(
setup.query_all().services,
vec![service_info(
2,
NymAddress::new("nymAddress2"),
Addr::unchecked("announcer2")
)]
);
setup.announce_net_req(
NymAddress::new("nymAddress4"),
Addr::unchecked("announcer4"),
);
assert_eq!(
setup.query_all().services,
vec![
service_info(
2,
NymAddress::new("nymAddress2"),
Addr::unchecked("announcer2")
),
service_info(
4,
NymAddress::new("nymAddress4"),
Addr::unchecked("announcer4")
)
]
);
}
@@ -0,0 +1,183 @@
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::{
response::PagedServicesListResponse, NymAddress, Service, ServiceDetails, ServiceType,
};
use rstest::rstest;
use crate::{
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
test_helpers::{fixture::new_service, helpers::nyms},
SpContractError,
};
use super::test_setup::TestSetup;
#[rstest::fixture]
fn setup() -> TestSetup {
TestSetup::new()
}
#[rstest]
fn basic_announce(mut setup: TestSetup) {
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: None,
}
);
// Announce a first service
let announcer = Addr::unchecked("announcer");
let nym_address = NymAddress::new("nymAddress");
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&announcer), nyms(250));
assert_eq!(setup.query_signing_nonce(announcer.to_string()), 0);
let service = setup.new_service(&nym_address);
let payload = setup.payload_to_sign(&announcer, &nyms(100), &service.service);
let service = service.sign(payload);
setup.announce_net_req(&service, &announcer);
// Deposit is deposited to contract and deducted from announcers's balance
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance(&announcer), nyms(150));
// The signing nonce has been incremented
assert_eq!(setup.query_signing_nonce(announcer.to_string()), 1);
// We can query the full service list
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![Service {
service_id: 1,
service: ServiceDetails {
nym_address: nym_address.clone(),
service_type: ServiceType::NetworkRequester,
identity_key: service.identity_key().to_string(),
},
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
}],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(1),
}
);
// ... and we can query by id
assert_eq!(
setup.query_id(1),
Service {
service_id: 1,
service: service.details().clone(),
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
}
);
// Announce a second service
let announcer2 = Addr::unchecked("announcer2");
let nym_address2 = NymAddress::new("nymAddress2");
let service2 = setup.new_signed_service(&nym_address2, &announcer2, &nyms(100));
setup.announce_net_req(&service2, &announcer2);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 1);
assert_eq!(setup.contract_balance(), nyms(200));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
new_service(1, &nym_address, &announcer, service.identity_key()),
new_service(2, &nym_address2, &announcer2, service2.identity_key())
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(2),
}
);
}
#[rstest]
fn announce_fails_when_announcer_mismatch(mut setup: TestSetup) {
let announcer = Addr::unchecked("steve");
let nym_address = NymAddress::new("foobar");
let service = setup.new_signed_service(&nym_address, &announcer, &nyms(100));
// A difference announcer tries to announce the service
let announcer2 = Addr::unchecked("timmy");
let resp: SpContractError = setup
.try_announce_net_req(&service, &announcer2)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(resp, SpContractError::InvalidEd25519Signature);
}
#[rstest]
fn signing_nonce_is_increased_when_announcing(mut setup: TestSetup) {
let announcer1 = Addr::unchecked("announcer1");
let announcer2 = Addr::unchecked("announcer2");
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 0);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 0);
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress1"), &announcer1, &nyms(100));
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 0);
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress2"), &announcer2, &nyms(100));
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 1);
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress3"), &announcer2, &nyms(100));
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 2);
}
#[rstest]
fn creating_two_services_in_a_row_without_announcing_fails(mut setup: TestSetup) {
let announcer = Addr::unchecked("wealthy_announcer_1");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
let deposit = nyms(100);
let s1 = setup.new_signed_service(&nym_address1, &announcer, &deposit);
// This second service will be signed with the same nonce
let s2 = setup.new_signed_service(&nym_address2, &announcer, &deposit);
// Announce the first service works, and this increments the nonce
setup.announce_net_req(&s1, &announcer);
// Now the nonce has been incremented, and the signature will not match
let resp: SpContractError = setup
.try_announce_net_req(&s2, &announcer)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(resp, SpContractError::InvalidEd25519Signature,);
}
#[rstest]
fn announcing_the_same_service_twice_fails(mut setup: TestSetup) {
let announcer = Addr::unchecked("wealthy_announcer_1");
let nym_address = NymAddress::new("nymAddress1");
let s1 = setup.new_signed_service(&nym_address, &announcer, &nyms(100));
setup.announce_net_req(&s1, &announcer);
// Now the nonce has been incremented, and the signature will not match
let resp: SpContractError = setup
.try_announce_net_req(&s1, &announcer)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(resp, SpContractError::InvalidEd25519Signature);
}
@@ -0,0 +1,144 @@
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::{response::PagedServicesListResponse, NymAddress};
use crate::{
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
test_helpers::{fixture::new_service, helpers::nyms},
SpContractError,
};
use super::test_setup::TestSetup;
#[test]
fn delete_service() {
let mut setup = TestSetup::new();
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress"),
&Addr::unchecked("announcer"),
&nyms(100),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance("announcer"), nyms(150));
assert!(!setup.query_all().services.is_empty());
setup.delete(1, &Addr::unchecked("announcer"));
// Deleting the service returns the deposit to the announcer
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance("announcer"), nyms(250));
assert!(setup.query_all().services.is_empty());
}
#[test]
fn only_announcer_can_delete_service() {
let mut setup = TestSetup::new();
assert_eq!(setup.contract_balance(), nyms(0));
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress"),
&Addr::unchecked("announcer"),
&nyms(100),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().services.is_empty());
let delete_resp: SpContractError = setup
.try_delete(1, &Addr::unchecked("not_announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp,
SpContractError::Unauthorized {
sender: Addr::unchecked("not_announcer")
}
);
}
#[test]
fn cant_delete_service_that_does_not_exist() {
let mut setup = TestSetup::new();
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress"),
&Addr::unchecked("announcer"),
&nyms(100),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().services.is_empty());
let delete_resp: SpContractError = setup
.try_delete(0, &Addr::unchecked("announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(delete_resp, SpContractError::NotFound { service_id: 0 });
let delete_resp: SpContractError = setup
.try_delete(2, &Addr::unchecked("announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(delete_resp, SpContractError::NotFound { service_id: 2 });
assert!(!setup.query_all().services.is_empty());
setup.delete(1, &Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(0));
assert!(setup.query_all().services.is_empty());
}
#[test]
fn announce_multiple_services_and_deleting_by_name() {
let mut setup = TestSetup::new();
let announcer1 = Addr::unchecked("wealthy_announcer_1");
let announcer2 = Addr::unchecked("wealthy_announcer_2");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
let deposit = nyms(100);
// We announce the same address three times, but with different annoucers
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&announcer1), nyms(1000));
let s1 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
let s2 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
let s3 = setup.sign_and_announce_net_req(&nym_address2, &announcer1, &deposit);
let s4 = setup.sign_and_announce_net_req(&nym_address1, &announcer2, &deposit);
let s5 = setup.sign_and_announce_net_req(&nym_address2, &announcer2, &deposit);
assert_eq!(setup.contract_balance(), nyms(500));
assert_eq!(setup.balance(&announcer1), nyms(700));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
// Even though multiple of them point to the same nym address, we only delete the ones we actually
// own.
setup.delete_nym_address(&nym_address1, &announcer1);
assert_eq!(setup.contract_balance(), nyms(300));
assert_eq!(setup.balance(&announcer1), nyms(900));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
}
@@ -0,0 +1,13 @@
//! Integration tests using cw-multi-test.
mod announce;
mod delete;
mod query;
mod service_id;
mod test_service;
mod test_setup;
#[test]
fn instantiate_contract() {
test_setup::TestSetup::new();
}
@@ -0,0 +1,77 @@
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::{
response::{ConfigResponse, PagedServicesListResponse},
NymAddress,
};
use crate::test_helpers::{fixture::new_service, helpers::nyms};
use super::test_setup::TestSetup;
#[test]
fn query_config() {
assert_eq!(
TestSetup::new().query_config(),
ConfigResponse {
deposit_required: nyms(100),
}
);
}
// add multiple services, then query all but with a paging limit less than the number of services
// added
#[test]
fn paging_works() {
let mut setup = TestSetup::new();
let announcer1 = Addr::unchecked("wealthy_announcer_1");
let announcer2 = Addr::unchecked("wealthy_announcer_2");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
let deposit = nyms(100);
// We announce the same address three times, but with different announcers
let s1 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
let s2 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
let s3 = setup.sign_and_announce_net_req(&nym_address2, &announcer1, &deposit);
let s4 = setup.sign_and_announce_net_req(&nym_address1, &announcer2, &deposit);
let s5 = setup.sign_and_announce_net_req(&nym_address2, &announcer2, &deposit);
assert_eq!(
setup.query_all_with_limit(Some(10), None),
PagedServicesListResponse {
services: vec![
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
],
per_page: 10,
start_next_after: Some(5),
}
);
assert_eq!(
setup.query_all_with_limit(Some(3), None),
PagedServicesListResponse {
services: vec![
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
],
per_page: 3,
start_next_after: Some(3),
}
);
assert_eq!(
setup.query_all_with_limit(Some(3), Some(3)),
PagedServicesListResponse {
services: vec![
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
],
per_page: 3,
start_next_after: Some(5),
}
);
}
@@ -0,0 +1,89 @@
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::NymAddress;
use crate::test_helpers::{fixture::new_service, helpers::nyms};
use super::test_setup::TestSetup;
#[test]
fn service_id_increases_for_new_services() {
let mut setup = TestSetup::new();
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress1"),
&Addr::unchecked("announcer1"),
&nyms(100),
);
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress2"),
&Addr::unchecked("announcer2"),
&nyms(100),
);
assert_eq!(
setup
.query_all()
.services
.iter()
.map(|s| s.service_id)
.collect::<Vec<_>>(),
vec![1, 2],
);
}
#[test]
fn service_id_is_not_resused_when_deleting_and_then_adding_a_new_service() {
let mut setup = TestSetup::new();
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress1"),
&Addr::unchecked("announcer1"),
&nyms(100),
);
let s2 = setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress2"),
&Addr::unchecked("announcer2"),
&nyms(100),
);
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress3"),
&Addr::unchecked("announcer3"),
&nyms(100),
);
setup.delete(1, &Addr::unchecked("announcer1"));
setup.delete(3, &Addr::unchecked("announcer3"));
assert_eq!(
setup.query_all().services,
vec![new_service(
2,
&NymAddress::new("nymAddress2"),
&Addr::unchecked("announcer2"),
s2.identity_key(),
)]
);
let s4 = setup.new_signed_service(
&NymAddress::new("nymAddress4"),
&Addr::unchecked("announcer4"),
&nyms(100),
);
setup.announce_net_req(&s4, &Addr::unchecked("announcer4"));
assert_eq!(
setup.query_all().services,
vec![
new_service(
2,
&NymAddress::new("nymAddress2"),
&Addr::unchecked("announcer2"),
s2.identity_key(),
),
new_service(
4,
&NymAddress::new("nymAddress4"),
&Addr::unchecked("announcer4"),
s4.identity_key(),
)
]
);
}
@@ -0,0 +1,75 @@
use nym_contracts_common::{signing::MessageSignature, IdentityKey};
use nym_crypto::asymmetric::identity;
use nym_service_provider_directory_common::{
signing_types::SignableServiceProviderAnnounceMsg, NymAddress, ServiceDetails, ServiceType,
};
use rand_chacha::ChaCha20Rng;
use crate::test_helpers::signing::ed25519_sign_message;
pub struct TestService {
pub service: ServiceDetails,
pub keys: identity::KeyPair,
pub rng: ChaCha20Rng,
}
impl TestService {
pub fn new(rng: &mut ChaCha20Rng, nym_address: NymAddress) -> Self {
let keys = identity::KeyPair::new(rng);
let service = ServiceDetails {
nym_address,
service_type: ServiceType::NetworkRequester,
identity_key: keys.public_key().to_base58_string(),
};
Self {
service,
keys,
rng: rng.clone(),
}
}
pub fn identity_key(&self) -> &IdentityKey {
&self.service.identity_key
}
pub fn details(&self) -> &ServiceDetails {
&self.service
}
pub fn sign(self, payload: SignableServiceProviderAnnounceMsg) -> SignedTestService {
let owner_signature = ed25519_sign_message(payload, self.keys.private_key());
SignedTestService {
service: self.service,
keys: self.keys,
owner_signature,
}
}
}
impl From<TestService> for ServiceDetails {
fn from(test_service: TestService) -> Self {
test_service.service
}
}
pub struct SignedTestService {
pub service: ServiceDetails,
pub keys: identity::KeyPair,
pub owner_signature: MessageSignature,
}
impl SignedTestService {
pub fn identity_key(&self) -> &IdentityKey {
&self.service.identity_key
}
pub fn details(&self) -> &ServiceDetails {
&self.service
}
}
impl From<SignedTestService> for ServiceDetails {
fn from(signed_service: SignedTestService) -> Self {
signed_service.service
}
}
@@ -1,14 +1,21 @@
use anyhow::Result;
use cosmwasm_std::{coins, Addr, Coin, Uint128};
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
use nym_contracts_common::signing::Nonce;
use nym_service_provider_directory_common::{
msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
response::{ConfigResponse, PagedServicesListResponse},
NymAddress, ServiceId, ServiceInfo, ServiceType,
signing_types::{
construct_service_provider_announce_sign_payload, SignableServiceProviderAnnounceMsg,
},
NymAddress, Service, ServiceDetails, ServiceId,
};
use rand_chacha::ChaCha20Rng;
use serde::de::DeserializeOwned;
use crate::test_helpers::helpers::get_app_attribute;
use crate::test_helpers::helpers::{get_app_attribute, test_rng};
use super::test_service::{SignedTestService, TestService};
const DENOM: &str = "unym";
const ADDRESSES: &[&str] = &[
@@ -19,6 +26,8 @@ const ADDRESSES: &[&str] = &[
"announcer2",
"announcer3",
"announcer4",
"steve",
"timmy",
];
const WEALTHY_ADDRESSES: &[&str] = &["wealthy_announcer_1", "wealthy_announcer_2"];
@@ -26,6 +35,7 @@ const WEALTHY_ADDRESSES: &[&str] = &["wealthy_announcer_1", "wealthy_announcer_2
pub struct TestSetup {
app: App,
addr: Addr,
rng: ChaCha20Rng,
}
impl Default for TestSetup {
@@ -51,7 +61,8 @@ impl TestSetup {
let code = ContractWrapper::new(crate::execute, crate::instantiate, crate::query);
let code_id = app.store_code(Box::new(code));
let addr = Self::instantiate(&mut app, code_id);
TestSetup { app, addr }
let rng = test_rng();
TestSetup { app, addr, rng }
}
fn instantiate(app: &mut App, code_id: u64) -> Addr {
@@ -68,11 +79,6 @@ impl TestSetup {
.unwrap()
}
#[allow(unused)]
pub fn address(&self) -> &Addr {
&self.addr
}
pub fn contract_balance(&self) -> Coin {
self.app.wrap().query_balance(&self.addr, DENOM).unwrap()
}
@@ -88,7 +94,7 @@ impl TestSetup {
self.query(&QueryMsg::Config {})
}
pub fn query_id(&self, service_id: ServiceId) -> ServiceInfo {
pub fn query_id(&self, service_id: ServiceId) -> Service {
self.query(&QueryMsg::ServiceId { service_id })
}
@@ -104,22 +110,69 @@ impl TestSetup {
self.query(&QueryMsg::All { limit, start_after })
}
pub fn announce_net_req(&mut self, address: NymAddress, announcer: Addr) -> AppResponse {
let resp = self
.app
.execute_contract(
announcer,
self.addr.clone(),
&ExecuteMsg::Announce {
nym_address: address,
service_type: ServiceType::NetworkRequester,
},
&[Coin {
denom: DENOM.to_string(),
amount: Uint128::new(100),
}],
)
.unwrap();
pub fn query_signing_nonce(&self, address: String) -> Nonce {
self.query(&QueryMsg::SigningNonce { address })
}
// Create a new service, together with its signing keypair
pub fn new_service(&mut self, nym_address: &NymAddress) -> TestService {
TestService::new(&mut self.rng, nym_address.clone())
}
// Create payload for the service operator to sign
pub fn payload_to_sign(
&mut self,
announcer: &Addr,
deposit: &Coin,
service: &ServiceDetails,
) -> SignableServiceProviderAnnounceMsg {
let nonce = self.query_signing_nonce(announcer.to_string());
construct_service_provider_announce_sign_payload(
nonce,
announcer.clone(),
deposit.clone(),
service.clone(),
)
}
// Convenience function for creating a new service and signing it.
pub fn new_signed_service(
&mut self,
nym_address: &NymAddress,
announcer: &Addr,
deposit: &Coin,
) -> SignedTestService {
let service = self.new_service(nym_address);
let payload = self.payload_to_sign(announcer, deposit, service.details());
service.sign(payload)
}
// Announce a new service
pub fn try_announce_net_req(
&mut self,
service: &SignedTestService,
announcer: &Addr,
) -> Result<AppResponse> {
self.app.execute_contract(
announcer.clone(),
self.addr.clone(),
&ExecuteMsg::Announce {
service: service.service.clone(),
owner_signature: service.owner_signature.clone(),
},
&[Coin {
denom: DENOM.to_string(),
amount: Uint128::new(100),
}],
)
}
pub fn announce_net_req(
&mut self,
service: &SignedTestService,
announcer: &Addr,
) -> AppResponse {
let resp = self.try_announce_net_req(service, announcer).unwrap();
assert_eq!(
get_app_attribute(&resp, "wasm-announce", "action"),
"announce"
@@ -127,16 +180,28 @@ impl TestSetup {
resp
}
pub fn try_delete(&mut self, service_id: ServiceId, announcer: Addr) -> Result<AppResponse> {
// Convenience function for create a new signed service and announcing it
pub fn sign_and_announce_net_req(
&mut self,
nym_address: &NymAddress,
announcer: &Addr,
deposit: &Coin,
) -> SignedTestService {
let service = self.new_signed_service(nym_address, announcer, deposit);
let _ = self.announce_net_req(&service, announcer);
service
}
pub fn try_delete(&mut self, service_id: ServiceId, announcer: &Addr) -> Result<AppResponse> {
self.app.execute_contract(
announcer,
announcer.clone(),
self.addr.clone(),
&ExecuteMsg::DeleteId { service_id },
&[],
)
}
pub fn delete(&mut self, service_id: ServiceId, announcer: Addr) -> AppResponse {
pub fn delete(&mut self, service_id: ServiceId, announcer: &Addr) -> AppResponse {
let delete_resp = self.try_delete(service_id, announcer).unwrap();
assert_eq!(
get_app_attribute(&delete_resp, "wasm-delete_id", "action"),
@@ -145,12 +210,18 @@ impl TestSetup {
delete_resp
}
pub fn delete_nym_address(&mut self, nym_address: NymAddress, announcer: Addr) -> AppResponse {
pub fn delete_nym_address(
&mut self,
nym_address: &NymAddress,
announcer: &Addr,
) -> AppResponse {
self.app
.execute_contract(
announcer,
announcer.clone(),
self.addr.clone(),
&ExecuteMsg::DeleteNymAddress { nym_address },
&ExecuteMsg::DeleteNymAddress {
nym_address: nym_address.clone(),
},
&[],
)
.unwrap()
@@ -4,7 +4,8 @@
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use crate::error::Result;
pub use nym_service_provider_directory_common::error::{Result, SpContractError};
use nym_service_provider_directory_common::msg::{
ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
};
@@ -12,14 +13,11 @@ use nym_service_provider_directory_common::msg::{
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response};
use error::ContractError;
mod constants;
mod contract;
mod error;
mod state;
pub mod constants;
#[cfg(test)]
mod integration_tests;
#[cfg(test)]
@@ -38,7 +36,7 @@ pub fn instantiate(
/// Contract entry point for migrations.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, SpContractError> {
contract::migrate(deps, env, msg)
}
@@ -49,7 +47,7 @@ pub fn execute(
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
) -> Result<Response, SpContractError> {
contract::execute(deps, env, info, msg)
}
@@ -1,7 +1,7 @@
use cosmwasm_std::{Addr, Deps, DepsMut};
use cw_controllers::Admin;
use crate::{constants::ADMIN_KEY, error::Result};
use crate::{constants::ADMIN_KEY, Result};
const ADMIN: Admin = Admin::new(ADMIN_KEY);
@@ -3,7 +3,7 @@ use cw_storage_plus::Item;
use nym_service_provider_directory_common::response::ConfigResponse;
use serde::{Deserialize, Serialize};
use crate::{constants::CONFIG_KEY, error::Result};
use crate::{constants::CONFIG_KEY, Result};
const CONFIG: Item<Config> = Item::new(CONFIG_KEY);
@@ -1,8 +1,13 @@
pub mod admin;
pub mod config;
pub mod service_id_counter;
pub mod services;
mod admin;
mod config;
mod nonce;
mod service_id_counter;
mod services;
pub(crate) use admin::{assert_admin, set_admin};
pub(crate) use config::{deposit_required, load_config, save_config, Config};
pub(crate) use nonce::{get_signing_nonce, increment_signing_nonce};
pub(crate) use service_id_counter::next_service_id_counter;
pub(crate) use services::{
has_service, load_all_paged, load_announcer, load_id, load_nym_address, remove, save, PagedLoad,
};
@@ -0,0 +1,71 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{constants::SIGNING_NONCES_NAMESPACE, Result};
use cosmwasm_std::{Addr, Storage};
use cw_storage_plus::Map;
use nym_contracts_common::signing::Nonce;
pub const NONCES: Map<'_, Addr, Nonce> = Map::new(SIGNING_NONCES_NAMESPACE);
pub fn get_signing_nonce(storage: &dyn Storage, address: Addr) -> Result<Nonce> {
let nonce = NONCES.may_load(storage, address)?.unwrap_or(0);
Ok(nonce)
}
fn update_signing_nonce(storage: &mut dyn Storage, address: Addr, value: Nonce) -> Result<()> {
NONCES
.save(storage, address, &value)
.map_err(|err| err.into())
}
pub fn increment_signing_nonce(storage: &mut dyn Storage, address: Addr) -> Result<()> {
// get the current nonce
let nonce = get_signing_nonce(storage, address.clone())?;
// increment it for the next use
update_signing_nonce(storage, address, nonce + 1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::transactions::instantiate_test_contract;
use cosmwasm_std::{
testing::{MockApi, MockQuerier},
MemoryStorage, OwnedDeps,
};
use rstest::rstest;
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
#[rstest::fixture]
fn deps() -> TestDeps {
instantiate_test_contract()
}
fn addr(s: &str) -> Addr {
Addr::unchecked(s)
}
#[rstest]
fn getting_signing_nonce_doesnt_increment_it(deps: TestDeps) {
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
}
#[rstest]
fn increment_works(mut deps: TestDeps) {
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap();
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1);
}
#[rstest]
fn incrementing_is_independent(mut deps: TestDeps) {
increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap();
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1);
assert_eq!(get_signing_nonce(&deps.storage, addr("bjorn")).unwrap(), 0);
}
}
@@ -2,7 +2,7 @@ use cosmwasm_std::Storage;
use cw_storage_plus::Item;
use nym_service_provider_directory_common::ServiceId;
use crate::{constants::SERVICE_ID_COUNTER_KEY, error::Result};
use crate::{constants::SERVICE_ID_COUNTER_KEY, Result};
const SERVICE_ID_COUNTER: Item<ServiceId> = Item::new(SERVICE_ID_COUNTER_KEY);
@@ -17,29 +17,50 @@ pub(crate) fn next_service_id_counter(store: &mut dyn Storage) -> Result<Service
#[cfg(test)]
mod tests {
use nym_service_provider_directory_common::ServiceInfo;
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::Service;
use crate::test_helpers::{
assert::assert_services,
fixture::service_fixture,
helpers::{announce_service, delete_service, instantiate_test_contract},
helpers::{nyms, test_rng},
transactions::{announce_service, delete_service, instantiate_test_contract},
};
#[test]
fn get_next_service_id() {
let mut deps = instantiate_test_contract();
let mut rng = test_rng();
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 1);
assert_services(deps.as_ref(), &[ServiceInfo::new(1, service_fixture())]);
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 2);
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 3);
let (id1, service1) = announce_service(deps.as_mut(), &mut rng, "addr1", "timmy");
let (id2, service2) = announce_service(deps.as_mut(), &mut rng, "addr2", "timmy");
let (id3, service3) = announce_service(deps.as_mut(), &mut rng, "addr3", "timmy");
assert_eq!(id1, 1);
assert_eq!(id2, 2);
assert_eq!(id3, 3);
assert_services(
deps.as_ref(),
&[
ServiceInfo::new(1, service_fixture()),
ServiceInfo::new(2, service_fixture()),
ServiceInfo::new(3, service_fixture()),
Service {
service_id: 1,
service: service1,
announcer: Addr::unchecked("timmy"),
block_height: 12345,
deposit: nyms(100),
},
Service {
service_id: 2,
service: service2,
announcer: Addr::unchecked("timmy"),
block_height: 12345,
deposit: nyms(100),
},
Service {
service_id: 3,
service: service3,
announcer: Addr::unchecked("timmy"),
block_height: 12345,
deposit: nyms(100),
},
],
);
}
@@ -47,31 +68,28 @@ mod tests {
#[test]
fn deleted_service_id_is_not_reused() {
let mut deps = instantiate_test_contract();
let mut rng = test_rng();
// Announce
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 1);
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 2);
let (_, service1) = announce_service(deps.as_mut(), &mut rng, "addr1", "timmy");
let _ = announce_service(deps.as_mut(), &mut rng, "addr2", "timmy");
//// Delete the last entry
delete_service(deps.as_mut(), 2, "timmy");
assert_services(
deps.as_ref(),
&[
ServiceInfo::new(1, service_fixture()),
ServiceInfo::new(2, service_fixture()),
],
&[Service {
service_id: 1,
service: service1,
announcer: Addr::unchecked("timmy"),
block_height: 12345,
deposit: nyms(100),
}],
);
// Delete the last entry
delete_service(deps.as_mut(), 2, "steve");
assert_services(deps.as_ref(), &[ServiceInfo::new(1, service_fixture())]);
// Create a third entry. The index should not reuse the previous entry that we just
// deleted.
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 3);
assert_services(
deps.as_ref(),
&[
ServiceInfo::new(1, service_fixture()),
ServiceInfo::new(3, service_fixture()),
],
);
let (id3, _) = announce_service(deps.as_mut(), &mut rng, "addr3", "timmy");
assert_eq!(id3, 3);
}
}
@@ -8,7 +8,7 @@ use crate::{
SERVICES_ANNOUNCER_IDX_NAMESPACE, SERVICES_NYM_ADDRESS_IDX_NAMESPACE,
SERVICES_PK_NAMESPACE, SERVICE_DEFAULT_RETRIEVAL_LIMIT, SERVICE_MAX_RETRIEVAL_LIMIT,
},
error::{ContractError, Result},
Result, SpContractError,
};
struct ServiceIndex<'a> {
@@ -26,7 +26,7 @@ impl<'a> IndexList<Service> for ServiceIndex<'a> {
fn services<'a>() -> IndexedMap<'a, ServiceId, Service, ServiceIndex<'a>> {
let indexes = ServiceIndex {
nym_address: MultiIndex::new(
|d| d.nym_address.to_string(),
|d| d.service.nym_address.to_string(),
SERVICES_PK_NAMESPACE,
SERVICES_NYM_ADDRESS_IDX_NAMESPACE,
),
@@ -39,10 +39,10 @@ fn services<'a>() -> IndexedMap<'a, ServiceId, Service, ServiceIndex<'a>> {
IndexedMap::new(SERVICES_PK_NAMESPACE, indexes)
}
pub fn save(store: &mut dyn Storage, new_service: &Service) -> Result<ServiceId> {
let service_id = super::next_service_id_counter(store)?;
pub fn save(store: &mut dyn Storage, new_service: &Service) -> Result<()> {
let service_id = new_service.service_id;
services().save(store, service_id, new_service)?;
Ok(service_id)
Ok(())
}
pub fn remove(store: &mut dyn Storage, service_id: ServiceId) -> Result<()> {
@@ -55,39 +55,38 @@ pub fn has_service(store: &dyn Storage, service_id: ServiceId) -> bool {
pub fn load_id(store: &dyn Storage, service_id: ServiceId) -> Result<Service> {
services().load(store, service_id).map_err(|err| match err {
StdError::NotFound { .. } => ContractError::NotFound { service_id },
StdError::NotFound { .. } => SpContractError::NotFound { service_id },
err => err.into(),
})
}
pub fn load_announcer(store: &dyn Storage, announcer: Addr) -> Result<Vec<(ServiceId, Service)>> {
pub fn load_announcer(store: &dyn Storage, announcer: Addr) -> Result<Vec<Service>> {
let services = services()
.idx
.announcer
.prefix(announcer)
.range(store, None, None, Order::Ascending)
.take(MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER as usize)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<_>>>()?;
Ok(services)
}
pub fn load_nym_address(
store: &dyn Storage,
nym_address: NymAddress,
) -> Result<Vec<(ServiceId, Service)>> {
pub fn load_nym_address(store: &dyn Storage, nym_address: NymAddress) -> Result<Vec<Service>> {
let services = services()
.idx
.nym_address
.prefix(nym_address.to_string())
.range(store, None, None, Order::Ascending)
.take(MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS as usize)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<_>>>()?;
Ok(services)
}
#[derive(Debug, PartialEq)]
pub struct PagedLoad {
pub services: Vec<(ServiceId, Service)>,
pub services: Vec<Service>,
pub limit: usize,
pub start_next_after: Option<ServiceId>,
}
@@ -106,9 +105,10 @@ pub fn load_all_paged(
let services = services()
.range(store, start, None, Order::Ascending)
.take(limit)
.collect::<StdResult<Vec<_>>>()?;
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<Service>>>()?;
let start_next_after = services.last().map(|service| service.0);
let start_next_after = services.last().map(|service| service.service_id);
Ok(PagedLoad {
services,
@@ -119,122 +119,128 @@ pub fn load_all_paged(
#[cfg(test)]
mod tests {
use cosmwasm_std::{
testing::{MockApi, MockQuerier},
MemoryStorage, OwnedDeps,
};
use rstest::rstest;
use crate::{
error::ContractError,
test_helpers::{
fixture::{service_fixture, service_fixture_with_address},
helpers::instantiate_test_contract,
transactions::instantiate_test_contract,
},
SpContractError,
};
use super::*;
#[test]
fn save_works() {
let mut deps = instantiate_test_contract();
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
#[rstest::fixture]
fn deps() -> TestDeps {
instantiate_test_contract()
}
#[rstest]
fn save_works(mut deps: TestDeps) {
assert!(!has_service(&deps.storage, 1));
save(deps.as_mut().storage, &service_fixture()).unwrap();
save(deps.as_mut().storage, &service_fixture(1)).unwrap();
assert!(has_service(&deps.storage, 1));
}
#[test]
fn save_and_check_incorrect_id_fails() {
let mut deps = instantiate_test_contract();
#[rstest]
fn save_and_check_incorrect_id_fails(mut deps: TestDeps) {
assert!(!has_service(&deps.storage, 2));
save(deps.as_mut().storage, &service_fixture()).unwrap();
save(deps.as_mut().storage, &service_fixture(1)).unwrap();
assert!(!has_service(&deps.storage, 2));
}
#[test]
fn remove_works() {
let mut deps = instantiate_test_contract();
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
#[rstest]
fn remove_works(mut deps: TestDeps) {
let id = 1;
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
assert!(has_service(&deps.storage, id));
remove(deps.as_mut().storage, id).unwrap();
assert!(!has_service(&deps.storage, id));
}
#[test]
fn load_by_id_works() {
let mut deps = instantiate_test_contract();
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
#[rstest]
fn load_by_id_works(mut deps: TestDeps) {
let id = 1;
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
let service = load_id(deps.as_ref().storage, id).unwrap();
assert_eq!(service, service_fixture());
assert_eq!(service, service_fixture(id));
}
#[test]
fn load_by_wrong_id_returns_not_found() {
let mut deps = instantiate_test_contract();
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
#[rstest]
fn load_by_wrong_id_returns_not_found(mut deps: TestDeps) {
let id = 1;
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
assert_eq!(
load_id(deps.as_ref().storage, id + 1).unwrap_err(),
ContractError::NotFound { service_id: id + 1 }
SpContractError::NotFound { service_id: id + 1 }
);
}
#[test]
fn load_by_announcer_works() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
#[rstest]
fn load_by_announcer_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
assert_eq!(
load_announcer(&deps.storage, Addr::unchecked("steve")).unwrap(),
vec![
(1, service_fixture_with_address("a")),
(2, service_fixture_with_address("b")),
(3, service_fixture_with_address("c")),
service_fixture_with_address(1, "a"),
service_fixture_with_address(2, "b"),
service_fixture_with_address(3, "c"),
]
);
}
#[test]
fn load_by_wrong_announcer_returns_empty() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
#[rstest]
fn load_by_wrong_announcer_returns_empty(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
assert_eq!(
load_announcer(&deps.storage, Addr::unchecked("timmy")).unwrap(),
vec![]
);
}
#[test]
fn load_by_nym_address_works() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
#[rstest]
fn load_by_nym_address_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
assert_eq!(
load_nym_address(&deps.storage, NymAddress::new("b")).unwrap(),
vec![(2, service_fixture_with_address("b"))]
vec![service_fixture_with_address(2, "b")]
);
}
#[test]
fn load_by_wrong_nym_address_returns_empty() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
#[rstest]
fn load_by_wrong_nym_address_returns_empty(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
assert_eq!(
load_nym_address(&deps.storage, NymAddress::new("d")).unwrap(),
vec![]
);
}
#[test]
fn load_all_paged_with_no_limit_works() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
#[rstest]
fn load_all_paged_with_no_limit_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
assert_eq!(
load_all_paged(&deps.storage, None, None).unwrap(),
PagedLoad {
services: vec![
(1, service_fixture_with_address("a")),
(2, service_fixture_with_address("b"))
service_fixture_with_address(1, "a"),
service_fixture_with_address(2, "b")
],
start_next_after: Some(2),
limit: 100,
@@ -242,20 +248,19 @@ mod tests {
);
}
#[test]
fn load_all_paged_with_limit_works() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("d")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("e")).unwrap();
#[rstest]
fn load_all_paged_with_limit_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(4, "d")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(5, "e")).unwrap();
assert_eq!(
load_all_paged(&deps.storage, Some(2), None).unwrap(),
PagedLoad {
services: vec![
(1, service_fixture_with_address("a")),
(2, service_fixture_with_address("b"))
service_fixture_with_address(1, "a"),
service_fixture_with_address(2, "b")
],
limit: 2,
start_next_after: Some(2),
@@ -265,8 +270,8 @@ mod tests {
load_all_paged(&deps.storage, Some(2), Some(2)).unwrap(),
PagedLoad {
services: vec![
(3, service_fixture_with_address("c")),
(4, service_fixture_with_address("d"))
service_fixture_with_address(3, "c"),
service_fixture_with_address(4, "d")
],
limit: 2,
start_next_after: Some(4),
@@ -275,7 +280,7 @@ mod tests {
assert_eq!(
load_all_paged(&deps.storage, Some(2), Some(4)).unwrap(),
PagedLoad {
services: vec![(5, service_fixture_with_address("e")),],
services: vec![service_fixture_with_address(5, "e")],
start_next_after: Some(5),
limit: 2,
}
@@ -1,11 +1,12 @@
use cosmwasm_std::{from_binary, testing::mock_env, Addr, Coin, Deps};
use nym_contracts_common::signing::Nonce;
use nym_service_provider_directory_common::{
msg::QueryMsg,
response::{ConfigResponse, PagedServicesListResponse},
ServiceId, ServiceInfo,
Service, ServiceId,
};
use crate::{constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT, error::ContractError};
use crate::{constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT, SpContractError};
pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) {
crate::state::assert_admin(deps, admin).unwrap();
@@ -14,7 +15,7 @@ pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) {
assert_eq!(config, ConfigResponse { deposit_required });
}
pub fn assert_services(deps: Deps, expected_services: &[ServiceInfo]) {
pub fn assert_services(deps: Deps, expected_services: &[Service]) {
let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap();
let services: PagedServicesListResponse = from_binary(&res).unwrap();
let start_next_after = expected_services.iter().last().map(|s| s.service_id);
@@ -28,7 +29,7 @@ pub fn assert_services(deps: Deps, expected_services: &[ServiceInfo]) {
);
}
pub fn assert_service(deps: Deps, expected_service: &ServiceInfo) {
pub fn assert_service(deps: Deps, expected_service: &Service) {
let res = crate::contract::query(
deps,
mock_env(),
@@ -37,7 +38,7 @@ pub fn assert_service(deps: Deps, expected_service: &ServiceInfo) {
},
)
.unwrap();
let services: ServiceInfo = from_binary(&res).unwrap();
let services: Service = from_binary(&res).unwrap();
assert_eq!(&services, expected_service);
}
@@ -58,8 +59,21 @@ pub fn assert_not_found(deps: Deps, expected_id: ServiceId) {
.unwrap_err();
assert!(matches!(
res,
ContractError::NotFound {
SpContractError::NotFound {
service_id: _expected_id
}
));
}
pub fn assert_current_nonce(deps: Deps, address: &Addr, expected_nonce: Nonce) {
let res = crate::contract::query(
deps,
mock_env(),
QueryMsg::SigningNonce {
address: address.to_string(),
},
)
.unwrap();
let nonce: Nonce = from_binary(&res).unwrap();
assert_eq!(nonce, expected_nonce);
}
@@ -1,43 +1,87 @@
use cosmwasm_std::Addr;
use cosmwasm_std::{Addr, Coin, DepsMut};
use nym_contracts_common::{signing::MessageSignature, IdentityKeyRef};
use nym_crypto::asymmetric::identity;
use nym_service_provider_directory_common::{
NymAddress, Service, ServiceId, ServiceInfo, ServiceType,
NymAddress, Service, ServiceDetails, ServiceId, ServiceType,
};
use rand_chacha::rand_core::{CryptoRng, RngCore};
use super::{
helpers::nyms,
signing::{ed25519_sign_message, service_provider_announce_sign_payload},
};
use super::helpers::nyms;
pub fn service_fixture() -> Service {
Service {
nym_address: NymAddress::new("nym"),
service_type: ServiceType::NetworkRequester,
announcer: Addr::unchecked("steve"),
block_height: 12345,
deposit: nyms(100),
}
}
pub fn service_fixture_with_address(nym_address: &str) -> Service {
Service {
nym_address: NymAddress::new(nym_address),
service_type: ServiceType::NetworkRequester,
announcer: Addr::unchecked("steve"),
block_height: 12345,
deposit: nyms(100),
}
}
pub fn service_info(
pub fn new_service(
service_id: ServiceId,
nym_address: NymAddress,
announcer: Addr,
) -> ServiceInfo {
ServiceInfo {
nym_address: &NymAddress,
announcer: &Addr,
identity_key: IdentityKeyRef,
) -> Service {
Service {
service_id,
service: Service {
nym_address,
service: ServiceDetails {
nym_address: nym_address.clone(),
service_type: ServiceType::NetworkRequester,
announcer,
block_height: 12345,
deposit: nyms(100),
identity_key: identity_key.to_string(),
},
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
}
}
pub fn service_fixture(service_id: ServiceId) -> Service {
new_service(
service_id,
&NymAddress::new("nym"),
&Addr::unchecked("steve"),
"identity",
)
}
pub fn service_fixture_with_address(service_id: ServiceId, nym_address: &str) -> Service {
new_service(
service_id,
&NymAddress::new(nym_address),
&Addr::unchecked("steve"),
"identity",
)
}
// Create a new service, using a correctly generted identity key
pub fn new_service_details<R>(rng: &mut R, nym_address: &str) -> (ServiceDetails, identity::KeyPair)
where
R: RngCore + CryptoRng,
{
let keypair = identity::KeyPair::new(rng);
(
ServiceDetails {
nym_address: NymAddress::new(nym_address),
service_type: ServiceType::NetworkRequester,
identity_key: keypair.public_key().to_base58_string(),
},
keypair,
)
}
// Create a new service, with a correctly generated identity key, and sign it
pub fn new_service_details_with_sign<R>(
deps: DepsMut<'_>,
rng: &mut R,
nym_address: &str,
announcer: &str,
deposit: Coin,
) -> (ServiceDetails, MessageSignature)
where
R: RngCore + CryptoRng,
{
// Service
let (service, keypair) = new_service_details(rng, nym_address);
// Sign
let sign_msg =
service_provider_announce_sign_payload(deps.as_ref(), announcer, service.clone(), deposit);
let owner_signature = ed25519_sign_message(sign_msg, keypair.private_key());
(service, owner_signature)
}
@@ -1,19 +1,16 @@
use cosmwasm_std::{
coin, coins,
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
Coin, DepsMut, Event, MemoryStorage, OwnedDeps, Response,
};
use cosmwasm_std::{Coin, Event, Response};
use cw_multi_test::AppResponse;
use nym_service_provider_directory_common::{
events::{ServiceProviderEventType, SERVICE_ID},
msg::{ExecuteMsg, InstantiateMsg},
Service, ServiceId,
};
use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
pub fn nyms(amount: u64) -> Coin {
Coin::new(amount.into(), "unym")
}
pub fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
ChaCha20Rng::from_seed(dummy_seed)
}
pub fn get_event_types(response: &Response, event_type: &str) -> Vec<Event> {
response
.events
@@ -55,50 +52,3 @@ pub fn get_app_attribute(response: &AppResponse, event_type: &str, key: &str) ->
.value
.clone()
}
#[allow(dead_code)]
pub fn get_app_attributes(response: &AppResponse, event_type: &str, key: &str) -> Vec<String> {
get_app_event_types(response, event_type)
.iter()
.map(|ev| {
ev.attributes
.iter()
.find(|attr| attr.key == key)
.unwrap()
.value
.clone()
})
.collect::<Vec<_>>()
}
pub fn instantiate_test_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
deposit_required: coin(100, "unym"),
};
let env = mock_env();
let info = mock_info("creator", &[]);
let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
deps
}
pub fn announce_service(deps: DepsMut<'_>, service: &Service) -> ServiceId {
let msg: ExecuteMsg = service.clone().into();
let info = mock_info(service.announcer.as_str(), &coins(100, "unym"));
let res = crate::execute(deps, mock_env(), info, msg).unwrap();
let service_id: ServiceId = get_attribute(
&res,
&ServiceProviderEventType::Announce.to_string(),
SERVICE_ID,
)
.parse()
.unwrap();
service_id
}
pub fn delete_service(deps: DepsMut<'_>, service_id: ServiceId, announcer: &str) {
let msg = ExecuteMsg::DeleteId { service_id };
let info = mock_info(announcer, &[]);
crate::execute(deps, mock_env(), info, msg).unwrap();
}
@@ -1,4 +1,5 @@
pub mod assert;
pub mod fixture;
pub mod helpers;
pub mod test_setup;
pub mod signing;
pub mod transactions;
@@ -0,0 +1,41 @@
use cosmwasm_std::{Addr, Coin, Deps};
use nym_contracts_common::signing::{
MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose,
};
use nym_crypto::asymmetric::identity;
use nym_service_provider_directory_common::{
signing_types::{
construct_service_provider_announce_sign_payload, SignableServiceProviderAnnounceMsg,
},
ServiceDetails,
};
use serde::Serialize;
use crate::state;
pub fn service_provider_announce_sign_payload(
deps: Deps<'_>,
owner: &str,
service: ServiceDetails,
deposit: Coin,
) -> SignableServiceProviderAnnounceMsg {
let owner = Addr::unchecked(owner);
let nonce = state::get_signing_nonce(deps.storage, owner.clone()).unwrap();
construct_service_provider_announce_sign_payload(nonce, owner, deposit, service)
}
pub fn ed25519_sign_message<T: Serialize + SigningPurpose>(
message: SignableMessage<T>,
private_key: &identity::PrivateKey,
) -> MessageSignature {
match message.algorithm {
SigningAlgorithm::Ed25519 => {
let plaintext = message.to_plaintext().unwrap();
let signature = private_key.sign(&plaintext);
MessageSignature::from(signature.to_bytes().as_ref())
}
SigningAlgorithm::Secp256k1 => {
unimplemented!()
}
}
}
@@ -0,0 +1,69 @@
use cosmwasm_std::{
coin,
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
DepsMut, MemoryStorage, OwnedDeps,
};
use nym_service_provider_directory_common::{
events::{ServiceProviderEventType, SERVICE_ID},
msg::{ExecuteMsg, InstantiateMsg},
ServiceDetails, ServiceId,
};
use rand_chacha::rand_core::{CryptoRng, RngCore};
use super::helpers::{get_attribute, nyms};
pub fn instantiate_test_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
deposit_required: coin(100, "unym"),
};
let env = mock_env();
let info = mock_info("creator", &[]);
let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
deps
}
pub fn announce_service<R>(
mut deps: DepsMut<'_>,
rng: &mut R,
nym_address: &str,
announcer: &str,
) -> (ServiceId, ServiceDetails)
where
R: RngCore + CryptoRng,
{
let deposit = nyms(100);
let (service, owner_signature) = super::fixture::new_service_details_with_sign(
deps.branch(),
rng,
nym_address,
announcer,
deposit.clone(),
);
// Announce
let msg = ExecuteMsg::Announce {
service: service.clone(),
owner_signature,
};
let info = mock_info(announcer, &[deposit]);
let res = crate::execute(deps, mock_env(), info, msg).unwrap();
let service_id: ServiceId = get_attribute(
&res,
&ServiceProviderEventType::Announce.to_string(),
SERVICE_ID,
)
.parse()
.unwrap();
(service_id, service)
}
pub fn delete_service(deps: DepsMut<'_>, service_id: ServiceId, announcer: &str) {
let msg = ExecuteMsg::DeleteId { service_id };
let info = mock_info(announcer, &[]);
crate::execute(deps, mock_env(), info, msg).unwrap();
}
+3 -3
View File
@@ -20,9 +20,9 @@ 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.5.0" }
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.4.0" }
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.6.0" }
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.5.0" }
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.7.0" }
cosmwasm-std = { workspace = true }
cosmwasm-derive = { workspace = true }
+3 -4
View File
@@ -49,10 +49,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
[preprocessor.variables.variables]
# code prerequisites versions
minimum_rust_version = "1.66"
platform_release_version = "v1.1.19"
upcoming_platform_release_version = "v1.1.20" # to use when adding 'edit on github' plugin
mix_node_release_version = "v1.1.20"
#
# TODO remove this in place of develop in next release
platform_release_version = "v1.1.20"
[preprocessor.last-changed]
command = "mdbook-last-changed"
renderer = ["html"]
+4 -4
View File
@@ -1,5 +1,5 @@
[book]
title = "Nym Docs v1.1.19"
title = "Nym Docs"
authors = ["Max Hampshire"]
description = "Nym technical documentation"
language = "en"
@@ -48,9 +48,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
# https://gitlab.com/tglman/mdbook-variables/
[preprocessor.variables.variables]
minimum_rust_version = "1.66"
platform_release_version = "v1.1.19"
upcoming_platform_release_version = "v1.1.20" # to use in 'edit page on github' plugin (coming soon)
mix_node_release_version = "v1.1.20"
# old variables - this is still needed for links.. TODO change to develop?
platform_release_version = "v1.1.20"
[preprocessor.last-changed]
command = "mdbook-last-changed"
@@ -2,6 +2,12 @@
> The Nym socks5 client was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code on this page, go there first.
## Current version
```
<!-- cmdrun ../../../../target/release/nym-socks5-client --version | grep "Build Version" | cut -b 21-26 -->
```
## What is this client for?
Many existing applications are able to use either the SOCKS4, SOCKS4A, or SOCKS5 proxy protocols. If you want to send such an application's traffic through the mixnet, you can use the `nym-socks5-client` to bounce network traffic through the Nym network, like this:
```
@@ -2,6 +2,11 @@
> The Nym Websocket Client was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code on this page, go there first.
## Current version
```
<!-- cmdrun ../../../../target/release/nym-client --version | grep "Build Version" | cut -b 21-26 -->
```
## Client setup
### Viewing command help
@@ -2,6 +2,12 @@
> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first.
## Current version
```
<!-- cmdrun ../../../../target/release/nym-gateway --version | grep "Build Version" | cut -b 21-26 -->
```
## Preliminary steps
There are a couple of steps that need completing before starting to set up your gateway:
@@ -2,9 +2,12 @@
> The Nym mix node binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first.
```admonish info
The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases.
## Current version
```
<!-- cmdrun ../../../../target/release/nym-mixnode --version | grep "Build Version" | cut -b 21-26 -->
```
The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases.
## Preliminary steps
@@ -228,10 +231,10 @@ sudo ufw enable
sudo ufw status
```
Finally open your mix node's p2p port, as well as ports for ssh, http, and https connections, and ports `8000` and `1790` for verloc and measurement pings:
Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings:
```
sudo ufw allow 1789,1790,8000,22,80,443/tcp
sudo ufw allow 1789,1790,8000,22/tcp
# check the status of the firewall
sudo ufw status
```
@@ -2,12 +2,17 @@
> The Nym network requester was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code on this page, go there first.
## Current version
```
<!-- cmdrun ../../../../target/release/nym-network-requester --version | grep "Build Version" | cut -b 21-26 -->
```
## Network Requester Whitelist
If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers.
> As of `v1.1.10`, the network requester longer requires a separate nym client instance for it to function, as it has a client embedded within the binary running as a single process.
>
## Network Requester Whitelist
The network requester is **not** an open proxy. It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester/<network-requester-id>/`) as a whitelist for outbound requests.
By default the network requester is **not** an open proxy (although it can be used as one). It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester/<network-requester-id>/`) as a whitelist for outbound requests.
Any request to a URL which is not on this list will be blocked.
@@ -118,6 +123,7 @@ In the previous version of the network-requester, users were required to run a n
If you are running an existing network requester registered with nym-connect, upgrading requires you move your old keys over to the new network requester configuration. We suggest following these instructions carefully to ensure a smooth transition.
Initiate the new network requester:
```
nym-network-requester init --id mynetworkrequester
```
@@ -190,7 +196,7 @@ sudo ufw status
Finally open your requester's p2p port, as well as ports for ssh and incoming traffic connections:
```
sudo ufw allow 1789,22,9000/tcp
sudo ufw allow 22,9000/tcp
# check the status of the firewall
sudo ufw status
```
@@ -214,7 +220,7 @@ ls $HOME/.nym/service-providers/network-requester/
# returns: allowed.list unknown.list
```
We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](../clients/socsk5-client.md) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to restart your network requester!
We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](../clients/socks5-client.md) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to restart your network requester!
> If you are adding custom domains, please note that whilst they may appear in the logs of your network-requester as something like `api-0.core.keybaseapi.com:443`, you **only need** to include the main domain name, in this instance `keybaseapi.com`
@@ -51,7 +51,7 @@ go version go1.20.4 linux/amd64
You can find pre-compiled binaries for Ubuntu `22.04` and `20.04` [here](https://github.com/nymtech/nyxd/releases).
```admonish caution title=""
Binaries for both Mainnet and Sandbox testnet can be found in each release - make sure to download the correct binary to avoid `bech32Prefix` mismatches.
There are seperate releases for Mainnet and the Sandbox testnet - make sure to download the correct binary to avoid `bech32Prefix` mismatches.
```
### Manually compiling your validator binary
@@ -80,17 +80,27 @@ You should see help text print out.
The `nyxd` binary and the `libwasmvm.so` shared object library binary have been compiled. `libwasmvm.so` is the wasm virtual machine which is needed to execute smart contracts.
```admonish caution title=""
If you have compiled these files locally you need to upload both of them to the server on which the validator will run. **If you have instead compiled them on the server skip to the step outlining setting `LD_LIBRARY PATH` below.**
If you have compiled these files locally and need to upload both of them to the server on which the validator will run, **or** downloaded a pre-compiled binary from Github, you need to locate and link these files in slightly different ways, outlined below. If you have instead compiled them on the server skip to the step outlining setting `LD_LIBRARY PATH` below.
```
To locate these files on your local system run:
To locate these files on your system **if you downloaded a pre-compiled binary from Github** run:
```
WASMVM_SO=$(ldd path/to/nyxd/binary | grep libwasmvm.so | awk '{ print $3 }')
ls ${WASMVM_SO}
```
e.g. if you downloaded your `nyxd` binary to `/root` then you would replace `ldd path/to/nyxd/binary` with `ldd nyxd` in the above command.
To locate these files on your system **if you uploaded your validator after compiling it on a local machine** run:
```
WASMVM_SO=$(ldd build/nyxd | grep libwasmvm.so | awk '{ print $3 }')
ls ${WASMVM_SO}
```
This will output something like:
The above commands will output something like:
```
'/home/username/go/pkg/mod/github.com/!cosm!wasm/wasmvm@v0.13.0/api/libwasmvm.so'
@@ -158,7 +168,7 @@ You can use the following command to download them for the correct network:
wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json
# Sandbox testnet
curl "https://sandbox-validator1.nymtech.net/genesis" | jq .result.genesis > ~/.nyxd/config/genesis.json
wget -O $HOME/.nyxd/config/genesis.json https://sandbox-validator1.nymtech.net/snapshots/genesis.json
```
### `config.toml` configuration
@@ -181,7 +191,7 @@ laddr = "tcp://0.0.0.0:26656"
```
These affect the following:
* `persistent_peers = "<PEER_ADDRESS>@<DOMAIN>.nymtech.net:26656"` allows your validator to start pulling blocks from other validators
* `persistent_peers = "<PEER_ADDRESS>@<DOMAIN>.nymtech.net:26666"` allows your validator to start pulling blocks from other validators. **The main sandbox validator listens on `26666` instead of the default `26656` for debugging**. It is recommended you do not change your port from `26656`.
* `create_empty_blocks = false` will save space
* `laddr = "tcp://0.0.0.0:26656"` is in your p2p configuration options
@@ -230,6 +240,11 @@ nyxd keys show nyxd-admin -a
Type in your keychain **password**, not the mnemonic, when asked.
## Starting your validator
```admonish caution title=""
If you are running a Sandbox testnet validator, please skip the `validate-genesis` command: it will fail due to the size of the genesis file as this is a fork of an existing chain state.
```
Everything should now be ready to go. You've got the validator set up, all changes made in `config.toml` and `app.toml`, the Nym genesis file copied into place (replacing the initial auto-generated one). Now let's validate the whole setup:
```
@@ -269,16 +284,37 @@ nyxd start
Once your validator starts, it will start requesting blocks from other validators. This may take several hours. Once it's up to date, you can issue a request to join the validator set with the command below.
If you wish to sync from a snapshot or via state-sync please check out the Polkachu [mainnet](https://polkachu.com/networks/nym) or [testnet](https://polkachu.com/testnets/nym/) resources.
### Syncing from a snapshot
If you wish to sync from a snapshot on **mainnet** use Polkachu's [mainnet](https://polkachu.com/networks/nym) resources.
> If you are having trouble upgrading your validator binary, try replacing (or re-compile) the `libwasmvm.so` file and replace it on your validator server.
If you wish to sync from a snapshot on **Sandbox testnet** use the below commands, which are a modified version of Polkachu's excellent resources. These commands assume you are running an OS with `apt` as the package manager:
```
# install lz4 if necessary
sudo apt install snapd -y
sudo snap install lz4
# download the snapshot
wget -O nyxd-sandbox-snapshot-data.tar.lz4 https://sandbox-validator1.nymtech.net/snapshots/nyxd-sandbox-snapshot-data.tar.lz4
# reset your validator state
nyxd tendermint unsafe-reset-all
# unpack the snapshot
lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd
```
You can then restart `nyxd` - it should start syncing from a block > 2000000.
### Joining Consensus
```admonish caution title=""
When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens.
Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time.
```
Once your validator has synced and you have received tokens, you can join consensus and produce blocks.
```
# Mainnet
nyxd tx staking create-validator
+30 -38
View File
@@ -1,5 +1,6 @@
# Nym-CLI
## What is this tool for?
This is a CLI tool for interacting with:
* the Nyx blockchain (account management, querying the chain state, etc)
@@ -7,20 +8,20 @@ This is a CLI tool for interacting with:
It provides a convenient wrapper around the `nymd` client, and has similar functionality to the `nyxd` binary for querying the chain or executing smart contract methods.
## Building
The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory.
## Building
The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory.
### Useage
You can see all available commands with:
### Useage
You can see all available commands with:
```
./nym-cli --help
./nym-cli --help
```
~~~admonish example collapsible=true title="Console output"
```
nym-cli
nym-cli
A client for interacting with Nym smart contracts and the Nyx blockchain
USAGE:
@@ -67,13 +68,13 @@ subcommands:
```
~~~
## Example Usage
Below we have listed some example commands for some of the features listed above.
## Example Usage
Below we have listed some example commands for some of the features listed above.
If ever in doubt what you need to type, or if you want to see alternative parameters for a command, use the `nym-cli <subcommand_name> --help` to view all available options.
```
./nym-cli account create --help
./nym-cli account create --help
```
~~~admonish example collapsible=true title="Console output"
@@ -120,7 +121,7 @@ OPTIONS:
Creates an account with a random Mnemonic and a new address.
```
./nym-cli account create
./nym-cli account create
# Result:
# 1. Mnemonic
@@ -136,7 +137,7 @@ Queries the existing balance of an account.
```
# Using adddress below for example purposes.
./nym-cli account balance n1hzn28p2c6pzr98r85jp3h53fy8mju5w7ndd5vh
./nym-cli account balance n1hzn28p2c6pzr98r85jp3h53fy8mju5w7ndd5vh
# Result:
2022-11-10T10:28:54.009Z INFO nym_cli_commands::validator::account::balance > Getting balance for n1hzn28p2c6pzr98r85jp3h53fy8mju5w7ndd5vh...
@@ -149,7 +150,7 @@ Queries the existing balance of an account.
You can also query an accounts balance by using its mnemonic:
```
./nym-cli account balance --mnemonic <mnemonic>
./nym-cli account balance --mnemonic <mnemonic>
```
### Send tokens to an account
@@ -167,7 +168,7 @@ Queries the specified blockchain (Nyx chain by default) for the current block he
```
./nym-cli block current-height --mnemonic <mnemonic>
# Result:
# Result:
Current block height:
<BLOCK_HEIGHT>
```
@@ -183,50 +184,50 @@ Query a mix node on the mixnet.
### Bond a mix node
Bonding a mix node is a process that takes a few steps due to the need to sign a transaction with your nym address for replay attack protection.
Bonding a mix node is a process that takes a few steps due to the need to sign a transaction with your nym address for replay attack protection.
* generate a signature payload:
* generate a signature payload:
```
./nym-cli mixnet operators mixnode create-mixnode-bonding-sign-payload
./nym-cli mixnet operators mixnode create-mixnode-bonding-sign-payload
# returns something like
# returns something like
97GEhgMrPTmQVZgHqJeqWmgQ154GLKqy8xNGtLkV8xy5xc1SuwsEnqjhtZVshBYK74n53fFkKbSrS6kxkBE3vUikbU76JZmLMFmfR7aaU2NdBnfTPPHP2nwb2hJiEueq4SvvtDtQckxv7ZJzdxyXHxUeDPhzbprxTff78U3NGNk4cg6Q2K4EFqishdaqToedsXAPvVCWNbC1iWVjEq8nJ95Eb3NJyi3KmXcNDy4i8ZXgZHu4v8F4htXq2vZUdBSbizdkNr1NRvEg6PGVQdTseyuN8JxD3yuvrqprPY2kvJaT2YiYLPgWxoQtbfwcpkX4PP1PvwuMg4W8EXhitMpM2WHqLDP5vgfDGxdDCmRS44pM8ya4hcQ4g3McHWxduGWdbCzNNEsX6oQw4LVFcWn4mhbXSgqHwNQMm2TQW6LatYZSwCczdhEwV2CXe36UGCUzozmm4nj9qfUtXqDzMrHAAS8kjbKaVNaVaRRKgauQrHnK7QGg1QpVnnaxCs14wvUb62sio8XZmMzP2SjVaRJFCyJB3UwZ6L4oXMGMXSRsiKe8ZNTaa6iX69tx54CAAHBHoiReiq7E5T2VuR5v
```
* sign this payload:
* sign this payload:
```
./nym-mixnode sign --id upgrade_test --contract-msg 97GEhgMrPTmQVZgHqJeqWmgQ154GLKqy8xNGtLkV8xy5xc1SuwsEnqjhtZVshBYK74n53fFkKbSrS6kxkBE3vUikbU76JZmLMFmfR7aaU2NdBnfTPPHP2nwb2hJiEueq4SvvtDtQckxv7ZJzdxyXHxUeDPhzbprxTff78U3NGNk4cg6Q2K4EFqishdaqToedsXAPvVCWNbC1iWVjEq8nJ95Eb3NJyi3KmXcNDy4i8ZXgZHu4v8F4htXq2vZUdBSbizdkNr1NRvEg6PGVQdTseyuN8JxD3yuvrqprPY2kvJaT2YiYLPgWxoQtbfwcpkX4PP1PvwuMg4W8EXhitMpM2WHqLDP5vgfDGxdDCmRS44pM8ya4hcQ4g3McHWxduGWdbCzNNEsX6oQw4LVFcWn4mhbXSgqHwNQMm2TQW6LatYZSwCczdhEwV2CXe36UGCUzozmm4nj9qfUtXqDzMrHAAS8kjbKaVNaVaRRKgauQrHnK7QGg1QpVnnaxCs14wvUb62sio8XZmMzP2SjVaRJFCyJB3UwZ6L4oXMGMXSRsiKe8ZNTaa6iX69tx54CAAHBHoiReiq7E5T2VuR5v
```
* bond the node using the signature:
* bond the node using the signature:
```
./nym-cli --mnemonic <mnemonic> mixnet operators mixnode bond --amount 100000000 --mix-port 1789 --version "1.1.13" --host "85.163.111.99" --identity-key "B6pWscxYb8sPAdKTci8zPy5AgMzn5Zx8KpWwQNCyUSU7" --location "nym-town" --sphinx-key "o6MmKHzRewpNzVwaV37ZX9G3BfK4AmfYvsQfyoyAFRk" --signature "2TujBZfer8r5QM639Yb8coD9xH6f5eXzjAT5dD7wMom9fH8D1u36d7UpPdVaaZrWsCynmYpobwMWqiMKr5kM6CprD"
```
### Bond a gateway
Bonding a mix node is a process that takes a few steps due to the need to sign a transaction with your nym address for replay attack protection.
### Bond a gateway
Bonding a mix node is a process that takes a few steps due to the need to sign a transaction with your nym address for replay attack protection.
* generate a signature payload:
* generate a signature payload:
```
./nym-cli mixnet operators gateway create-gateway-bonding-sign-payload
./nym-cli mixnet operators gateway create-gateway-bonding-sign-payload
# returns something like
# returns something like
97GEhgMrPTmQVZgHqJeqWmgQ154GLKqy8xNGtLkV8xy5xc1SuwsEnqjhtZVshBYK74n53fFkKbSrS6kxkBE3vUikbU76JZmLMFmfR7aaU2NdBnfTPPHP2nwb2hJiEueq4SvvtDtQckxv7ZJzdxyXHxUeDPhzbprxTff78U3NGNk4cg6Q2K4EFqishdaqToedsXAPvVCWNbC1iWVjEq8nJ95Eb3NJyi3KmXcNDy4i8ZXgZHu4v8F4htXq2vZUdBSbizdkNr1NRvEg6PGVQdTseyuN8JxD3yuvrqprPY2kvJaT2YiYLPgWxoQtbfwcpkX4PP1PvwuMg4W8EXhitMpM2WHqLDP5vgfDGxdDCmRS44pM8ya4hcQ4g3McHWxduGWdbCzNNEsX6oQw4LVFcWn4mhbXSgqHwNQMm2TQW6LatYZSwCczdhEwV2CXe36UGCUzozmm4nj9qfUtXqDzMrHAAS8kjbKaVNaVaRRKgauQrHnK7QGg1QpVnnaxCs14wvUb62sio8XZmMzP2SjVaRJFCyJB3UwZ6L4oXMGMXSRsiKe8ZNTaa6iX69tx54CAAHBHoiReiq7E5T2VuR5v
```
* sign this payload:
* sign this payload:
```
./nym-gateway sign --id upgrade_test --contract-msg 97GEhgMrPTmQVZgHqJeqWmgQ154GLKqy8xNGtLkV8xy5xc1SuwsEnqjhtZVshBYK74n53fFkKbSrS6kxkBE3vUikbU76JZmLMFmfR7aaU2NdBnfTPPHP2nwb2hJiEueq4SvvtDtQckxv7ZJzdxyXHxUeDPhzbprxTff78U3NGNk4cg6Q2K4EFqishdaqToedsXAPvVCWNbC1iWVjEq8nJ95Eb3NJyi3KmXcNDy4i8ZXgZHu4v8F4htXq2vZUdBSbizdkNr1NRvEg6PGVQdTseyuN8JxD3yuvrqprPY2kvJaT2YiYLPgWxoQtbfwcpkX4PP1PvwuMg4W8EXhitMpM2WHqLDP5vgfDGxdDCmRS44pM8ya4hcQ4g3McHWxduGWdbCzNNEsX6oQw4LVFcWn4mhbXSgqHwNQMm2TQW6LatYZSwCczdhEwV2CXe36UGCUzozmm4nj9qfUtXqDzMrHAAS8kjbKaVNaVaRRKgauQrHnK7QGg1QpVnnaxCs14wvUb62sio8XZmMzP2SjVaRJFCyJB3UwZ6L4oXMGMXSRsiKe8ZNTaa6iX69tx54CAAHBHoiReiq7E5T2VuR5v
```
* bond the node using this signature:
* bond the node using this signature:
```
./nym-cli --mnemonic <mnemonic> mixnet operators gateway bond --amount 100000000 --mix-port 1789 --version "1.1.13" --host "85.163.111.99" --identity-key "B6pWscxYb8sPAdKTci8zPy5AgMzn5Zx8KpWwQNCyUSU7" --location "nym-town" --sphinx-key "o6MmKHzRewpNzVwaV37ZX9G3BfK4AmfYvsQfyoyAFRk" --signature "2TujBZfer8r5QM639Yb8coD9xH6f5eXzjAT5dD7wMom9fH8D1u36d7UpPdVaaZrWsCynmYpobwMWqiMKr5kM6CprD"
```
### Unbond a node
Unbond a mix node or gateway.
Unbond a mix node or gateway.
```
./nym-cli mixnet operators gateway unbound --mnemonic <mnemonic>
```
@@ -235,7 +236,7 @@ Unbond a mix node or gateway.
### Upgrade a mix node
Upgrade your node config.
Upgrade your node config.
```
./nym-cli mixnet operators mixnode settings update-config --version <new_version>
```
@@ -298,7 +299,7 @@ Sign a message.
Verify a signature.
```
./nym-cli signature verify --mnemonic <mnemonic> <PUBLIC_KEY_OR_ADDRESS> <SIGNATURE_AS_HEX> <MESSAGE>
./nym-cli signature verify --mnemonic <mnemonic> <PUBLIC_KEY_OR_ADDRESS> <SIGNATURE_AS_HEX> <MESSAGE>
```
### Create a Vesting Schedule
@@ -336,12 +337,3 @@ Query for staking on behlaf of someone else
```
./nym-cli --mnemonic <staking address mnemonic> mixnet delegators delegate --mix-id <input> --identity-key <input> --amount <input>
```
+672
View File
@@ -2340,6 +2340,678 @@
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
/* Themes */
.ayu {
+168
View File
@@ -762,6 +762,174 @@
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
{{{ content }}}
</main>
+1 -1
View File
@@ -17,7 +17,7 @@ GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm2
MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy
COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1qsn2655eflc0nx2uwqtwyv5kad5dwm4c0gn72yr4q4de5r3jaz2slvqjgt
NAME_SERVICE_CONTRACT_ADDRESS=n1cm2u5vfjd3zalfw0p65xyh4tcrw3hjlm0960gzhewga449h4mgas77mjkl
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYXD="https://qwerty-validator.qa.nymte.ch/"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.19"
version = "1.1.20"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+12 -25
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.19"
version = "1.1.20"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -17,6 +17,7 @@ rust-version = "1.56"
[dependencies]
anyhow = "1.0.53"
async-trait = { workspace = true }
atty = "0.2"
bip39 = { workspace = true }
bs58 = "0.4.0"
clap = { version = "4.0", features = ["cargo", "derive"] }
@@ -32,20 +33,11 @@ once_cell = "1.7.2"
pretty_env_logger = "0.4"
rand = "0.7"
serde = { workspace = true, features = ["derive"] }
sqlx = { version = "0.5", features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
serde_json = { workspace = true }
sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "sqlite", "macros", "migrate", ] }
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
thiserror = "1"
tokio = { version = "1.24.1", features = [
"rt-multi-thread",
"net",
"signal",
"fs",
] }
tokio = { version = "1.24.1", features = [ "rt-multi-thread", "net", "signal", "fs", ] }
tokio-stream = { version = "0.1.11", features = ["fs"] }
tokio-tungstenite = "0.14"
tokio-util = { version = "0.7.4", features = ["codec"] }
@@ -53,27 +45,22 @@ url = { version = "2.2", features = ["serde"] }
zeroize = { workspace = true }
# internal
nym-coconut-interface = { path = "../common/coconut-interface" }
nym-credentials = { path = "../common/credentials" }
nym-config = { path = "../common/config" }
nym-crypto = { path = "../common/crypto" }
nym-api-requests = { path = "../nym-api/nym-api-requests" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
nym-coconut-interface = { path = "../common/coconut-interface" }
nym-config = { path = "../common/config" }
nym-credentials = { path = "../common/credentials" }
nym-crypto = { path = "../common/crypto" }
nym-gateway-requests = { path = "gateway-requests" }
nym-mixnet-client = { path = "../common/client-libs/mixnet-client" }
nym-mixnode-common = { path = "../common/mixnode-common" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-pemstore = { path = "../common/pemstore" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-statistics-common = { path = "../common/statistics" }
nym-api-requests = { path = "../nym-api/nym-api-requests" }
nym-task = { path = "../common/task" }
nym-validator-client = { path = "../common/client-libs/validator-client", features = [
"nyxd-client",
] }
nym-types = { path = "../common/types" }
serde_json = { workspace = true }
atty = "0.2"
nym-validator-client = { path = "../common/client-libs/validator-client", features = [ "nyxd-client" ] }
[build-dependencies]
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
+2 -2
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.20"
version = "1.1.21"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -52,7 +52,7 @@ nym-mixnode-common = { path = "../common/mixnode-common" }
nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-sphinx-params = { path = "../common/nymsphinx/params" }
nym-pemstore = { path = "../common/pemstore", version = "0.2.0" }
nym-pemstore = { path = "../common/pemstore", version = "0.3.0" }
nym-task = { path = "../common/task" }
nym-types = { path = "../common/types" }
nym-topology = { path = "../common/topology" }
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-api"
version = "1.1.20"
version = "1.1.21"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+2 -2
View File
@@ -7,7 +7,7 @@ use nym_mixnet_contract_common::{
RewardingParams,
};
use nym_name_service_common::NameEntry;
use nym_service_provider_directory_common::ServiceInfo;
use nym_service_provider_directory_common::Service;
use std::collections::HashSet;
pub(crate) struct ValidatorCacheData {
@@ -25,7 +25,7 @@ pub(crate) struct ValidatorCacheData {
pub(crate) mix_to_family: Cache<Vec<(IdentityKey, FamilyHead)>>,
pub(crate) service_providers: Cache<Vec<ServiceInfo>>,
pub(crate) service_providers: Cache<Vec<Service>>,
pub(crate) registered_names: Cache<Vec<NameEntry>>,
}
+3 -3
View File
@@ -6,7 +6,7 @@ use nym_mixnet_contract_common::{
RewardingParams,
};
use nym_name_service_common::NameEntry;
use nym_service_provider_directory_common::ServiceInfo;
use nym_service_provider_directory_common::Service;
use rocket::fairing::AdHoc;
use std::{
collections::HashSet,
@@ -52,7 +52,7 @@ impl NymContractCache {
rewarding_params: RewardingParams,
current_interval: Interval,
mix_to_family: Vec<(IdentityKey, FamilyHead)>,
services: Option<Vec<ServiceInfo>>,
services: Option<Vec<Service>>,
names: Option<Vec<NameEntry>>,
) {
match time::timeout(Duration::from_millis(100), self.inner.write()).await {
@@ -293,7 +293,7 @@ impl NymContractCache {
self.mixnode_details(mix_id).await.1
}
pub(crate) async fn services(&self) -> Cache<Vec<ServiceInfo>> {
pub(crate) async fn services(&self) -> Cache<Vec<Service>> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.service_providers.clone(),
Err(err) => {
+23 -5
View File
@@ -1053,6 +1053,20 @@ dependencies = [
"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-storage-plus"
version = "0.13.4"
@@ -3344,7 +3358,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3379,7 +3393,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"aes 0.8.2",
"blake3",
@@ -3484,7 +3498,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3573,7 +3587,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"pem",
]
@@ -3583,8 +3597,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -3884,7 +3902,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
@@ -42,7 +42,7 @@ pub async fn get_services() -> Result<Vec<DirectoryServiceProvider>> {
let mut filtered: Vec<DirectoryService> = vec![];
for service in &services_res {
let items: _ = service
let items = service
.items
.clone()
.into_iter()
+41 -8
View File
@@ -6,27 +6,60 @@ _TODO_
### Getting started
Install Android Studio and open the project.
[Install](https://developer.android.com/studio/install) Android Studio and open
the project.\
Setup an android emulator using AVD.\
[Run](https://developer.android.com/studio/run/emulator) the project.
**⚠ NOTE**: be sure
to [set](https://developer.android.com/studio/run#changing-variant)
the build variant to `x86_64Debug` when running on emulator
### Lib Nym socks5
This Application needs the native [nym-socks5-listener](https://github.com/nymtech/nym/blob/develop/sdk/lib/socks5-listener/Cargo.toml)
This Application needs the
native [nym-socks5-listener](https://github.com/nymtech/nym/blob/develop/sdk/lib/socks5-listener/Cargo.toml)
library in order to work.
To build it, from the root of the repo run
To build it for x86_64 and arm64 arch, from the root of the repo run
```shell
cd sdk/lib/socks5-listener/
./build-android.sh
./build-android.sh aarch64 x86_64
```
To build in release mode
To build in release mode run
```shell
RELEASE=true ./build-android.sh
RELEASE=true ./build-android.sh aarch64 x86_64
```
The shared library for each ABIs will be automatically moved into
The shared library for each ABIs will be automatically moved into
`app/src/main/jniLibs/*` directories.
### Run _TODO_
### APK build (from terminal)
This project is setup with multiple [product flavors](app/build.gradle) to
build for specific architectures.\
Supported archs:
- arm64
- arm (old arm)
- x86_64
- x86
For example to build for _arm64_ in _release_ mode use
```shell
./gradlew :app:assembleArm64Release
```
**NOTE**: you likely want _arch64_ (`arm64` & `x86_64`) for APK distribution
To build a _universal_ APK which includes all ABI use
```shell
./gradlew :app:assembleUniversalRelease
```
**⚠ WARNING**: APK size will be multiplied
+47 -7
View File
@@ -23,20 +23,60 @@ android {
buildTypes {
release {
minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
splits {
abi {
enable true
reset()
include "x86_64", "arm64-v8a"
universalApk false
flavorDimensions "abi"
productFlavors {
universal {
dimension "abi"
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86"
}
}
arch64 {
dimension "abi"
ndk {
abiFilters "arm64-v8a", "x86_64"
}
}
arm64 {
dimension "abi"
ndk {
abiFilters "arm64-v8a"
}
}
arm {
dimension "abi"
ndk {
abiFilters "armeabi-v7a"
}
}
x86_64 {
dimension "abi"
ndk {
abiFilters "x86_64"
}
}
x86 {
dimension "abi"
ndk {
abiFilters "x86"
}
}
}
// splits {
// abi {
// enable true
// reset()
// include "x86_64", "arm64-v8a"
// universalApk true
// }
// }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@@ -12,13 +12,17 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@OptIn(DelicateCoroutinesApi::class)
class MainViewModel(
private val workManager: WorkManager,
private val nymProxy: NymProxy
@@ -39,39 +43,32 @@ class MainViewModel(
init {
Log.d(tag, "____init")
// TODO ⚠ In some circumstances this `init` block can be run multiple
// time which means the below observer will be registered more than once
// This can leads to multiple sequential calls to `stopClient`
// → nym_socks5_listener panics when this happens, crashing the
// entire App
// When the work is cancelled "externally" ie. when the user tap the
// "Stop" action on the notification, or when the app is intentionally
// killed the underlying proxy client keeps running in background
// We have to manually call `stopClient` to stop it
// "Stop" action on the notification, the underlying proxy process
// keeps running in background
// We have to manually call `stopClient` to kill it
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// watch "forever", ie. even when the main activity has been stopped
// watch "forever", ie. even when this viewModel has been cleared
.observeForever { workInfo ->
if (workInfo?.state == WorkInfo.State.CANCELLED || workInfo?.state == WorkInfo.State.FAILED) {
cancelProxyWork()
Log.d(tag, "proxy work cancelled")
// ⚠ here one could be tempted to call cancelProxyWork
// but it uses viewModelScope which is cancelled when
// this viewModel instance is cleared
// use GlobalScope instead
GlobalScope.launch(Dispatchers.IO) {
// if the proxy process is still running ie. connected
// kill it
if (nymProxy.getState() == NymProxy.Companion.State.CONNECTED) {
Log.d(tag, "stopping proxy")
nymProxy.stop()
Log.i(tag, "proxy work cancelled")
}
}
setDisconnected()
}
}
}
private val callback = object {
fun onStop() {
Log.d(tag, "⚡ ON STOP callback")
_uiState.update { currentState ->
currentState.copy(
connected = false,
loading = false,
)
}
Log.i(tag, "Nym proxy disconnected")
}
}
data class ProxyState(
val connected: Boolean = false,
val loading: Boolean = false
@@ -90,6 +87,15 @@ class MainViewModel(
}
}
private fun setDisconnected() {
_uiState.update { currentState ->
currentState.copy(
connected = false,
loading = false,
)
}
}
fun startProxyWork() {
// start loading state
_uiState.update { currentState ->
@@ -116,7 +122,13 @@ class MainViewModel(
)
}
viewModelScope.launch(Dispatchers.IO) {
nymProxy.stop(callback)
nymProxy.stop()
// TODO instead of delaying an arbitrary amount of time here,
// rely on lib callback for the shutdown connection state
// wait a bit to be sure the proxy client has enough time to
// close connection
delay(2000)
setDisconnected()
}
}
}
@@ -7,36 +7,59 @@ const val nymNativeLib = "nym_socks5_listener"
class NymProxy {
private val tag = "NymProxy"
companion object {
enum class State {
UNINITIALIZED,
CONNECTED,
DISCONNECTED
}
}
// Load the native library "libnym_socks5_listener.so"
init {
System.loadLibrary(nymNativeLib)
Log.i(tag, "loaded native library $nymNativeLib")
}
fun start(serviceProvider: String, callback: Any) {
Log.d(tag, "calling $nymNativeLib:run")
fun start(serviceProvider: String, onStartCbObj: Any, onStopCbObj: Any) {
Log.d(tag, "calling $nymNativeLib:startClient")
try {
run(serviceProvider, callback)
startClient(serviceProvider, onStartCbObj, onStopCbObj)
} catch (e: Throwable) {
Log.e(tag, "$nymNativeLib:run internal error: $e")
Log.e(tag, "$nymNativeLib:startClient internal error: $e")
}
}
/* fun start() {
Log.d(tag, "calling $nymNativeLib:startClient")
return startClient()
} */
fun stop(callback: Any) {
fun stop() {
Log.d(tag, "calling $nymNativeLib:stopClient")
try {
stopClient(callback)
stopClient()
} catch (e: Throwable) {
Log.e(tag, "$nymNativeLib:stopClient internal error: $e")
}
}
private external fun run(spAddress: String, callback: Any)
private external fun stopClient(callbacks: Any)
// private external fun startClient()
fun getState(): State {
Log.d(tag, "calling $nymNativeLib:getClientState")
try {
return when (getClientState()) {
0 -> State.UNINITIALIZED
1 -> State.CONNECTED
2 -> State.DISCONNECTED
else -> throw Error("unknown state")
}
} catch (e: Throwable) {
Log.e(tag, "$nymNativeLib:getClientState internal error: $e")
}
return State.UNINITIALIZED
}
private external fun startClient(
spAddress: String,
onStartCbObj: Any,
onStopCbObj: Any
)
private external fun stopClient()
private external fun getClientState(): Int
}
@@ -71,13 +71,20 @@ class ProxyWorker(
private val json = Json { ignoreUnknownKeys = true }
private val callback = object {
private val onStartCb = object {
fun onStart() {
Log.d(tag, "⚡ ON START callback")
setProgressAsync(workDataOf(State to Status.CONNECTED.name))
}
}
private val onStopCb = object {
fun onStop() {
Log.d(tag, "⚡ ON STOP callback")
setProgressAsync(workDataOf(State to Status.DISCONNECTED.name))
}
}
@RequiresApi(Build.VERSION_CODES.O)
override suspend fun doWork(): Result {
setProgress(workDataOf(State to Status.STARTING.name))
@@ -121,10 +128,12 @@ class ProxyWorker(
Log.w(tag, "using a default service provider $defaultSp")
}
nymProxy.start(serviceProvider ?: defaultSp, callback)
nymProxy.start(serviceProvider ?: defaultSp, onStartCb, onStopCb)
// the state should be already set to DISCONNECTED at this point
// but for the sake of it, reset it
setProgress(workDataOf(State to Status.DISCONNECTED.name))
Log.d(tag, "work finished")
Log.i(tag, "work finished")
Result.success()
} catch (e: Throwable) {
Log.e(tag, "error: ${e.message}")
@@ -134,8 +143,11 @@ class ProxyWorker(
private fun createNotification(): Notification {
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.stop_proxy)
val cancel = applicationContext.getString(R.string.notification_action_stop)
val content = applicationContext.getString(R.string.notification_content)
// this pending intent is used to cancel the worker
// TODO instead of using this intent to cancel the work
// use a custom intent to call `nymProxy.stopClient`
val stopPendingIntent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(id)
@@ -155,8 +167,7 @@ class ProxyWorker(
return NotificationCompat.Builder(applicationContext, channelId)
.setContentTitle(title)
.setTicker(title)
.setContentText("Nym socks5 proxy running")
.setContentText(content)
.setSmallIcon(R.drawable.shield_24)
.setOngoing(true)
.setContentIntent(tapPendingIntent)
@@ -0,0 +1 @@
contact@nymtech.net
@@ -0,0 +1 @@
https://nymtech.net/
@@ -0,0 +1 @@
en-US

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