Merge branch 'develop' into feature/vouchers
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an enhancement to the product
|
||||
title: "[Feature Request]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is...
|
||||
|
||||
**Is your request a feature not related to an existing problem? A new feature.**
|
||||
For example.
|
||||
- Given I am using the nym wallet
|
||||
- When I transfer nym tokens across the network
|
||||
- Then I want to have an url link in the wallet which navigates outside the application to the nym-explorer
|
||||
|
||||
**Where does the feature fit in the Nym real estate?**
|
||||
- Application / UI
|
||||
|
||||
**What is this solving?**
|
||||
How will this improve the product...
|
||||
|
||||
**Is this an update to packages or libraries?**
|
||||
If so, please list them. If not, please ignore this section.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: Report
|
||||
about: To help identify and reproduce issues
|
||||
title: "[Issue]"
|
||||
labels: bug, bug-needs-triage, qa
|
||||
assignees: tommyv1987
|
||||
|
||||
---
|
||||
|
||||
**Describe the issue**
|
||||
A clear and concise description of what the issue is...
|
||||
|
||||
**Expected behaviour**
|
||||
A clear and concise description of what you expected to happen...
|
||||
|
||||
**Stack Traces**
|
||||
If there are stack traces or logs, please provide them here...
|
||||
|
||||
**Steps to Reproduce**
|
||||
Steps to reproduce the behaviour, if you're familiar with BDD syntax, please write it in this style:
|
||||
- Given I was doing X
|
||||
- And I installed Y
|
||||
- When I actioned Y
|
||||
- Then I expect Z
|
||||
|
||||
*An example:*
|
||||
- Given I was setting up a mix-node following the instructions in the docs
|
||||
- And I successfully bonded my node via the the wallet
|
||||
- When I went to start my mixnode
|
||||
- Then I was presented with an error
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem...
|
||||
|
||||
**Which area of Nym were you using?**
|
||||
- UI: [e.g. Websites - network-explorer, nym-website]
|
||||
- Application: [e.g Gateway, Client, Wallet]
|
||||
- OS: [e.g. Ubuntu 20.x, MacOs Big Sur, Windows 10]
|
||||
- Browser: [e.g Chrome (if applicable)]
|
||||
- Version: [e.g. nym binary(0.11.0), browser(94.0)]
|
||||
|
||||
**Additional context**
|
||||
Please provide any other information
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
name: webdriverio tests for nym wallet
|
||||
name: Webdriverio tests for nym wallet
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '/tauri-wallet/*'
|
||||
- 'tauri-wallet/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: tauri-wallet
|
||||
@@ -12,7 +13,6 @@ jobs:
|
||||
test:
|
||||
name: wallet tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -27,18 +27,44 @@ jobs:
|
||||
webkit2gtk-driver
|
||||
xvfb
|
||||
|
||||
- name: Install minimal stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Install yarn for building application
|
||||
run: yarn install
|
||||
|
||||
- name: Build application
|
||||
run: yarn run webpack:build & yarn run tauri:build
|
||||
working-directory: tauri-wallet
|
||||
|
||||
- name: Check binary exists
|
||||
run: |
|
||||
cd target/release/
|
||||
(test -f nym-wallet && echo nym binary exists) || echo wallet does not exist
|
||||
|
||||
- name: Yarn install
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
working-directory: webdriver/
|
||||
working-directory: tauri-wallet/webdriver
|
||||
|
||||
- name: Remove existing user datafile
|
||||
uses: JesseTG/rm@v1.0.2
|
||||
with:
|
||||
path: tauri-wallet/webdriver/common/data/user-data.json
|
||||
|
||||
- name: Create user data json file
|
||||
id: create-json
|
||||
uses: jsdaniell/create-json@1.1.2
|
||||
with:
|
||||
name: "user-data.json"
|
||||
json: ${{ secrets.WALLET_USERDATA }}
|
||||
dir: 'tauri-wallet/webdriver/common/data/'
|
||||
|
||||
- name: Install tauri-driver
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -47,5 +73,5 @@ jobs:
|
||||
args: tauri-driver
|
||||
|
||||
- name: Launch tests
|
||||
run: xvfb-run yarn test:login
|
||||
working-directory: webdriver/
|
||||
run: xvfb-run yarn test:newuser
|
||||
working-directory: tauri-wallet/webdriver
|
||||
|
||||
Generated
+86
-104
@@ -12,15 +12,6 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
@@ -66,15 +57,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anomaly"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "550632e31568ae1a5f47998c3aa48563030fc49b9ec91913ca337cf64fbc5ccb"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
@@ -246,21 +228,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"miniz_oxide 0.4.4",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base-x"
|
||||
version = "0.2.8"
|
||||
@@ -864,9 +831,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.6.3"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7115eae4b8518967b8e737a3ef76025f2d007de7906d88c18f00b8bbfc70f51e"
|
||||
checksum = "edb5204c6ddc4352c74297638b5561f2929d6334866c156e5f3c75e1e1a1436a"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
@@ -875,9 +842,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmrs"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "609eba7eee6d9929927cbdf15f9fe96d250c316c5e1b983c4a47a10f60da5ea7"
|
||||
checksum = "d31147fe89e547e74e2692e4bec35387e1ea406fe8cfb14c0ea177b58fd2a8a9"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"cosmos-sdk-proto",
|
||||
@@ -888,6 +855,8 @@ dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
"rand_core 0.6.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"subtle-encoding",
|
||||
"tendermint",
|
||||
"tendermint-rpc",
|
||||
@@ -1527,7 +1496,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4620d40f6d2601794401d6dd95a5cf69b6c157852539470eeda433a99b3c0efc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"signature",
|
||||
]
|
||||
|
||||
@@ -1541,7 +1509,6 @@ dependencies = [
|
||||
"ed25519",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"sha2",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -1738,6 +1705,16 @@ dependencies = [
|
||||
"miniz_oxide 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flex-error"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b"
|
||||
dependencies = [
|
||||
"eyre",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluvio-wasm-timer"
|
||||
version = "0.2.5"
|
||||
@@ -2120,12 +2097,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.14.6"
|
||||
@@ -2608,6 +2579,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2768,15 +2740,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.1"
|
||||
@@ -3716,15 +3679,6 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "okapi"
|
||||
version = "0.6.0-alpha-1"
|
||||
@@ -3874,6 +3828,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.8.0"
|
||||
@@ -3906,6 +3866,33 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a"
|
||||
dependencies = [
|
||||
"peg-macros",
|
||||
"peg-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-macros"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c"
|
||||
dependencies = [
|
||||
"peg-runtime",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-runtime"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "0.8.3"
|
||||
@@ -4283,9 +4270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2"
|
||||
checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
@@ -4293,12 +4280,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4"
|
||||
checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.9.0",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -4306,9 +4293,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb"
|
||||
checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost",
|
||||
@@ -4927,12 +4914,6 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@@ -5513,7 +5494,7 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4"
|
||||
dependencies = [
|
||||
"itertools 0.10.1",
|
||||
"itertools",
|
||||
"nom",
|
||||
"unicode_categories",
|
||||
]
|
||||
@@ -5999,7 +5980,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"itertools 0.10.1",
|
||||
"itertools",
|
||||
"pkg-config",
|
||||
"strum 0.21.0",
|
||||
"strum_macros 0.21.1",
|
||||
@@ -6214,16 +6195,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendermint"
|
||||
version = "0.21.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0752808f59b612916614e92e43be64e9ba566b762a266bcfcf7dce1b324e9319"
|
||||
checksum = "50d1cdb0236becb17ab35a2ed1566503e412fd910944dc940239857bb7663652"
|
||||
dependencies = [
|
||||
"anomaly",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"ed25519",
|
||||
"ed25519-dalek",
|
||||
"flex-error",
|
||||
"futures",
|
||||
"k256",
|
||||
"num-traits",
|
||||
@@ -6240,21 +6221,32 @@ dependencies = [
|
||||
"subtle 2.4.1",
|
||||
"subtle-encoding",
|
||||
"tendermint-proto",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"url",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-proto"
|
||||
version = "0.21.0"
|
||||
name = "tendermint-config"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfd0371c6b0c7fc4f6b053b8a1bc868a2a47c49a1408c4cd6f595d9f3bd3c238"
|
||||
checksum = "2a1f94250d30e3011130a09756b05985d8dbfbd562cf261b5a17e36d89a37992"
|
||||
dependencies = [
|
||||
"flex-error",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tendermint",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-proto"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff16a7b42bdbcf31c8cd10a4cffc7631f2a301360ba3a3f71dde48eabfa5bced"
|
||||
dependencies = [
|
||||
"anomaly",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flex-error",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"prost",
|
||||
@@ -6262,30 +6254,32 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-rpc"
|
||||
version = "0.21.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fa8d172e3129802d28de980531993dd25cc01841c5bc8fe4a87c7750a7f222"
|
||||
checksum = "c7842dcd5edb60b077572aa92ff8b29fc810b9b463310f9810a2607474130db4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flex-error",
|
||||
"futures",
|
||||
"getrandom 0.1.16",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-proxy",
|
||||
"hyper-rustls",
|
||||
"peg",
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"subtle-encoding",
|
||||
"tendermint",
|
||||
"tendermint-config",
|
||||
"tendermint-proto",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -6577,21 +6571,9 @@ checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.20"
|
||||
@@ -6812,7 +6794,7 @@ dependencies = [
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"flate2",
|
||||
"itertools 0.10.1",
|
||||
"itertools",
|
||||
"log",
|
||||
"mixnet-contract",
|
||||
"network-defaults",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
overflow-checks = true
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
@@ -257,7 +257,7 @@ impl TopologyRefresher {
|
||||
Ok(mixes) => mixes,
|
||||
};
|
||||
|
||||
let gateways = match self.validator_client.get_cached_active_gateways().await {
|
||||
let gateways = match self.validator_client.get_cached_gateways().await {
|
||||
Err(err) => {
|
||||
error!("failed to get network gateways - {}", err);
|
||||
return None;
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "nym-client"
|
||||
version = "0.11.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "nym-socks5-client"
|
||||
version = "0.11.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
[lib]
|
||||
name = "nym_socks5"
|
||||
|
||||
@@ -7,6 +7,7 @@ keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/nymtech/nym"
|
||||
description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation."
|
||||
rust-version = "1.56"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
@@ -301,7 +301,7 @@ impl NymClient {
|
||||
};
|
||||
|
||||
let gateways = match validator_client.get_cached_gateways().await {
|
||||
Err(err) => panic!("{:?}", err),
|
||||
Err(err) => panic!("{}", err),
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
|
||||
@@ -173,9 +173,9 @@ impl PartiallyDelegated {
|
||||
// this call failing is incredibly unlikely, but not impossible.
|
||||
// basically the gateway connection must have failed after executing previous line but
|
||||
// before starting execution of this one.
|
||||
if notify.send(()).is_err() {
|
||||
return Err(GatewayClientError::ConnectionAbruptlyClosed);
|
||||
}
|
||||
notify
|
||||
.send(())
|
||||
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
|
||||
|
||||
let stream_results: Result<_, GatewayClientError> = stream_receiver
|
||||
.await
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "validator-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -25,8 +26,8 @@ network-defaults = { path = "../../network-defaults" }
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
bip39 = { version = "1", features = ["rand"], optional = true }
|
||||
config = { path = "../../config", optional = true }
|
||||
cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
prost = { version = "0.7", default-features = false, optional = true }
|
||||
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
prost = { version = "0.9", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
|
||||
@@ -24,7 +24,6 @@ pub struct Config {
|
||||
mixnode_page_limit: Option<u32>,
|
||||
gateway_page_limit: Option<u32>,
|
||||
mixnode_delegations_page_limit: Option<u32>,
|
||||
gateway_delegations_page_limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -41,7 +40,6 @@ impl Config {
|
||||
mixnode_page_limit: None,
|
||||
gateway_page_limit: None,
|
||||
mixnode_delegations_page_limit: None,
|
||||
gateway_delegations_page_limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +57,6 @@ impl Config {
|
||||
self.mixnode_delegations_page_limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_gateway_delegations_page_limit(mut self, limit: Option<u32>) -> Config {
|
||||
self.gateway_delegations_page_limit = limit;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -74,7 +67,6 @@ pub struct Client<C> {
|
||||
mixnode_page_limit: Option<u32>,
|
||||
gateway_page_limit: Option<u32>,
|
||||
mixnode_delegations_page_limit: Option<u32>,
|
||||
gateway_delegations_page_limit: Option<u32>,
|
||||
|
||||
// ideally they would have been read-only, but unfortunately rust doesn't have such features
|
||||
pub validator_api: validator_api::Client,
|
||||
@@ -100,7 +92,6 @@ impl Client<SigningNymdClient> {
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
|
||||
gateway_delegations_page_limit: config.gateway_delegations_page_limit,
|
||||
validator_api: validator_api_client,
|
||||
nymd: nymd_client,
|
||||
})
|
||||
@@ -136,7 +127,6 @@ impl Client<QueryNymdClient> {
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
|
||||
gateway_delegations_page_limit: config.gateway_delegations_page_limit,
|
||||
validator_api: validator_api_client,
|
||||
nymd: nymd_client,
|
||||
})
|
||||
@@ -339,116 +329,6 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_single_gateway_delegations(
|
||||
&self,
|
||||
identity: mixnet_contract::IdentityKey,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_gateway_delegations(
|
||||
identity.clone(),
|
||||
start_after.take(),
|
||||
self.gateway_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_gateway_delegations(
|
||||
&self,
|
||||
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_all_gateway_delegations(
|
||||
start_after.take(),
|
||||
self.gateway_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_reverse_gateway_delegations(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_reverse_gateway_delegations_paged(
|
||||
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
|
||||
start_after.take(),
|
||||
self.mixnode_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegated_nodes);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_gateway_delegations_of_owner(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
for node_identity in self
|
||||
.get_all_nymd_reverse_gateway_delegations(delegation_owner)
|
||||
.await?
|
||||
{
|
||||
let delegation = self
|
||||
.nymd
|
||||
.get_gateway_delegation(node_identity, delegation_owner)
|
||||
.await?;
|
||||
delegations.push(delegation);
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
@@ -488,12 +368,6 @@ impl ApiClient {
|
||||
Ok(self.validator_api.get_active_mixnodes().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cached_active_gateways(
|
||||
&self,
|
||||
) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_active_gateways().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use cosmrs::rpc::query::Query;
|
||||
use cosmrs::rpc::{self, HttpClient, Order};
|
||||
use cosmrs::tendermint::abci::Transaction;
|
||||
use cosmrs::tendermint::{abci, block, chain};
|
||||
use cosmrs::{AccountId, Coin, Denom};
|
||||
use cosmrs::{tx, AccountId, Coin, Denom};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
@@ -153,12 +153,9 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
|
||||
}
|
||||
|
||||
// disabled until https://github.com/tendermint/tendermint/issues/6802
|
||||
// and consequently https://github.com/informalsystems/tendermint-rs/issues/942 is resolved
|
||||
//
|
||||
// async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
|
||||
// Ok(self.tx(id, false).await?)
|
||||
// }
|
||||
async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
|
||||
Ok(self.tx(id, false).await?)
|
||||
}
|
||||
|
||||
async fn search_tx(&self, query: Query) -> Result<Vec<TxResponse>, NymdError> {
|
||||
// according to https://docs.tendermint.com/master/rpc/#/Info/tx_search
|
||||
|
||||
@@ -13,8 +13,8 @@ use cosmrs::distribution::MsgWithdrawDelegatorReward;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
|
||||
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
|
||||
use cosmrs::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo};
|
||||
use cosmrs::{cosmwasm, rpc, tx, AccountId, Coin};
|
||||
use cosmrs::tx::{Fee, Msg, SignDoc, SignerInfo};
|
||||
use cosmrs::{cosmwasm, rpc, tx, AccountId, Any, Coin};
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
@@ -52,7 +52,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.unwrap_or_default(),
|
||||
instantiate_permission: Default::default(),
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgStoreCode".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -114,7 +114,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
init_msg: serde_json::to_vec(msg)?,
|
||||
funds: options.map(|options| options.funds).unwrap_or_default(),
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgInstantiateContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -154,7 +154,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
new_admin: new_admin.clone(),
|
||||
contract: contract_address.clone(),
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgUpdateAdmin".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -179,7 +179,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgClearAdmin".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -211,7 +211,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
code_id,
|
||||
migrate_msg: serde_json::to_vec(msg)?,
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -243,7 +243,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
msg: serde_json::to_vec(msg)?,
|
||||
funds,
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -278,7 +278,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
msg: serde_json::to_vec(&msg)?,
|
||||
funds,
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
@@ -312,7 +312,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
to_address: recipient_address.clone(),
|
||||
amount,
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
|
||||
@@ -332,7 +332,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
validator_address: validator_address.to_owned(),
|
||||
amount,
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
|
||||
@@ -352,7 +352,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
validator_address: validator_address.to_owned(),
|
||||
amount: Some(amount),
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
|
||||
@@ -370,7 +370,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
delegator_address: delegator_address.to_owned(),
|
||||
validator_address: validator_address.to_owned(),
|
||||
}
|
||||
.to_msg()
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
|
||||
@@ -381,7 +381,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
async fn sign_and_broadcast_async(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
messages: Vec<Any>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_async::Response, NymdError> {
|
||||
@@ -397,7 +397,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
async fn sign_and_broadcast_sync(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
messages: Vec<Any>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_sync::Response, NymdError> {
|
||||
@@ -413,7 +413,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
async fn sign_and_broadcast_commit(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
messages: Vec<Any>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError> {
|
||||
@@ -428,7 +428,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
fn sign_direct(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
messages: Vec<Any>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
signer_data: SignerData,
|
||||
@@ -466,7 +466,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
async fn sign(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
messages: Vec<Any>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<tx::Raw, NymdError> {
|
||||
@@ -506,7 +506,7 @@ impl Client {
|
||||
|
||||
#[async_trait]
|
||||
impl rpc::Client for Client {
|
||||
async fn perform<R>(&self, request: R) -> rpc::Result<R::Response>
|
||||
async fn perform<R>(&self, request: R) -> Result<R::Response, rpc::Error>
|
||||
where
|
||||
R: SimpleRequest,
|
||||
{
|
||||
|
||||
@@ -7,7 +7,10 @@ use cosmrs::{bip32, tx, AccountId};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use cosmrs::rpc::error::{Code, Error as TendermintRpcError};
|
||||
pub use cosmrs::rpc::error::{
|
||||
Error as TendermintRpcError, ErrorDetail as TendermintRpcErrorDetail,
|
||||
};
|
||||
pub use cosmrs::rpc::response_error::{Code, ResponseError};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NymdError {
|
||||
@@ -102,17 +105,21 @@ pub enum NymdError {
|
||||
}
|
||||
|
||||
impl NymdError {
|
||||
pub fn is_tendermint_timeout(&self) -> bool {
|
||||
pub fn is_tendermint_response_timeout(&self) -> bool {
|
||||
match &self {
|
||||
NymdError::TendermintError(tm_err) => {
|
||||
if tm_err.code() == Code::InternalError {
|
||||
NymdError::TendermintError(TendermintRpcError(
|
||||
TendermintRpcErrorDetail::Response(err),
|
||||
_,
|
||||
)) => {
|
||||
let response = &err.source;
|
||||
if response.code() == Code::InternalError {
|
||||
// 0.34 (and earlier) versions of tendermint seemed to be using phrase "timed out waiting ..."
|
||||
// (https://github.com/tendermint/tendermint/blob/v0.34.13/rpc/core/mempool.go#L124)
|
||||
// while 0.35+ has "timeout waiting for ..."
|
||||
// https://github.com/tendermint/tendermint/blob/v0.35.0-rc3/internal/rpc/core/mempool.go#L99
|
||||
// note that as of the time of writing this comment (08.10.2021), the most recent version
|
||||
// of cosmos-sdk (v0.44.1) uses tendermint 0.34.13
|
||||
if let Some(data) = tm_err.data() {
|
||||
if let Some(data) = response.data() {
|
||||
data.contains("timed out") || data.contains("timeout")
|
||||
} else {
|
||||
false
|
||||
@@ -125,14 +132,18 @@ impl NymdError {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_tendermint_duplicate(&self) -> bool {
|
||||
pub fn is_tendermint_response_duplicate(&self) -> bool {
|
||||
match &self {
|
||||
NymdError::TendermintError(tm_err) => {
|
||||
if tm_err.code() == Code::InternalError {
|
||||
NymdError::TendermintError(TendermintRpcError(
|
||||
TendermintRpcErrorDetail::Response(err),
|
||||
_,
|
||||
)) => {
|
||||
let response = &err.source;
|
||||
if response.code() == Code::InternalError {
|
||||
// this particular error message seems to be unchanged between 0.34 and newer versions
|
||||
// https://github.com/tendermint/tendermint/blob/v0.34.13/mempool/errors.go#L10
|
||||
// https://github.com/tendermint/tendermint/blob/v0.35.0-rc3/types/mempool.go#L10
|
||||
if let Some(data) = tm_err.data() {
|
||||
if let Some(data) = response.data() {
|
||||
data.contains("tx already exists in cache")
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -15,8 +15,7 @@ use cosmwasm_std::Coin;
|
||||
use mixnet_contract::{
|
||||
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
|
||||
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
|
||||
PagedGatewayDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse, PagedReverseGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
|
||||
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, StateParams,
|
||||
};
|
||||
use serde::Serialize;
|
||||
@@ -354,82 +353,6 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all delegations towards particular gateway on particular page.
|
||||
pub async fn get_gateway_delegations(
|
||||
&self,
|
||||
gateway_identity: IdentityKey,
|
||||
start_after: Option<Addr>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedGatewayDelegationsResponse, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetGatewayDelegations {
|
||||
gateway_identity,
|
||||
start_after,
|
||||
limit: page_limit,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all gateway delegations on particular page.
|
||||
pub async fn get_all_gateway_delegations(
|
||||
&self,
|
||||
start_after: Option<Vec<u8>>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedAllDelegationsResponse<RawDelegationData>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetAllGatewayDelegations {
|
||||
start_after,
|
||||
limit: page_limit,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all the gateways on which a particular address delegated.
|
||||
pub async fn get_reverse_gateway_delegations_paged(
|
||||
&self,
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedReverseGatewayDelegationsResponse, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetReverseGatewayDelegations {
|
||||
delegation_owner,
|
||||
start_after,
|
||||
limit: page_limit,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Checks value of delegation of given client towards particular gateway.
|
||||
pub async fn get_gateway_delegation(
|
||||
&self,
|
||||
gateway_identity: IdentityKey,
|
||||
delegator: &AccountId,
|
||||
) -> Result<Delegation, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetGatewayDelegation {
|
||||
gateway_identity,
|
||||
address: Addr::unchecked(delegator.as_ref()),
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send funds from one address to another
|
||||
pub async fn send(
|
||||
&self,
|
||||
@@ -698,57 +621,6 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Delegates specified amount of stake to particular gateway.
|
||||
pub async fn delegate_to_gateway(
|
||||
&self,
|
||||
gateway_identity: &str,
|
||||
amount: &Coin,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.get_fee(Operation::DelegateToGateway);
|
||||
|
||||
let req = ExecuteMsg::DelegateToGateway {
|
||||
gateway_identity: gateway_identity.to_string(),
|
||||
};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Delegating to gateway from rust!",
|
||||
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Removes stake delegation from a particular gateway.
|
||||
pub async fn remove_gateway_delegation(
|
||||
&self,
|
||||
gateway_identity: &str,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.get_fee(Operation::UndelegateFromGateway);
|
||||
|
||||
let req = ExecuteMsg::UndelegateFromGateway {
|
||||
gateway_identity: gateway_identity.to_string(),
|
||||
};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Removing gateway delegation from rust!",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_state_params(
|
||||
&self,
|
||||
new_params: StateParams,
|
||||
|
||||
@@ -73,11 +73,6 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_active_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorAPIError> {
|
||||
self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS, routes::ACTIVE])
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -5,18 +5,13 @@ use digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use generic_array::ArrayLength;
|
||||
use hkdf::Hkdf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HkdfError {
|
||||
InvalidOkmLength,
|
||||
}
|
||||
|
||||
/// Perform HKDF `extract` then `expand` as a single step.
|
||||
pub fn extract_then_expand<D>(
|
||||
salt: Option<&[u8]>,
|
||||
ikm: &[u8],
|
||||
info: Option<&[u8]>,
|
||||
okm_length: usize,
|
||||
) -> Result<Vec<u8>, HkdfError>
|
||||
) -> Result<Vec<u8>, hkdf::InvalidLength>
|
||||
where
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
@@ -27,9 +22,7 @@ where
|
||||
|
||||
let hkdf = Hkdf::<D>::new(salt, ikm);
|
||||
let mut okm = vec![0u8; okm_length];
|
||||
if hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm).is_err() {
|
||||
return Err(HkdfError::InvalidOkmLength);
|
||||
}
|
||||
hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm)?;
|
||||
|
||||
Ok(okm)
|
||||
}
|
||||
|
||||
@@ -120,48 +120,6 @@ impl PagedReverseMixDelegationsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedGatewayDelegationsResponse {
|
||||
pub node_identity: IdentityKey,
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<Addr>,
|
||||
}
|
||||
|
||||
impl PagedGatewayDelegationsResponse {
|
||||
pub fn new(
|
||||
node_identity: IdentityKey,
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<Addr>,
|
||||
) -> Self {
|
||||
PagedGatewayDelegationsResponse {
|
||||
node_identity,
|
||||
delegations,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedReverseGatewayDelegationsResponse {
|
||||
pub delegation_owner: Addr,
|
||||
pub delegated_nodes: Vec<IdentityKey>,
|
||||
pub start_next_after: Option<IdentityKey>,
|
||||
}
|
||||
|
||||
impl PagedReverseGatewayDelegationsResponse {
|
||||
pub fn new(
|
||||
delegation_owner: Addr,
|
||||
delegated_nodes: Vec<IdentityKey>,
|
||||
start_next_after: Option<IdentityKey>,
|
||||
) -> Self {
|
||||
PagedReverseGatewayDelegationsResponse {
|
||||
delegation_owner,
|
||||
delegated_nodes,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse<T> {
|
||||
pub delegations: Vec<UnpackedDelegation<T>>,
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::{IdentityKey, SphinxKey};
|
||||
use cosmwasm_std::{coin, Addr, Coin};
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::current_block_height;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
|
||||
pub struct Gateway {
|
||||
pub host: String,
|
||||
@@ -26,9 +24,7 @@ pub struct Gateway {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBond {
|
||||
pub bond_amount: Coin,
|
||||
pub total_delegation: Coin,
|
||||
pub owner: Addr,
|
||||
#[serde(default = "current_block_height")]
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
}
|
||||
@@ -36,7 +32,6 @@ pub struct GatewayBond {
|
||||
impl GatewayBond {
|
||||
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
|
||||
GatewayBond {
|
||||
total_delegation: coin(0, &bond_amount.denom),
|
||||
bond_amount,
|
||||
owner,
|
||||
block_height,
|
||||
@@ -64,27 +59,11 @@ impl GatewayBond {
|
||||
impl PartialOrd for GatewayBond {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// first remove invalid cases
|
||||
if self.bond_amount.denom != self.total_delegation.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
if other.bond_amount.denom != other.total_delegation.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.bond_amount.denom != other.bond_amount.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
// try to order by total bond + delegation
|
||||
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
|
||||
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
|
||||
|
||||
if total_cmp != Ordering::Equal {
|
||||
return Some(total_cmp);
|
||||
}
|
||||
|
||||
// then if those are equal, prefer higher bond over delegation
|
||||
// try to order by total bond
|
||||
let bond_cmp = self
|
||||
.bond_amount
|
||||
.amount
|
||||
@@ -93,15 +72,6 @@ impl PartialOrd for GatewayBond {
|
||||
return Some(bond_cmp);
|
||||
}
|
||||
|
||||
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
|
||||
let delegation_cmp = self
|
||||
.total_delegation
|
||||
.amount
|
||||
.partial_cmp(&other.total_delegation.amount)?;
|
||||
if delegation_cmp != Ordering::Equal {
|
||||
return Some(delegation_cmp);
|
||||
}
|
||||
|
||||
// then check block height
|
||||
let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
|
||||
if height_cmp != Ordering::Equal {
|
||||
@@ -175,20 +145,19 @@ mod tests {
|
||||
#[test]
|
||||
fn gateway_bond_partial_ord() {
|
||||
let _150foos = Coin::new(150, "foo");
|
||||
let _140foos = Coin::new(140, "foo");
|
||||
let _50foos = Coin::new(50, "foo");
|
||||
let _0foos = Coin::new(0, "foo");
|
||||
|
||||
let gate1 = GatewayBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
total_delegation: _50foos.clone(),
|
||||
owner: Addr::unchecked("foo1"),
|
||||
block_height: 100,
|
||||
gateway: gateway_fixture(),
|
||||
};
|
||||
|
||||
let gate2 = GatewayBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
total_delegation: _50foos.clone(),
|
||||
bond_amount: _150foos,
|
||||
owner: Addr::unchecked("foo2"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
@@ -196,15 +165,13 @@ mod tests {
|
||||
|
||||
let gate3 = GatewayBond {
|
||||
bond_amount: _50foos,
|
||||
total_delegation: _150foos.clone(),
|
||||
owner: Addr::unchecked("foo3"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
};
|
||||
|
||||
let gate4 = GatewayBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
total_delegation: _0foos.clone(),
|
||||
bond_amount: _140foos,
|
||||
owner: Addr::unchecked("foo4"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
@@ -212,21 +179,19 @@ mod tests {
|
||||
|
||||
let gate5 = GatewayBond {
|
||||
bond_amount: _0foos,
|
||||
total_delegation: _150foos,
|
||||
owner: Addr::unchecked("foo5"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
};
|
||||
|
||||
// summary:
|
||||
// gate1: 150bond + 50delegation, foo1, 100
|
||||
// gate2: 150bond + 50delegation, foo2, 120
|
||||
// gate3: 50bond + 150delegation, foo3, 120
|
||||
// gate4: 150bond + 0delegation, foo4, 120
|
||||
// gate5: 0bond + 150delegation, foo5, 120
|
||||
// gate1: 150bond, foo1, 100
|
||||
// gate2: 150bond, foo2, 120
|
||||
// gate3: 50bond, foo3, 120
|
||||
// gate4: 140bond, foo4, 120
|
||||
// gate5: 0bond, foo5, 120
|
||||
|
||||
// highest total bond+delegation is used
|
||||
// then bond followed by delegation
|
||||
// highest total bond is used
|
||||
// finally just the rest of the fields
|
||||
|
||||
// gate1 has higher total than gate4 or gate5
|
||||
|
||||
@@ -9,18 +9,10 @@ mod types;
|
||||
|
||||
pub use cosmwasm_std::{Addr, Coin};
|
||||
pub use delegation::{
|
||||
Delegation, PagedAllDelegationsResponse, PagedGatewayDelegationsResponse,
|
||||
PagedMixDelegationsResponse, PagedReverseGatewayDelegationsResponse,
|
||||
Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse,
|
||||
PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation,
|
||||
};
|
||||
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
|
||||
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
|
||||
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams};
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
pub static CURRENT_BLOCK_HEIGHT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub fn current_block_height() -> u64 {
|
||||
CURRENT_BLOCK_HEIGHT.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::current_block_height;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
@@ -41,7 +39,6 @@ pub struct MixNodeBond {
|
||||
pub total_delegation: Coin,
|
||||
pub owner: Addr,
|
||||
pub layer: Layer,
|
||||
#[serde(default = "current_block_height")]
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
}
|
||||
|
||||
@@ -31,25 +31,11 @@ pub enum ExecuteMsg {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
|
||||
DelegateToGateway {
|
||||
gateway_identity: IdentityKey,
|
||||
},
|
||||
|
||||
UndelegateFromGateway {
|
||||
gateway_identity: IdentityKey,
|
||||
},
|
||||
|
||||
RewardMixnode {
|
||||
identity: IdentityKey,
|
||||
// percentage value in range 0-100
|
||||
uptime: u32,
|
||||
},
|
||||
|
||||
RewardGateway {
|
||||
identity: IdentityKey,
|
||||
// percentage value in range 0-100
|
||||
uptime: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
@@ -88,24 +74,6 @@ pub enum QueryMsg {
|
||||
mix_identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
GetGatewayDelegations {
|
||||
gateway_identity: IdentityKey,
|
||||
start_after: Option<Addr>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetAllGatewayDelegations {
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetReverseGatewayDelegations {
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetGatewayDelegation {
|
||||
gateway_identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
LayerDistribution {},
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,10 @@ pub struct StateParams {
|
||||
|
||||
pub minimum_mixnode_bond: Uint128, // minimum amount a mixnode must bond to get into the system
|
||||
pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system
|
||||
|
||||
pub mixnode_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||
pub gateway_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||
pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||
pub gateway_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||
pub mixnode_active_set_size: u32,
|
||||
pub gateway_active_set_size: u32,
|
||||
}
|
||||
|
||||
impl Display for StateParams {
|
||||
@@ -51,30 +49,15 @@ impl Display for StateParams {
|
||||
"mixnode bond reward rate: {}; ",
|
||||
self.mixnode_bond_reward_rate
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"gateway bond reward rate: {}; ",
|
||||
self.gateway_bond_reward_rate
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode delegation reward rate: {}; ",
|
||||
self.mixnode_delegation_reward_rate
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"gateway delegation reward rate: {}; ",
|
||||
self.gateway_delegation_reward_rate
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode active set size: {}",
|
||||
self.mixnode_active_set_size
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"gateway active set size: {} ]",
|
||||
self.gateway_active_set_size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,11 +156,9 @@ impl MessageReceiver {
|
||||
};
|
||||
|
||||
// Finally, remove the zero padding from the message
|
||||
if Self::remove_padding(&mut message).is_err() {
|
||||
return Err(MessageRecoveryError::MalformedReconstructedMessage(
|
||||
used_sets,
|
||||
));
|
||||
};
|
||||
Self::remove_padding(&mut message).map_err(|_| {
|
||||
MessageRecoveryError::MalformedReconstructedMessage(used_sets.clone())
|
||||
})?;
|
||||
|
||||
Ok(Some((
|
||||
ReconstructedMessage {
|
||||
@@ -270,7 +268,6 @@ mod message_receiver {
|
||||
vec![gateway::Node {
|
||||
owner: "foomp4".to_string(),
|
||||
stake: 123,
|
||||
delegation: 456,
|
||||
location: "unknown".to_string(),
|
||||
host: "1.2.3.4".parse().unwrap(),
|
||||
mix_host: "1.2.3.4:1789".parse().unwrap(),
|
||||
|
||||
@@ -72,7 +72,6 @@ pub struct Node {
|
||||
// somebody correct me if I'm wrong, but we should only ever have a single denom of currency
|
||||
// on the network at a type, right?
|
||||
pub stake: u128,
|
||||
pub delegation: u128,
|
||||
pub location: String,
|
||||
pub host: NetworkAddress,
|
||||
// we're keeping this as separate resolved field since we do not want to be resolving the potential
|
||||
@@ -127,7 +126,6 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
|
||||
Ok(Node {
|
||||
owner: bond.owner.as_str().to_owned(),
|
||||
stake: bond.bond_amount.amount.into(),
|
||||
delegation: bond.total_delegation.amount.into(),
|
||||
location: bond.gateway.location.clone(),
|
||||
host,
|
||||
mix_host,
|
||||
|
||||
@@ -22,18 +22,13 @@ pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128(100_000000);
|
||||
|
||||
// percentage annual increase. Given starting value of x, we expect to have 1.1x at the end of the year
|
||||
pub const INITIAL_MIXNODE_BOND_REWARD_RATE: u64 = 110;
|
||||
pub const INITIAL_GATEWAY_BOND_REWARD_RATE: u64 = 110;
|
||||
pub const INITIAL_MIXNODE_DELEGATION_REWARD_RATE: u64 = 110;
|
||||
pub const INITIAL_GATEWAY_DELEGATION_REWARD_RATE: u64 = 110;
|
||||
|
||||
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
|
||||
pub const INITIAL_GATEWAY_ACTIVE_SET_SIZE: u32 = 20;
|
||||
|
||||
fn default_initial_state(owner: Addr) -> State {
|
||||
let mixnode_bond_reward_rate = Decimal::percent(INITIAL_MIXNODE_BOND_REWARD_RATE);
|
||||
let gateway_bond_reward_rate = Decimal::percent(INITIAL_GATEWAY_BOND_REWARD_RATE);
|
||||
let mixnode_delegation_reward_rate = Decimal::percent(INITIAL_MIXNODE_DELEGATION_REWARD_RATE);
|
||||
let gateway_delegation_reward_rate = Decimal::percent(INITIAL_GATEWAY_DELEGATION_REWARD_RATE);
|
||||
|
||||
State {
|
||||
owner,
|
||||
@@ -43,28 +38,17 @@ fn default_initial_state(owner: Addr) -> State {
|
||||
minimum_mixnode_bond: INITIAL_MIXNODE_BOND,
|
||||
minimum_gateway_bond: INITIAL_GATEWAY_BOND,
|
||||
mixnode_bond_reward_rate,
|
||||
gateway_bond_reward_rate,
|
||||
mixnode_delegation_reward_rate,
|
||||
gateway_delegation_reward_rate,
|
||||
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
|
||||
gateway_active_set_size: INITIAL_GATEWAY_ACTIVE_SET_SIZE,
|
||||
},
|
||||
mixnode_epoch_bond_reward: calculate_epoch_reward_rate(
|
||||
INITIAL_DEFAULT_EPOCH_LENGTH,
|
||||
mixnode_bond_reward_rate,
|
||||
),
|
||||
gateway_epoch_bond_reward: calculate_epoch_reward_rate(
|
||||
INITIAL_DEFAULT_EPOCH_LENGTH,
|
||||
gateway_bond_reward_rate,
|
||||
),
|
||||
mixnode_epoch_delegation_reward: calculate_epoch_reward_rate(
|
||||
INITIAL_DEFAULT_EPOCH_LENGTH,
|
||||
mixnode_delegation_reward_rate,
|
||||
),
|
||||
gateway_epoch_delegation_reward: calculate_epoch_reward_rate(
|
||||
INITIAL_DEFAULT_EPOCH_LENGTH,
|
||||
gateway_delegation_reward_rate,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,21 +94,12 @@ pub fn execute(
|
||||
ExecuteMsg::RewardMixnode { identity, uptime } => {
|
||||
transactions::try_reward_mixnode(deps, env, info, identity, uptime)
|
||||
}
|
||||
ExecuteMsg::RewardGateway { identity, uptime } => {
|
||||
transactions::try_reward_gateway(deps, env, info, identity, uptime)
|
||||
}
|
||||
ExecuteMsg::DelegateToMixnode { mix_identity } => {
|
||||
transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
|
||||
transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity)
|
||||
}
|
||||
ExecuteMsg::DelegateToGateway { gateway_identity } => {
|
||||
transactions::try_delegate_to_gateway(deps, env, info, gateway_identity)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromGateway { gateway_identity } => {
|
||||
transactions::try_remove_delegation_from_gateway(deps, info, gateway_identity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,37 +151,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
mix_identity,
|
||||
address,
|
||||
)?),
|
||||
QueryMsg::GetGatewayDelegations {
|
||||
gateway_identity,
|
||||
start_after,
|
||||
limit,
|
||||
} => to_binary(&queries::query_gateway_delegations_paged(
|
||||
deps,
|
||||
gateway_identity,
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetAllGatewayDelegations { start_after, limit } => to_binary(
|
||||
&queries::query_all_gateway_delegations_paged(deps, start_after, limit)?,
|
||||
),
|
||||
QueryMsg::GetReverseGatewayDelegations {
|
||||
delegation_owner,
|
||||
start_after,
|
||||
limit,
|
||||
} => to_binary(&queries::query_reverse_gateway_delegations_paged(
|
||||
deps,
|
||||
delegation_owner,
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetGatewayDelegation {
|
||||
gateway_identity,
|
||||
address,
|
||||
} => to_binary(&queries::query_gateway_delegation(
|
||||
deps,
|
||||
gateway_identity,
|
||||
address,
|
||||
)?),
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
|
||||
@@ -48,15 +48,9 @@ pub enum ContractError {
|
||||
#[error("The bond reward rate for mixnode was set to be lower than 1")]
|
||||
DecreasingMixnodeBondReward,
|
||||
|
||||
#[error("The bond reward rate for gateway was set to be lower than 1")]
|
||||
DecreasingGatewayBondReward,
|
||||
|
||||
#[error("The delegation reward rate for mixnode was set to be lower than 1")]
|
||||
DecreasingMixnodeDelegationReward,
|
||||
|
||||
#[error("The delegation reward rate for gateway was set to be lower than 1")]
|
||||
DecreasingGatewayDelegationReward,
|
||||
|
||||
#[error("The node had uptime larger than 100%")]
|
||||
UnexpectedUptime,
|
||||
|
||||
@@ -83,10 +77,4 @@ pub enum ContractError {
|
||||
identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
|
||||
#[error("Could not find any delegation information associated with gateway {identity} for {address}")]
|
||||
NoGatewayDelegationFound {
|
||||
identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -209,10 +209,7 @@ impl<'a, T: Clone + Serialize + DeserializeOwned> Iterator for Delegations<'a, T
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::queries::tests::store_n_mix_delegations;
|
||||
use crate::storage::{
|
||||
all_gateway_delegations_read, all_mix_delegations_read, gateway_delegations,
|
||||
mix_delegations,
|
||||
};
|
||||
use crate::storage::{all_mix_delegations_read, mix_delegations};
|
||||
use crate::support::tests::helpers;
|
||||
use cosmwasm_std::testing::mock_dependencies;
|
||||
use mixnet_contract::RawDelegationData;
|
||||
@@ -389,66 +386,4 @@ mod tests {
|
||||
UnpackedDelegation::new(delegation_owner2, node_identity2, raw_delegation.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_gateway_delegations() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation = RawDelegationData::new(1000u128.into(), 42);
|
||||
let mut start_after = None;
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation)
|
||||
.unwrap();
|
||||
|
||||
let bucket = all_gateway_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
start_after = response.start_next_after;
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 1);
|
||||
assert_eq!(
|
||||
delegations[0],
|
||||
UnpackedDelegation::new(
|
||||
delegation_owner1.clone(),
|
||||
node_identity1.clone(),
|
||||
raw_delegation.clone()
|
||||
)
|
||||
);
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation)
|
||||
.unwrap();
|
||||
|
||||
let bucket = all_gateway_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
start_after = response.start_next_after;
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 2);
|
||||
assert_eq!(
|
||||
delegations[1],
|
||||
UnpackedDelegation::new(
|
||||
delegation_owner2.clone(),
|
||||
node_identity2.clone(),
|
||||
raw_delegation.clone()
|
||||
)
|
||||
);
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity1)
|
||||
.remove(delegation_owner1.as_bytes());
|
||||
|
||||
let bucket = all_gateway_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 1);
|
||||
assert_eq!(
|
||||
delegations[0],
|
||||
UnpackedDelegation::new(delegation_owner2, node_identity2, raw_delegation.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,17 @@
|
||||
use crate::error::ContractError;
|
||||
use crate::helpers::get_all_delegations_paged;
|
||||
use crate::storage::{
|
||||
all_gateway_delegations_read, all_mix_delegations_read, gateway_delegations_read,
|
||||
gateways_owners_read, gateways_read, mix_delegations_read, mixnodes_owners_read, mixnodes_read,
|
||||
read_layer_distribution, read_state_params, reverse_gateway_delegations_read,
|
||||
all_mix_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read,
|
||||
mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params,
|
||||
reverse_mix_delegations_read,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{coin, Addr, Deps, Order, StdResult};
|
||||
use mixnet_contract::{
|
||||
Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond,
|
||||
MixOwnershipResponse, PagedAllDelegationsResponse, PagedGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
|
||||
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, RawDelegationData,
|
||||
StateParams,
|
||||
MixOwnershipResponse, PagedAllDelegationsResponse, PagedGatewayResponse,
|
||||
PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseMixDelegationsResponse,
|
||||
RawDelegationData, StateParams,
|
||||
};
|
||||
|
||||
const BOND_PAGE_MAX_LIMIT: u32 = 100;
|
||||
@@ -211,114 +209,11 @@ pub(crate) fn query_mixnode_delegation(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn query_gateway_delegations_paged(
|
||||
deps: Deps,
|
||||
gateway_identity: IdentityKey,
|
||||
start_after: Option<Addr>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedGatewayDelegationsResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
let start = calculate_start_value(start_after);
|
||||
|
||||
let delegations = gateway_delegations_read(deps.storage, &gateway_identity)
|
||||
.range(start.as_deref(), None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| {
|
||||
res.map(|entry| {
|
||||
Delegation::new(
|
||||
Addr::unchecked(String::from_utf8(entry.0).expect(
|
||||
"Non-UTF8 address used as key in bucket. The storage is corrupted!",
|
||||
)),
|
||||
coin(entry.1.amount.u128(), DENOM),
|
||||
entry.1.block_height,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<StdResult<Vec<Delegation>>>()?;
|
||||
|
||||
let start_next_after = delegations.last().map(|delegation| delegation.owner());
|
||||
|
||||
Ok(PagedGatewayDelegationsResponse::new(
|
||||
gateway_identity,
|
||||
delegations,
|
||||
start_next_after,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn query_all_gateway_delegations_paged(
|
||||
deps: Deps,
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedAllDelegationsResponse<RawDelegationData>> {
|
||||
let limit = limit
|
||||
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let bucket = all_gateway_delegations_read::<RawDelegationData>(deps.storage);
|
||||
let start = start_after.map(|mut v| {
|
||||
v.push(0);
|
||||
v
|
||||
});
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start, limit)
|
||||
}
|
||||
|
||||
pub(crate) fn query_reverse_gateway_delegations_paged(
|
||||
deps: Deps,
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedReverseGatewayDelegationsResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
let start = calculate_start_value(start_after);
|
||||
|
||||
let delegations = reverse_gateway_delegations_read(deps.storage, &delegation_owner)
|
||||
.range(start.as_deref(), None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| {
|
||||
res.map(|entry| {
|
||||
String::from_utf8(entry.0)
|
||||
.expect("Non-UTF8 address used as key in bucket. The storage is corrupted!")
|
||||
})
|
||||
})
|
||||
.collect::<StdResult<Vec<IdentityKey>>>()?;
|
||||
|
||||
let start_next_after = delegations.last().cloned();
|
||||
|
||||
Ok(PagedReverseGatewayDelegationsResponse::new(
|
||||
delegation_owner,
|
||||
delegations,
|
||||
start_next_after,
|
||||
))
|
||||
}
|
||||
|
||||
// queries for delegation value of given address for particular node
|
||||
pub(crate) fn query_gateway_delegation(
|
||||
deps: Deps,
|
||||
gateway_identity: IdentityKey,
|
||||
address: Addr,
|
||||
) -> Result<Delegation, ContractError> {
|
||||
match gateway_delegations_read(deps.storage, &gateway_identity).may_load(address.as_bytes())? {
|
||||
Some(delegation_value) => Ok(Delegation::new(
|
||||
address,
|
||||
coin(delegation_value.amount.u128(), DENOM),
|
||||
delegation_value.block_height,
|
||||
)),
|
||||
None => Err(ContractError::NoGatewayDelegationFound {
|
||||
identity: gateway_identity,
|
||||
address,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::state::State;
|
||||
use crate::storage::{config, gateway_delegations, gateways, mix_delegations, mixnodes};
|
||||
use crate::storage::{config, gateways, mix_delegations, mixnodes};
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::{
|
||||
good_gateway_bond, good_mixnode_bond, raw_delegation_fixture,
|
||||
@@ -673,16 +568,11 @@ pub(crate) mod tests {
|
||||
minimum_mixnode_bond: 123u128.into(),
|
||||
minimum_gateway_bond: 456u128.into(),
|
||||
mixnode_bond_reward_rate: "1.23".parse().unwrap(),
|
||||
gateway_bond_reward_rate: "4.56".parse().unwrap(),
|
||||
mixnode_delegation_reward_rate: "7.89".parse().unwrap(),
|
||||
gateway_delegation_reward_rate: "0.12".parse().unwrap(),
|
||||
mixnode_active_set_size: 1000,
|
||||
gateway_active_set_size: 20,
|
||||
},
|
||||
mixnode_epoch_bond_reward: "1.23".parse().unwrap(),
|
||||
gateway_epoch_bond_reward: "4.56".parse().unwrap(),
|
||||
mixnode_epoch_delegation_reward: "7.89".parse().unwrap(),
|
||||
gateway_epoch_delegation_reward: "0.12".parse().unwrap(),
|
||||
};
|
||||
|
||||
config(deps.as_mut().storage).save(&dummy_state).unwrap();
|
||||
@@ -1203,524 +1093,4 @@ pub(crate) mod tests {
|
||||
assert_eq!(2, page2.delegated_nodes.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_n_gateway_delegations(
|
||||
n: u32,
|
||||
storage: &mut dyn Storage,
|
||||
node_identity: &IdentityKey,
|
||||
) {
|
||||
for i in 0..n {
|
||||
let address = format!("address{}", i);
|
||||
gateway_delegations(storage, node_identity)
|
||||
.save(address.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_gateway_delegations_paged {
|
||||
use super::*;
|
||||
use crate::storage::gateway_delegations;
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity,
|
||||
None,
|
||||
Option::from(limit),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 =
|
||||
query_gateway_delegations_paged(deps.as_ref(), node_identity, None, None).unwrap();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT,
|
||||
page1.delegations.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity,
|
||||
None,
|
||||
Option::from(crazy_limit),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_works() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("1".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.delegations.len());
|
||||
|
||||
// save another
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("2".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("3".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = Addr::unchecked("2");
|
||||
let page2 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.delegations.len());
|
||||
|
||||
// save another one
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("4".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let start_after = Addr::unchecked("2");
|
||||
let page2 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity,
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegations.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_deletion_query_returns_current_delegation_value() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
let delegation_owner = Addr::unchecked("bar");
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegation_owner.as_bytes(),
|
||||
&&RawDelegationData::new(42u128.into(), 12_345),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Ok(Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
coin(42, DENOM),
|
||||
12_345
|
||||
)),
|
||||
query_gateway_delegation(deps.as_ref(), node_identity, delegation_owner)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_deletion_query_returns_error_if_delegation_doesnt_exist() {
|
||||
let mut deps = helpers::init_contract();
|
||||
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar");
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
|
||||
assert_eq!(
|
||||
Err(ContractError::NoGatewayDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: delegation_owner1.clone(),
|
||||
}),
|
||||
query_gateway_delegation(
|
||||
deps.as_ref(),
|
||||
node_identity1.clone(),
|
||||
delegation_owner1.clone()
|
||||
)
|
||||
);
|
||||
|
||||
// add delegation from a different address
|
||||
gateway_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Err(ContractError::NoGatewayDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: delegation_owner1.clone(),
|
||||
}),
|
||||
query_gateway_delegation(
|
||||
deps.as_ref(),
|
||||
node_identity1.clone(),
|
||||
delegation_owner1.clone()
|
||||
)
|
||||
);
|
||||
|
||||
// add delegation for a different node
|
||||
gateway_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Err(ContractError::NoGatewayDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: delegation_owner1.clone()
|
||||
}),
|
||||
query_gateway_delegation(deps.as_ref(), node_identity1, delegation_owner1)
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_all_gateway_delegations_paged {
|
||||
use super::*;
|
||||
use crate::helpers::identity_and_owner_to_bytes;
|
||||
use crate::storage::gateway_delegations;
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(limit))
|
||||
.unwrap();
|
||||
assert_eq!(limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_all_gateway_delegations_paged(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT,
|
||||
page1.delegations.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(crazy_limit))
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_works() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("1".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.delegations.len());
|
||||
|
||||
// save another
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("2".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("3".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = identity_and_owner_to_bytes(&node_identity, &Addr::unchecked("2"));
|
||||
let page2 = query_all_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.clone()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.delegations.len());
|
||||
|
||||
// save another one
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("4".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let page2 = query_all_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegations.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_reverse_gateway_delegations_paged {
|
||||
use super::*;
|
||||
use crate::storage::reverse_gateway_delegations;
|
||||
|
||||
fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) {
|
||||
for i in 0..n {
|
||||
let node_identity = format!("node{}", i);
|
||||
reverse_gateway_delegations(storage, delegation_owner)
|
||||
.save(node_identity.as_bytes(), &())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let delegation_owner = Addr::unchecked("foo");
|
||||
store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner);
|
||||
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner,
|
||||
None,
|
||||
Option::from(limit),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(limit, page1.delegated_nodes.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let delegation_owner = Addr::unchecked("foo");
|
||||
store_n_reverse_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&delegation_owner,
|
||||
);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT,
|
||||
page1.delegated_nodes.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let delegation_owner = Addr::unchecked("foo");
|
||||
store_n_reverse_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&delegation_owner,
|
||||
);
|
||||
|
||||
// query with a crazy high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner,
|
||||
None,
|
||||
Option::from(crazy_limit),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.delegated_nodes.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_works() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let delegation_owner = Addr::unchecked("bar");
|
||||
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save("1".as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.delegated_nodes.len());
|
||||
|
||||
// save another
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save("2".as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegated_nodes.len());
|
||||
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save("3".as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegated_nodes.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after: IdentityKey = String::from("2");
|
||||
let page2 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner.clone(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.delegated_nodes.len());
|
||||
|
||||
// save another one
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save("4".as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
let start_after = String::from("2");
|
||||
let page2 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner,
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegated_nodes.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,5 @@ pub struct State {
|
||||
|
||||
// helper values to avoid having to recalculate them on every single payment operation
|
||||
pub mixnode_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
|
||||
pub gateway_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
|
||||
pub mixnode_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
|
||||
pub gateway_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
|
||||
}
|
||||
|
||||
@@ -32,9 +32,7 @@ const PREFIX_GATEWAYS: &[u8] = b"gt";
|
||||
const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go";
|
||||
|
||||
const PREFIX_MIX_DELEGATION: &[u8] = b"md";
|
||||
const PREFIX_GATEWAY_DELEGATION: &[u8] = b"gd";
|
||||
const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm";
|
||||
const PREFIX_REVERSE_GATEWAY_DELEGATION: &[u8] = b"dg";
|
||||
|
||||
// Contract-level stuff
|
||||
|
||||
@@ -63,14 +61,6 @@ pub(crate) fn read_mixnode_epoch_bond_reward_rate(storage: &dyn Storage) -> Deci
|
||||
.mixnode_epoch_bond_reward
|
||||
}
|
||||
|
||||
pub(crate) fn read_gateway_epoch_bond_reward_rate(storage: &dyn Storage) -> Decimal {
|
||||
// same justification as in `read_state_params` for the unwrap
|
||||
config_read(storage)
|
||||
.load()
|
||||
.unwrap()
|
||||
.gateway_epoch_bond_reward
|
||||
}
|
||||
|
||||
pub(crate) fn read_mixnode_epoch_delegation_reward_rate(storage: &dyn Storage) -> Decimal {
|
||||
// same justification as in `read_state_params` for the unwrap
|
||||
config_read(storage)
|
||||
@@ -79,14 +69,6 @@ pub(crate) fn read_mixnode_epoch_delegation_reward_rate(storage: &dyn Storage) -
|
||||
.mixnode_epoch_delegation_reward
|
||||
}
|
||||
|
||||
pub(crate) fn read_gateway_epoch_delegation_reward_rate(storage: &dyn Storage) -> Decimal {
|
||||
// same justification as in `read_state_params` for the unwrap
|
||||
config_read(storage)
|
||||
.load()
|
||||
.unwrap()
|
||||
.gateway_epoch_delegation_reward
|
||||
}
|
||||
|
||||
pub fn layer_distribution(storage: &mut dyn Storage) -> Singleton<LayerDistribution> {
|
||||
singleton(storage, LAYER_DISTRIBUTION_KEY)
|
||||
}
|
||||
@@ -214,56 +196,6 @@ pub(crate) fn increase_mix_delegated_stakes(
|
||||
Ok(total_rewarded)
|
||||
}
|
||||
|
||||
pub(crate) fn increase_gateway_delegated_stakes(
|
||||
storage: &mut dyn Storage,
|
||||
gateway_identity: IdentityKeyRef,
|
||||
scaled_reward_rate: Decimal,
|
||||
reward_blockstamp: u64,
|
||||
) -> StdResult<Uint128> {
|
||||
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
|
||||
|
||||
let mut total_rewarded = Uint128::zero();
|
||||
let mut chunk_start: Option<Vec<_>> = None;
|
||||
loop {
|
||||
// get `chunk_size` of delegations
|
||||
let delegations_chunk = gateway_delegations_read(storage, gateway_identity)
|
||||
.range(chunk_start.as_deref(), None, Order::Ascending)
|
||||
.take(chunk_size)
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
if delegations_chunk.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// append 0 byte to the last value to start with whatever is the next suceeding key
|
||||
chunk_start = Some(
|
||||
delegations_chunk
|
||||
.last()
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(std::iter::once(0u8))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// and for each of them increase the stake proportionally to the reward
|
||||
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
|
||||
// since they delegated
|
||||
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
|
||||
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp {
|
||||
let reward = delegation.amount * scaled_reward_rate;
|
||||
delegation.amount += reward;
|
||||
total_rewarded += reward;
|
||||
gateway_delegations(storage, gateway_identity)
|
||||
.save(&delegator_address, &delegation)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(total_rewarded)
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn read_mixnode_bond(
|
||||
@@ -338,53 +270,6 @@ pub fn reverse_mix_delegations_read<'a>(
|
||||
ReadonlyBucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()])
|
||||
}
|
||||
|
||||
pub fn all_gateway_delegations_read<T>(storage: &dyn Storage) -> ReadonlyBucket<T>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
{
|
||||
bucket_read(storage, PREFIX_GATEWAY_DELEGATION)
|
||||
}
|
||||
|
||||
pub fn gateway_delegations<'a>(
|
||||
storage: &'a mut dyn Storage,
|
||||
gateway_identity: IdentityKeyRef,
|
||||
) -> Bucket<'a, RawDelegationData> {
|
||||
Bucket::multilevel(
|
||||
storage,
|
||||
&[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn gateway_delegations_read<'a>(
|
||||
storage: &'a dyn Storage,
|
||||
gateway_identity: IdentityKeyRef,
|
||||
) -> ReadonlyBucket<'a, RawDelegationData> {
|
||||
ReadonlyBucket::multilevel(
|
||||
storage,
|
||||
&[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reverse_gateway_delegations<'a>(
|
||||
storage: &'a mut dyn Storage,
|
||||
owner: &Addr,
|
||||
) -> Bucket<'a, ()> {
|
||||
Bucket::multilevel(
|
||||
storage,
|
||||
&[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reverse_gateway_delegations_read<'a>(
|
||||
storage: &'a dyn Storage,
|
||||
owner: &Addr,
|
||||
) -> ReadonlyBucket<'a, ()> {
|
||||
ReadonlyBucket::multilevel(
|
||||
storage,
|
||||
&[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()],
|
||||
)
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn read_gateway_bond(
|
||||
@@ -396,17 +281,6 @@ pub(crate) fn read_gateway_bond(
|
||||
Ok(node.bond_amount.amount)
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn read_gateway_delegation(
|
||||
storage: &dyn Storage,
|
||||
identity: &[u8],
|
||||
) -> StdResult<cosmwasm_std::Uint128> {
|
||||
let bucket = gateways_read(storage);
|
||||
let node = bucket.load(identity)?;
|
||||
Ok(node.total_delegation.amount)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -499,7 +373,6 @@ mod tests {
|
||||
|
||||
let gateway_bond = GatewayBond {
|
||||
bond_amount: coin(bond_value, DENOM),
|
||||
total_delegation: coin(0, DENOM),
|
||||
owner: node_owner.clone(),
|
||||
block_height: 12_345,
|
||||
gateway: Gateway {
|
||||
@@ -551,39 +424,6 @@ mod tests {
|
||||
assert_eq!(raw_delegation2, res2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_gateway_delegations_read_retrieval() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000);
|
||||
let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000);
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation1)
|
||||
.unwrap();
|
||||
gateway_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation2)
|
||||
.unwrap();
|
||||
|
||||
let res1 = all_gateway_delegations_read::<RawDelegationData>(&deps.storage)
|
||||
.load(&*identity_and_owner_to_bytes(
|
||||
&node_identity1,
|
||||
&delegation_owner1,
|
||||
))
|
||||
.unwrap();
|
||||
let res2 = all_gateway_delegations_read::<RawDelegationData>(&deps.storage)
|
||||
.load(&*identity_and_owner_to_bytes(
|
||||
&node_identity2,
|
||||
&delegation_owner2,
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(raw_delegation1, res1);
|
||||
assert_eq!(raw_delegation2, res2);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod increasing_mix_delegated_stakes {
|
||||
use super::*;
|
||||
@@ -858,279 +698,4 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod increasing_gateway_delegated_stakes {
|
||||
use super::*;
|
||||
use crate::queries::query_gateway_delegations_paged;
|
||||
use cosmwasm_std::testing::mock_dependencies;
|
||||
|
||||
#[test]
|
||||
fn when_there_are_no_delegations() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
42,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// there was no increase
|
||||
assert!(total_increase.is_zero());
|
||||
|
||||
// there are no 'new' delegations magically added
|
||||
assert!(
|
||||
query_gateway_delegations_paged(deps.as_ref(), node_identity, None, None)
|
||||
.unwrap()
|
||||
.delegations
|
||||
.is_empty()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_there_is_a_single_delegation() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
let delegation_blockstamp = 42;
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
let delegator_address = Addr::unchecked("bob");
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Uint128(1), total_increase);
|
||||
|
||||
// amount is incremented, block height remains the same
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1001u128.into(), 42),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_there_is_a_single_delegation_depending_on_blockstamp() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
let delegation_blockstamp = 42;
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
let delegator_address = Addr::unchecked("bob");
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// there was no increase
|
||||
assert!(total_increase.is_zero());
|
||||
|
||||
// amount is not incremented
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// there is an increase now, that the lock period has passed
|
||||
assert_eq!(Uint128(1), total_increase);
|
||||
|
||||
// amount is incremented
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1001u128.into(), delegation_blockstamp),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_there_are_multiple_delegations() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
let delegation_blockstamp = 42;
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
for i in 0..100 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Uint128(100), total_increase);
|
||||
|
||||
for i in 0..100 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
assert_eq!(
|
||||
raw_delegation_fixture(1001),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_there_are_more_delegations_than_page_size() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
let delegation_blockstamp = 42;
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Uint128(queries::DELEGATION_PAGE_MAX_LIMIT as u128 * 10),
|
||||
total_increase
|
||||
);
|
||||
|
||||
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
assert_eq!(
|
||||
raw_delegation_fixture(1001),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod reverse_gateway_delegations {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers;
|
||||
|
||||
#[test]
|
||||
fn reverse_gateway_delegation_exists() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
let delegation_owner = Addr::unchecked("bar");
|
||||
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save(node_identity.as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner)
|
||||
.may_load(node_identity.as_bytes())
|
||||
.unwrap()
|
||||
.is_some(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reverse_gateway_delegation_returns_none_if_delegation_doesnt_exist() {
|
||||
let mut deps = helpers::init_contract();
|
||||
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar");
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
|
||||
assert!(
|
||||
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
|
||||
.may_load(node_identity1.as_bytes())
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
// add delegation for a different node
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner1)
|
||||
.save(node_identity2.as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
|
||||
.may_load(node_identity1.as_bytes())
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
// add delegation from a different owner
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner2)
|
||||
.save(node_identity1.as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
|
||||
.may_load(node_identity1.as_bytes())
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ name = "nym-gateway"
|
||||
version = "0.11.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ authors = [
|
||||
"Drazen Urch <durch@users.noreply.github.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
Generated
+94
-108
@@ -12,15 +12,6 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
@@ -42,15 +33,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anomaly"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "550632e31568ae1a5f47998c3aa48563030fc49b9ec91913ca337cf64fbc5ccb"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.43"
|
||||
@@ -148,21 +130,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"miniz_oxide 0.4.4",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
@@ -430,12 +397,9 @@ version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.43",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -632,9 +596,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.6.3"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7115eae4b8518967b8e737a3ef76025f2d007de7906d88c18f00b8bbfc70f51e"
|
||||
checksum = "edb5204c6ddc4352c74297638b5561f2929d6334866c156e5f3c75e1e1a1436a"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
@@ -643,9 +607,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmrs"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "609eba7eee6d9929927cbdf15f9fe96d250c316c5e1b983c4a47a10f60da5ea7"
|
||||
checksum = "d31147fe89e547e74e2692e4bec35387e1ea406fe8cfb14c0ea177b58fd2a8a9"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"cosmos-sdk-proto",
|
||||
@@ -656,6 +620,8 @@ dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
"rand_core 0.6.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"subtle-encoding",
|
||||
"tendermint",
|
||||
"tendermint-rpc",
|
||||
@@ -1077,7 +1043,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4620d40f6d2601794401d6dd95a5cf69b6c157852539470eeda433a99b3c0efc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"signature",
|
||||
]
|
||||
|
||||
@@ -1089,9 +1054,6 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"sha2",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -1228,6 +1190,16 @@ dependencies = [
|
||||
"miniz_oxide 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flex-error"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b"
|
||||
dependencies = [
|
||||
"eyre",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1531,12 +1503,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.14.5"
|
||||
@@ -1976,6 +1942,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2098,15 +2065,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.1"
|
||||
@@ -2610,15 +2568,6 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
@@ -2744,6 +2693,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.8.0"
|
||||
@@ -2753,6 +2708,33 @@ dependencies = [
|
||||
"crypto-mac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a"
|
||||
dependencies = [
|
||||
"peg-macros",
|
||||
"peg-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-macros"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c"
|
||||
dependencies = [
|
||||
"peg-runtime",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-runtime"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
@@ -3061,9 +3043,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2"
|
||||
checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
@@ -3071,12 +3053,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4"
|
||||
checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.9.0",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -3084,9 +3066,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb"
|
||||
checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost",
|
||||
@@ -3476,12 +3458,6 @@ dependencies = [
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.3"
|
||||
@@ -4193,7 +4169,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"itertools 0.10.1",
|
||||
"itertools",
|
||||
"pkg-config",
|
||||
"strum 0.21.0",
|
||||
"strum_macros 0.21.1",
|
||||
@@ -4408,16 +4384,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendermint"
|
||||
version = "0.21.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0752808f59b612916614e92e43be64e9ba566b762a266bcfcf7dce1b324e9319"
|
||||
checksum = "50d1cdb0236becb17ab35a2ed1566503e412fd910944dc940239857bb7663652"
|
||||
dependencies = [
|
||||
"anomaly",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"ed25519",
|
||||
"ed25519-dalek",
|
||||
"flex-error",
|
||||
"futures",
|
||||
"k256",
|
||||
"num-traits",
|
||||
@@ -4434,21 +4410,32 @@ dependencies = [
|
||||
"subtle",
|
||||
"subtle-encoding",
|
||||
"tendermint-proto",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"url",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-proto"
|
||||
version = "0.21.0"
|
||||
name = "tendermint-config"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfd0371c6b0c7fc4f6b053b8a1bc868a2a47c49a1408c4cd6f595d9f3bd3c238"
|
||||
checksum = "2a1f94250d30e3011130a09756b05985d8dbfbd562cf261b5a17e36d89a37992"
|
||||
dependencies = [
|
||||
"flex-error",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tendermint",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-proto"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff16a7b42bdbcf31c8cd10a4cffc7631f2a301360ba3a3f71dde48eabfa5bced"
|
||||
dependencies = [
|
||||
"anomaly",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flex-error",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"prost",
|
||||
@@ -4456,30 +4443,32 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-rpc"
|
||||
version = "0.21.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fa8d172e3129802d28de980531993dd25cc01841c5bc8fe4a87c7750a7f222"
|
||||
checksum = "c7842dcd5edb60b077572aa92ff8b29fc810b9b463310f9810a2607474130db4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flex-error",
|
||||
"futures",
|
||||
"getrandom 0.1.16",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-proxy",
|
||||
"hyper-rustls",
|
||||
"peg",
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"subtle-encoding",
|
||||
"tendermint",
|
||||
"tendermint-config",
|
||||
"tendermint-proto",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -4656,21 +4645,9 @@ checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.19"
|
||||
@@ -4812,7 +4789,7 @@ dependencies = [
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"flate2",
|
||||
"itertools 0.10.1",
|
||||
"itertools",
|
||||
"log",
|
||||
"mixnet-contract",
|
||||
"network-defaults",
|
||||
@@ -5023,6 +5000,15 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2"
|
||||
version = "0.1.1"
|
||||
|
||||
@@ -7,12 +7,19 @@ SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
A Rust and Tauri desktop wallet implementation.
|
||||
|
||||
## Installation prerequisites
|
||||
## Installation prerequisites Linux / Mac
|
||||
|
||||
- `Yarn`
|
||||
- `NodeJS >= v16.8.0`
|
||||
- `Rust & cargo >= v1.51`
|
||||
|
||||
## Installation prerequisites Windows
|
||||
|
||||
- When running on Windows you will need to install c++ build tools
|
||||
- An easy guide to get rust up and running [Installation]("http://kennykerr.ca/2019/11/18/rust-getting-started/")
|
||||
- When installing NodeJS please use the `current features` version
|
||||
- Using a package manager like [Chocolatey]("chocolatey.org") is recommended
|
||||
|
||||
## Installation
|
||||
|
||||
Inside of the `tauri-wallet` folder, run the following commands
|
||||
|
||||
@@ -8,6 +8,7 @@ repository = ""
|
||||
default-run = "nym_wallet"
|
||||
edition = "2018"
|
||||
build = "src/build.rs"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -23,12 +24,12 @@ dirs = "3.0"
|
||||
# url = "2.2"
|
||||
bip39 = "1.0"
|
||||
thiserror = "1.0"
|
||||
tendermint-rpc = "0.21.0"
|
||||
tendermint-rpc = "0.23.0"
|
||||
ts-rs = "3.0"
|
||||
url = "2.0"
|
||||
rand = "0.6.5"
|
||||
|
||||
cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"] }
|
||||
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"] }
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" }
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
use mixnet_contract::{Gateway, MixNode};
|
||||
use std::sync::Arc;
|
||||
use tauri::{Menu, MenuItem};
|
||||
use tokio::sync::RwLock;
|
||||
use ts_rs::export;
|
||||
use validator_client::nymd::fee_helpers::Operation;
|
||||
@@ -35,6 +36,12 @@ macro_rules! format_err {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn create_menu_items() -> Menu {
|
||||
Menu::new()
|
||||
.add_native_item(MenuItem::Copy)
|
||||
.add_native_item(MenuItem::Paste)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.manage(Arc::new(RwLock::new(State::default())))
|
||||
@@ -51,16 +58,14 @@ fn main() {
|
||||
unbond_gateway,
|
||||
delegate_to_mixnode,
|
||||
undelegate_from_mixnode,
|
||||
delegate_to_gateway,
|
||||
undelegate_from_gateway,
|
||||
send,
|
||||
create_new_account,
|
||||
get_fee,
|
||||
get_state_params,
|
||||
update_state_params,
|
||||
get_reverse_mix_delegations_paged,
|
||||
get_reverse_gateway_delegations_paged,
|
||||
])
|
||||
.menu(create_menu_items())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -16,11 +16,8 @@ pub struct TauriStateParams {
|
||||
minimum_mixnode_bond: String,
|
||||
minimum_gateway_bond: String,
|
||||
mixnode_bond_reward_rate: String,
|
||||
gateway_bond_reward_rate: String,
|
||||
mixnode_delegation_reward_rate: String,
|
||||
gateway_delegation_reward_rate: String,
|
||||
mixnode_active_set_size: u32,
|
||||
gateway_active_set_size: u32,
|
||||
}
|
||||
|
||||
impl From<StateParams> for TauriStateParams {
|
||||
@@ -30,11 +27,8 @@ impl From<StateParams> for TauriStateParams {
|
||||
minimum_mixnode_bond: p.minimum_mixnode_bond.to_string(),
|
||||
minimum_gateway_bond: p.minimum_gateway_bond.to_string(),
|
||||
mixnode_bond_reward_rate: p.mixnode_bond_reward_rate.to_string(),
|
||||
gateway_bond_reward_rate: p.gateway_bond_reward_rate.to_string(),
|
||||
mixnode_delegation_reward_rate: p.mixnode_delegation_reward_rate.to_string(),
|
||||
gateway_delegation_reward_rate: p.gateway_delegation_reward_rate.to_string(),
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
gateway_active_set_size: p.gateway_active_set_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,11 +42,8 @@ impl TryFrom<TauriStateParams> for StateParams {
|
||||
minimum_mixnode_bond: Uint128::try_from(p.minimum_mixnode_bond.as_str())?,
|
||||
minimum_gateway_bond: Uint128::try_from(p.minimum_gateway_bond.as_str())?,
|
||||
mixnode_bond_reward_rate: Decimal::from_str(p.mixnode_bond_reward_rate.as_str())?,
|
||||
gateway_bond_reward_rate: Decimal::from_str(p.gateway_bond_reward_rate.as_str())?,
|
||||
mixnode_delegation_reward_rate: Decimal::from_str(p.mixnode_delegation_reward_rate.as_str())?,
|
||||
gateway_delegation_reward_rate: Decimal::from_str(p.gateway_delegation_reward_rate.as_str())?,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
gateway_active_set_size: p.gateway_active_set_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ use crate::coin::Coin;
|
||||
use crate::format_err;
|
||||
use crate::state::State;
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use mixnet_contract::{
|
||||
Addr, PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse,
|
||||
};
|
||||
use mixnet_contract::{Addr, PagedReverseMixDelegationsResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
@@ -57,45 +55,6 @@ pub async fn undelegate_from_mixnode(
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delegate_to_gateway(
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, String> {
|
||||
let r_state = state.read().await;
|
||||
let bond: CosmWasmCoin = match amount.try_into() {
|
||||
Ok(b) => b,
|
||||
Err(e) => return Err(format_err!(e)),
|
||||
};
|
||||
let client = r_state.client()?;
|
||||
match client.delegate_to_gateway(identity, &bond).await {
|
||||
Ok(_result) => Ok(DelegationResult {
|
||||
source_address: client.address().to_string(),
|
||||
target_address: identity.to_string(),
|
||||
amount: Some(bond.into()),
|
||||
}),
|
||||
Err(e) => Err(format_err!(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn undelegate_from_gateway(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, String> {
|
||||
let r_state = state.read().await;
|
||||
let client = r_state.client()?;
|
||||
match client.remove_gateway_delegation(identity).await {
|
||||
Ok(_result) => Ok(DelegationResult {
|
||||
source_address: client.address().to_string(),
|
||||
target_address: identity.to_string(),
|
||||
amount: None,
|
||||
}),
|
||||
Err(e) => Err(format_err!(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_reverse_mix_delegations_paged(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
@@ -104,16 +63,6 @@ pub async fn get_reverse_mix_delegations_paged(
|
||||
let client = r_state.client()?;
|
||||
client
|
||||
.get_reverse_mix_delegations_paged(Addr::unchecked(client.address().as_ref()), None, None)
|
||||
.await.map_err(|err| format_err!(err))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_reverse_gateway_delegations_paged(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<PagedReverseGatewayDelegationsResponse, String> {
|
||||
let r_state = state.read().await;
|
||||
let client = r_state.client()?;
|
||||
client
|
||||
.get_reverse_gateway_delegations_paged(Addr::unchecked(client.address().as_ref()), None, None)
|
||||
.await.map_err(|err| format_err!(err))
|
||||
.await
|
||||
.map_err(|err| format_err!(err))
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const Confirmation = ({
|
||||
{error === null ? (
|
||||
SuccessMessage
|
||||
) : (
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" data-testid="error-message">
|
||||
<AlertTitle>{error.name}</AlertTitle>
|
||||
<strong>{failureMessage}</strong> - {error.message}
|
||||
</Alert>
|
||||
|
||||
@@ -40,6 +40,7 @@ export const CopyToClipboard = ({ text }: { text: string }) => {
|
||||
size="small"
|
||||
variant={copied ? 'text' : 'outlined'}
|
||||
aria-label="save"
|
||||
data-testid="copy-button"
|
||||
onClick={() => handleCopy({ text, cb: updateCopyStatus })}
|
||||
endIcon={copied && <Check />}
|
||||
style={copied ? { background: green[500], color: 'white' } : {}}
|
||||
|
||||
@@ -6,11 +6,11 @@ import { Button } from '@material-ui/core'
|
||||
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
|
||||
return (
|
||||
<div>
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" data-testid="error-message">
|
||||
<AlertTitle>{error.name}</AlertTitle>
|
||||
{error.message}
|
||||
</Alert>
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" data-testid="stack-trace">
|
||||
<AlertTitle>Stack trace</AlertTitle>
|
||||
{error.stack}
|
||||
</Alert>
|
||||
|
||||
@@ -131,7 +131,7 @@ export const Nav = () => {
|
||||
)}
|
||||
|
||||
<ListItem button onClick={logOut}>
|
||||
<ListItemIcon className={classes.navItem}>
|
||||
<ListItemIcon data-testid="log-out" className={classes.navItem}>
|
||||
<ExitToApp />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
|
||||
@@ -28,7 +28,7 @@ export const BalanceCard = () => {
|
||||
noPadding
|
||||
Action={
|
||||
<Tooltip title="Refresh balance">
|
||||
<IconButton onClick={getBalance.fetchBalance} size="small">
|
||||
<IconButton data-testid="refresh-balance" onClick={getBalance.fetchBalance} size="small">
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@@ -43,7 +43,7 @@ export const BalanceCard = () => {
|
||||
{getBalance.error}
|
||||
</Alert>
|
||||
) : (
|
||||
<Typography variant="h6">
|
||||
<Typography variant="h6" data-testid="account-balance">
|
||||
{getBalance.balance?.printable_balance}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -71,6 +71,7 @@ export const AddressCard = () => {
|
||||
title="Address"
|
||||
subheader="Wallet payments address"
|
||||
noPadding
|
||||
data-testid="wallet-address-header"
|
||||
Action={
|
||||
<Tooltip title={!copyState ? 'Copy address' : 'Copied'}>
|
||||
<span>
|
||||
@@ -106,7 +107,7 @@ export const AddressCard = () => {
|
||||
}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography
|
||||
<Typography data-testid="wallet-address"
|
||||
style={{ fontWeight: theme.typography.fontWeightRegular }}
|
||||
>
|
||||
{truncate(clientDetails?.client_address!, 35)}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Alert, AlertTitle } from '@material-ui/lab'
|
||||
export const NoClientError = () => {
|
||||
return (
|
||||
<Alert severity="error">
|
||||
<AlertTitle>No client detected</AlertTitle>
|
||||
<AlertTitle data-testid="client-error">No client detected</AlertTitle>
|
||||
Have you signed in? Try to go back to{' '}
|
||||
<Link to="/signin">the main page</Link> and try again
|
||||
</Alert>
|
||||
|
||||
@@ -34,11 +34,13 @@ export const NodeTypeSelector = ({
|
||||
value={EnumNodeType.mixnode}
|
||||
control={<Radio />}
|
||||
label="Mixnode"
|
||||
data-testid="mix-node"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={EnumNodeType.gateway}
|
||||
control={<Radio />}
|
||||
data-testid="gate-way"
|
||||
label="Gateway"
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
@@ -13,6 +13,7 @@ export const NymCard: React.FC<{
|
||||
<CardHeader
|
||||
title={title}
|
||||
subheader={subheader}
|
||||
data-testid={title}
|
||||
titleTypographyProps={{ variant: 'h5' }}
|
||||
subheaderTypographyProps={{ variant: 'subtitle1' }}
|
||||
action={Action}
|
||||
|
||||
@@ -18,6 +18,7 @@ export const Balance = () => {
|
||||
size="small"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="refresh-button"
|
||||
onClick={fetchBalance}
|
||||
disabled={isLoading}
|
||||
disableElevation
|
||||
@@ -31,12 +32,13 @@ export const Balance = () => {
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<NymCard title="Check Balance">
|
||||
<NymCard title="Check Balance" data-testid="check-balance">
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
{error && (
|
||||
<Alert
|
||||
severity="error"
|
||||
data-testid="error-refresh"
|
||||
action={<RefreshAction />}
|
||||
style={{ padding: theme.spacing(2) }}
|
||||
>
|
||||
@@ -46,6 +48,7 @@ export const Balance = () => {
|
||||
{!error && (
|
||||
<Alert
|
||||
severity="success"
|
||||
data-testid="refresh-success"
|
||||
style={{ padding: theme.spacing(2, 3) }}
|
||||
action={<RefreshAction />}
|
||||
>
|
||||
|
||||
@@ -142,7 +142,7 @@ export const BondForm = ({
|
||||
</Grid>
|
||||
{fees && (
|
||||
<Grid item>
|
||||
<Alert severity="info">
|
||||
<Alert severity="info" data-testid="fee-amount">
|
||||
{`A fee of ${
|
||||
watchNodeType === EnumNodeType.mixnode
|
||||
? fees.mixnode.amount
|
||||
@@ -369,6 +369,7 @@ export const BondForm = ({
|
||||
color="primary"
|
||||
type="submit"
|
||||
size="large"
|
||||
data-testid="submit-button"
|
||||
disableElevation
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
|
||||
@@ -43,7 +43,7 @@ export const Bond = () => {
|
||||
<NymCard title="Bond" subheader="Bond a node or gateway" noPadding>
|
||||
{ownership?.hasOwnership && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
severity="warning"
|
||||
action={
|
||||
<Button
|
||||
disabled={status === EnumRequestStatus.loading}
|
||||
@@ -53,6 +53,7 @@ export const Bond = () => {
|
||||
getBalance.fetchBalance()
|
||||
setStatus(EnumRequestStatus.initial)
|
||||
}}
|
||||
data-testid="unBond"
|
||||
>
|
||||
Unbond
|
||||
</Button>
|
||||
@@ -93,10 +94,10 @@ export const Bond = () => {
|
||||
<RequestStatus
|
||||
status={status}
|
||||
Success={
|
||||
<Alert severity="success">Successfully bonded node</Alert>
|
||||
<Alert severity="success" data-testid="bond-success">Successfully bonded node</Alert>
|
||||
}
|
||||
Error={
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" data-testid="bond-error">
|
||||
An error occurred with the request: {message}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export const DelegateForm = ({
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Alert severity="info">
|
||||
<Alert severity="info" data-testid="fee-amount">
|
||||
{`A fee of ${
|
||||
watchNodeType === EnumNodeType.mixnode
|
||||
? fees.mixnode.amount
|
||||
@@ -153,6 +153,7 @@ export const DelegateForm = ({
|
||||
<Button
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
disabled={isSubmitting}
|
||||
data-testid="delegate-button"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
|
||||
@@ -41,6 +41,7 @@ export const Delegate = () => {
|
||||
title="Delegate"
|
||||
subheader="Delegate to mixnode or gateway"
|
||||
noPadding
|
||||
data-testid="delegateCard"
|
||||
>
|
||||
{isLoading && (
|
||||
<Box
|
||||
@@ -72,15 +73,16 @@ export const Delegate = () => {
|
||||
<RequestStatus
|
||||
status={status}
|
||||
Error={
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" data-testid="delegate-error">
|
||||
<AlertTitle>Delegation failed</AlertTitle>
|
||||
An error occurred with the request:
|
||||
<Box style={{ wordBreak: 'break-word' }}>{message}</Box>
|
||||
</Alert>
|
||||
}
|
||||
Success={
|
||||
<Alert severity="success">
|
||||
<Alert severity="success" data-testid="delegate-success">
|
||||
<AlertTitle>Delegation complete</AlertTitle>
|
||||
{message}
|
||||
<Box style={{ wordBreak: 'break-word' }}>{message}</Box>
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
@@ -95,6 +97,7 @@ export const Delegate = () => {
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
data-testid="finish-button"
|
||||
onClick={() => {
|
||||
setStatus(EnumRequestStatus.initial)
|
||||
}}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const Receive = () => {
|
||||
<NymCard title="Receive Nym">
|
||||
<Grid container direction="column" spacing={1}>
|
||||
<Grid item>
|
||||
<Alert severity="info">
|
||||
<Alert severity="info" data-testid="receive-nym">
|
||||
You can receive tokens by providing this address to the sender
|
||||
</Alert>
|
||||
</Grid>
|
||||
@@ -40,6 +40,7 @@ export const Receive = () => {
|
||||
>
|
||||
<Grid item>
|
||||
<Typography
|
||||
data-testid="client-address"
|
||||
variant={matches ? 'h5' : 'subtitle1'}
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
@@ -61,7 +62,7 @@ export const Receive = () => {
|
||||
component="div"
|
||||
>
|
||||
{clientDetails && (
|
||||
<QRCode value={clientDetails.client_address} />
|
||||
<QRCode data-testid="qr-code" value={clientDetails.client_address} />
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
@@ -46,7 +46,7 @@ export const SendConfirmation = ({
|
||||
marginBottom: theme.spacing(1),
|
||||
}}
|
||||
/>
|
||||
<Typography>Transaction complete</Typography>
|
||||
<Typography data-testid="transaction-complete">Transaction complete</Typography>
|
||||
</div>
|
||||
|
||||
<Card
|
||||
@@ -60,7 +60,7 @@ export const SendConfirmation = ({
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{ wordBreak: 'break-all' }}>
|
||||
<Typography>{data.to_address}</Typography>
|
||||
<Typography data-testid="to-address">{data.to_address}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
@@ -70,7 +70,7 @@ export const SendConfirmation = ({
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography>{data.amount.amount + ' punks'}</Typography>
|
||||
<Typography data-testid="send-amount">{data.amount.amount + ' punks'}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -37,7 +37,7 @@ export const SendError = ({ message }: { message?: string }) => {
|
||||
variant="outlined"
|
||||
style={{ width: '100%', padding: theme.spacing(2) }}
|
||||
>
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" data-testid="transaction-error">
|
||||
An error occured during the request {message}
|
||||
</Alert>
|
||||
</Card>
|
||||
|
||||
@@ -44,7 +44,7 @@ export const SendReview = () => {
|
||||
) : (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<SendReviewField title="From" subtitle={values.from} />
|
||||
<SendReviewField title="From" subtitle={values.from}/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider light />
|
||||
@@ -86,7 +86,7 @@ export const SendReviewField = ({
|
||||
<Typography style={{ color: theme.palette.grey[600] }}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography style={{ wordBreak: 'break-all' }}>{subtitle}</Typography>
|
||||
<Typography data-testid={title} style={{ wordBreak: 'break-all' }}>{subtitle}</Typography>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ export const SendWizard = () => {
|
||||
disableElevation
|
||||
style={{ marginRight: theme.spacing(1) }}
|
||||
onClick={handlePreviousStep}
|
||||
data-testid="back-button"
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
@@ -156,6 +157,7 @@ export const SendWizard = () => {
|
||||
variant={activeStep > 0 ? 'contained' : 'text'}
|
||||
color={activeStep > 0 ? 'primary' : 'default'}
|
||||
disableElevation
|
||||
data-testid="button"
|
||||
onClick={
|
||||
activeStep === 0
|
||||
? handleNextStep
|
||||
|
||||
@@ -100,7 +100,7 @@ const SignInContent = ({
|
||||
return (
|
||||
<SignInCard>
|
||||
<>
|
||||
<Typography variant="h4">Sign in</Typography>
|
||||
<Typography variant="h4" data-testid="sign-in">Sign in</Typography>
|
||||
<form noValidate onSubmit={handleSignIn}>
|
||||
<Grid container direction="column" spacing={1}>
|
||||
<Grid item>
|
||||
@@ -239,7 +239,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
|
||||
/>
|
||||
<Typography>Wallet setup complete</Typography>
|
||||
</div>
|
||||
<Alert severity="info" style={{ marginBottom: theme.spacing(2) }}>
|
||||
<Alert severity="info" style={{ marginBottom: theme.spacing(2) }} data-testid="mnemonic-warning">
|
||||
Please store your <strong>mnemonic</strong> in a safe place.
|
||||
You'll need it to access your wallet
|
||||
</Alert>
|
||||
@@ -257,7 +257,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography>{accountDetails.mnemonic}</Typography>
|
||||
<Typography data-testid="mnemonic-phrase">{accountDetails.mnemonic}</Typography>
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'flex-end' }}
|
||||
>
|
||||
@@ -273,7 +273,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography>{accountDetails.client_address}</Typography>
|
||||
<Typography data-testid="wallet-address">{accountDetails.client_address}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
@@ -282,7 +282,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
|
||||
</Grid>
|
||||
{error && (
|
||||
<Grid item style={{ marginTop: theme.spacing(1) }}>
|
||||
<Alert severity="error">{error}</Alert>
|
||||
<Alert severity="error" data-testid="error">{error}</Alert>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item>
|
||||
@@ -293,6 +293,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="create-button"
|
||||
disableElevation
|
||||
style={{ marginBottom: theme.spacing(1) }}
|
||||
disabled={isLoading}
|
||||
@@ -304,6 +305,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
|
||||
fullWidth
|
||||
variant="text"
|
||||
onClick={showSignIn}
|
||||
data-testid="sign-in-button"
|
||||
startIcon={<ArrowBack />}
|
||||
>
|
||||
Sign in
|
||||
|
||||
@@ -29,8 +29,10 @@ export const Unbond = () => {
|
||||
{ownership?.hasOwnership && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
data-testid="bond-noded"
|
||||
action={
|
||||
<Button
|
||||
data-testid="un-bond"
|
||||
disabled={isLoading}
|
||||
onClick={async () => {
|
||||
setIsLoading(true)
|
||||
@@ -48,7 +50,7 @@ export const Unbond = () => {
|
||||
</Alert>
|
||||
)}
|
||||
{!ownership.hasOwnership && (
|
||||
<Alert severity="info" style={{ margin: theme.spacing(3) }}>
|
||||
<Alert severity="info" style={{ margin: theme.spacing(3) }} data-testid="no-bond">
|
||||
You don't currently have a bonded node
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -84,7 +84,7 @@ export const UndelegateForm = ({
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Alert severity="info">
|
||||
<Alert severity="info" data-testid="fee-amount">
|
||||
{`A fee of ${
|
||||
watchNodeType === EnumNodeType.mixnode
|
||||
? fees.mixnode.amount
|
||||
@@ -140,6 +140,7 @@ export const UndelegateForm = ({
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="submit-button"
|
||||
disableElevation
|
||||
disabled={isSubmitting}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
|
||||
@@ -102,14 +102,14 @@ export const Undelegate = () => {
|
||||
<RequestStatus
|
||||
status={status}
|
||||
Error={
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" data-testid="request-error">
|
||||
An error occurred with the request: {message}
|
||||
</Alert>
|
||||
}
|
||||
Success={
|
||||
<Alert severity="success">
|
||||
{' '}
|
||||
<AlertTitle>Undelegation complete</AlertTitle>
|
||||
<AlertTitle data-testid="undelegate-success">Undelegation complete</AlertTitle>
|
||||
{message}
|
||||
</Alert>
|
||||
}
|
||||
@@ -125,6 +125,7 @@ export const Undelegate = () => {
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
data-testid="finish-button"
|
||||
onClick={() => {
|
||||
setStatus(EnumRequestStatus.initial)
|
||||
initialize()
|
||||
|
||||
@@ -3,9 +3,6 @@ export interface TauriStateParams {
|
||||
minimum_mixnode_bond: string;
|
||||
minimum_gateway_bond: string;
|
||||
mixnode_bond_reward_rate: string;
|
||||
gateway_bond_reward_rate: string;
|
||||
mixnode_delegation_reward_rate: string;
|
||||
gateway_delegation_reward_rate: string;
|
||||
mixnode_active_set_size: number;
|
||||
gateway_active_set_size: number;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
reports
|
||||
allure-results
|
||||
node_modules
|
||||
.vscode
|
||||
.idea
|
||||
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
# Nym Wallet Webdriverio testsuite
|
||||
|
||||
A webdriverio test suite implementation using tauri driver
|
||||
with a page object model design. This is to provide quick iterative feedback
|
||||
on the UI of the nym wallet. Currently, tauri-driver is available to run on Windows and Linux machines.
|
||||
|
||||
## Installation prerequisites
|
||||
* `Yarn`
|
||||
* `NodeJS >= v16.8.0`
|
||||
* `Rust & cargo >= v1.51`
|
||||
* `tauri-driver`
|
||||
* `That you have an existing mnemonic and you can login to the app`
|
||||
* `Have the details listed below to provide the user-data.json file`
|
||||
|
||||
|
||||
## Key Information
|
||||
* Please read the instructions on the `nym/tauri-wallet/README.md` in the root of the project on how to build the application
|
||||
* Please ensure you have the relevant Webdriver kits installed on your machine -
|
||||
```
|
||||
linux:
|
||||
sudo apt-get install -y webkit2gtk-driver
|
||||
```
|
||||
```
|
||||
windows:
|
||||
download msedgedriver.exe from https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
|
||||
```
|
||||
please visit [Tauri Studio](https://tauri.studio/en/docs/usage/guides/webdriver/introduction), this will specify the additional drivers you need
|
||||
* The path to run the application is set in the `wdio.conf.js` which lives in the root directory
|
||||
* Before running the suite you need to build the application and check that the application has
|
||||
built successfully, if so, you will have an executable sitting in the target directory in `tauri-wallet/target/*/nym_wallet` (refer to point 1)
|
||||
* The suite will not be able to detect elements on screen if you select a release build, however you can run tests against a release target
|
||||
|
||||
|
||||
## Installation & usage
|
||||
* `test excution happens inside /webdriver directory`
|
||||
* `test data needs to be provided inside the user-data.json`
|
||||
* `check the wdio.conf.cjs to see the test execution along with the path location of the binary`
|
||||
```
|
||||
example:
|
||||
//mnemonic is a base64 enconded value, which is your 24 character passphrase, these values are for illustration purposes
|
||||
{
|
||||
"mnemonic" : "dGhpcyBpcyBhIHBhc3NwaHJhc2UK",
|
||||
"punk_address" : "punk1f3dzkhmunma5ze5q952daxca6371989189",
|
||||
"receiver_address" : "punk1p0ce82jxxglpmutvhq4mdwgcwf4avm5n1821982",
|
||||
"amount_to_send" : "1",
|
||||
"identity_key_to_delegate_mix_node": "value",
|
||||
"identity_key_to_delegate_gateway" : "value",
|
||||
"delegate_amount" : "1"
|
||||
}
|
||||
```
|
||||
* `yarn test:runall` - the first test run will take some time to spin up be patient
|
||||
* You can run tests individually by passing through the script situated in the package.json for example `yarn test:newuser`
|
||||
|
||||
Tests are categorised and run by their pages, they follow a sequential flow, if one test case fails before the next execution it may derail the next test.
|
||||
|
||||
//todo improve in near future
|
||||
|
||||
## Test reporting
|
||||
Currently the tests use allure reporting, the configuration can be altered in the `wdio.conf.cjs`. At present it takes snapshots of any failing tests, the test output run can be seen in the allure-results directory
|
||||
Tests ouput:
|
||||
* <guid-testuite.xml>
|
||||
* <guid-attachment.png>
|
||||
|
||||
If any tests fail in their test run it will produce the stack trace error along with the test in question
|
||||
|
||||
## TODO
|
||||
|
||||
*Disclaimer*: Still WIP
|
||||
|
||||
|
||||
Implement error handling/ beforeTest() - validating json file exists with data for test execution
|
||||
|
||||
|
||||
Currently this is dev'd against a Linux based OS, not tested against windows yet.
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "14"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
//receivePage
|
||||
"recievePageInformation" : "You can receive tokens by providing this address to the sender",
|
||||
"receivePageHeaderText": "Receive Nym",
|
||||
|
||||
//sendPage
|
||||
"sendPunk" : "Send PUNK",
|
||||
|
||||
//homePage
|
||||
"homePageErrorMnemonic": "Error parsing bip39 mnemonic",
|
||||
"homePageSignIn" : "Sign in",
|
||||
"createOne" : "Create one",
|
||||
"walletSuccess" : "Please store your mnemonic in a safe place. You'll need it to access your wallet",
|
||||
|
||||
//bondPage // unbondPage
|
||||
"bondAlreadyNoded" : "Looks like you already have a mixnode bonded.",
|
||||
"bondNodeHeaderText": "Bond a node or gateway",
|
||||
"unbondNodeHeaderText" : "Unbond a mixnode or gateway",
|
||||
"unbondMixNodeText": "Looks like you already have a mixnode bonded.",
|
||||
"unbondMixNode": "UNBOND",
|
||||
|
||||
//delegatePage // undelegatePage
|
||||
"delegateHeaderText" : "Delegate\nDelegate to mixnode or gateway",
|
||||
"nodeIdentityValidationText" : "identity is a required field",
|
||||
"amountValidationText": "amount is a required field",
|
||||
"undelegateHeaderText" : "Undelegate from a mixnode or gateway",
|
||||
"delegationComplete" : "Delegation complete"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"mnemonic": "value",
|
||||
"punk_address": "",
|
||||
"receiver_address": "",
|
||||
"amount_to_send": "",
|
||||
"identity_key_to_delegate_mix_node": "",
|
||||
"identity_key_to_delegate_gateway" : "",
|
||||
"delegate_amount": ""
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
class Helpers {
|
||||
|
||||
//helper to decode mnemonic so plain 24 character passphrase isn't in sight albeit it is presented when ruunning the scripts
|
||||
//maybe a show passphrase toggle button?
|
||||
decodeBase = async (input) => {
|
||||
var m = Buffer.from(input, 'base64').toString()
|
||||
return m
|
||||
}
|
||||
|
||||
navigateAndClick = async (element) => {
|
||||
await element.click()
|
||||
}
|
||||
|
||||
scrollIntoView = async (element) => {
|
||||
await element.scrollIntoView()
|
||||
}
|
||||
|
||||
|
||||
currentBalance = async (value) => {
|
||||
return parseFloat(value.split(/\s+/)[0].toString()).toFixed(5)
|
||||
}
|
||||
|
||||
//todo need to improve calculation - WIP
|
||||
calculateFees = async (beforeBalance, transactionFee, amount, isSend) => {
|
||||
let fee
|
||||
|
||||
if (isSend) {
|
||||
//send transaction
|
||||
fee = transactionFee.split(/\s+/)[0]
|
||||
} else {
|
||||
//delegate transaction
|
||||
fee = transactionFee.split(/\s+/)[3]
|
||||
}
|
||||
|
||||
const currentBalance = beforeBalance.split(/\s+/)[0]
|
||||
|
||||
const castCurrentBalance = parseFloat(currentBalance).toFixed(5)
|
||||
const transCost = +parseFloat(amount) + +parseFloat(fee).toFixed(5)
|
||||
|
||||
let sum = parseFloat(castCurrentBalance) - parseFloat(transCost)
|
||||
return sum.toFixed(5)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Helpers()
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "tauri_nym_wallet",
|
||||
"version": "1.0.0",
|
||||
"private": false,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test:runall": "wdio run wdio.conf.cjs",
|
||||
"test:sendreceive": "wdio run wdio.conf.cjs --suite sendreceive",
|
||||
"test:home": "wdio run wdio.conf.cjs --suite home",
|
||||
"test:bond": "wdio run wdio.conf.cjs --suite bond",
|
||||
"test:delegate": "wdio run wdio.conf.cjs --suite delegate",
|
||||
"test:newuser": "wdio run wdio.conf.cjs --suite newuser"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^16.11.0",
|
||||
"@wdio/allure-reporter": "^7.16.1",
|
||||
"@wdio/cli": "^7.9.1",
|
||||
"@zxing/browser": "^0.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wdio/local-runner": "^7.14.1",
|
||||
"@wdio/mocha-framework": "^7.14.1",
|
||||
"@wdio/spec-reporter": "^7.14.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
class WalletBond {
|
||||
|
||||
get header() { return $("#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span.MuiTypography-root.MuiCardHeader-subheader.MuiTypography-subtitle1.MuiTypography-colorTextSecondary.MuiTypography-displayBlock") }
|
||||
get identityKey() { return $("#identityKey") }
|
||||
get sphinxKey() { return $("#sphinxKey") }
|
||||
get amountToBond() { return $("#amount") }
|
||||
get hostInput() { return $("#host") }
|
||||
get versionInput() { return $("version") }
|
||||
get selectAdvancedOptions() { return $("[type='checkbox']") }
|
||||
get mixPort() { return $("#mixPort") }
|
||||
get verlocPort() { return $("#verlocPort") }
|
||||
get httpApiPort() { return $("#httpApiPort") }
|
||||
get bondButton() { return $("[data-testid='bond-button']") }
|
||||
get unBondButton() { return $("[data-testid='un-bond']") }
|
||||
get unBond() { return $("[data-testid='bond-noded']") }
|
||||
get unBondWarning() {return $("div.MuiAlert-message")}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new WalletBond()
|
||||
@@ -0,0 +1,12 @@
|
||||
class WalletCreate {
|
||||
|
||||
get createAccount() { return $("[href='#']") }
|
||||
get create() { return $("[data-testid='create-button']") }
|
||||
get accountCreatedSuccessfully() { return $("[data-testid='mnemonic-warning']") }
|
||||
get walletMnemonicValue() { return $("[data-testid='mnemonic-phrase']") }
|
||||
get punkAddress() { return $("[data-testid='wallet-address']") }
|
||||
get backToSignIn() { return $("[data-testid='sign-in-button']") }
|
||||
get signInButton() { return $("[type='submit']") }
|
||||
|
||||
}
|
||||
module.exports = new WalletCreate()
|
||||
@@ -0,0 +1,26 @@
|
||||
class WalletDelegate {
|
||||
|
||||
get header() { return $("[data-testid='Delegate']") }
|
||||
get nodeIdentity() { return $("#identity") }
|
||||
get amountToDelegate() { return $("#amount") }
|
||||
get identityValidation() { return $("#identity-helper-text") }
|
||||
get amountToDelegateValidation() { return $("#amount-helper-text") }
|
||||
get delegateStakeButton() { return $("[data-testid='delegate-button']") }
|
||||
get mixNodeRadioButton() { return $("[data-testid='mix-node']") }
|
||||
get gateWayRadioButton() { return $("[data-testid='gate-way']") }
|
||||
get successfullyDelegate() { return $("[data-testid='delegate-success']") }
|
||||
get finishButton() { return $("[data-testid='finish-button']") }
|
||||
get transactionFeeAmount() { return $("[data-testid='fee-amount']") }
|
||||
get accountBalance() { return $("[data-testid='account-balance']") }
|
||||
|
||||
//Undelegate
|
||||
get unDelegateHeader() { return $("[data-testid='Undelegate']") }
|
||||
get unNodeIdentity() { return $("[name='identity']") }
|
||||
get unDelegateFeeText() { return $("[data-testid='fee-amount']") }
|
||||
get unDelegateGatewayRadioButton() { return $("[data-testid='gate-way']") }
|
||||
get unMixNodeRadioButton() { return $("[data-testid='mix-node']") }
|
||||
get unDelegateButton() { return $("[data-testid='submit-button']") }
|
||||
|
||||
}
|
||||
|
||||
module.exports = new WalletDelegate()
|
||||
@@ -0,0 +1,18 @@
|
||||
class WalletHome {
|
||||
|
||||
get balanceCheck() { return $("#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span") }
|
||||
get punkBalance() { return $("") }
|
||||
get punkAddress() { return $("[data-testid='wallet-address']") }
|
||||
get accountBalance() { return $("[data-testid='account-balance']") }
|
||||
get balanceButton() { return $("[href='/balance']") }
|
||||
get sendButton() { return $("[href='/send']"); }
|
||||
get receiveButton() { return $("[href='/receive']") }
|
||||
get bondButton() { return $("[href='/bond']") }
|
||||
get unBondButton() { return $("[href='/unbond']") }
|
||||
get delegateButton() { return $("[href='/delegate']") }
|
||||
get unDelegateButton() { return $("[href='/undelegate']") }
|
||||
get logOutButton() { return $("[data-testid='log-out']") }
|
||||
|
||||
}
|
||||
|
||||
module.exports = new WalletHome()
|
||||
@@ -0,0 +1,18 @@
|
||||
class WalletLogin {
|
||||
|
||||
get signInLabel() {return $("[data-testid='sign-in']")}
|
||||
get mnemonic() { return $("#mnemonic")}
|
||||
get signInButton() {return $("[type='submit']") }
|
||||
get errorValidation() {return $("[class='MuiAlert-message']") }
|
||||
get accountBalance() {return $("[data-test-id='account-balance']")}
|
||||
get accountBalanceText() {return $("[class='MuiAlert-message']")}
|
||||
get walletAddress() {return $("[data-testid='wallet-address']")}
|
||||
|
||||
//login to the application
|
||||
enterMnemonic = async(mnemonic) => {
|
||||
await this.mnemonic.addValue(mnemonic);
|
||||
await this.signInButton.click();
|
||||
await this.accountBalance.isExisting();
|
||||
}
|
||||
}
|
||||
module.exports = new WalletLogin()
|
||||
@@ -0,0 +1,24 @@
|
||||
class WalletReceive {
|
||||
|
||||
get receiveNymHeader() { return $("#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span") }
|
||||
get receiveNymText() { return $("[data-testid='receive-nym']") }
|
||||
get walletAddress() { return $("[data-testid='client-address']"); }
|
||||
get copyButton() { return $("[data-testid='copy-button']") }
|
||||
get qrCode() { return $("[data-testid='qr-code']") }
|
||||
|
||||
WaitForButtonChangeOnCopy = async () => {
|
||||
await this.copyButton.click()
|
||||
|
||||
await this.copyButton.waitForDisplayed({ timeout: 1500 })
|
||||
|
||||
await this.copyButton.waitUntil(async function () {
|
||||
return (await this.getText()) === 'COPIED'
|
||||
}, {
|
||||
timeout: 1500,
|
||||
timeoutMsg: 'expected text to be different after 1.5s'
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new WalletReceive()
|
||||
@@ -0,0 +1,22 @@
|
||||
class WalletSend {
|
||||
|
||||
get fromAddress() { return $("#from") }
|
||||
get toAddress() { return $("#to") }
|
||||
get amount() { return $("#amount") }
|
||||
get nextButton() { return $("[data-testid='button") }
|
||||
get sendHeader() { return $("[data-testid='Send PUNK']") }
|
||||
get accountBalance() { return $("[data-testid='account-balance']") }
|
||||
get amountReviewAndSend() { return $("[data-testid='Amount']") }
|
||||
get toAddressReviewAndSend() { return $("[data-testid='To']") }
|
||||
get fromAddressReviewAndSend() { return $("[data-testid='From']") }
|
||||
get transferFeeAmount() { return $("[data-testid='Transfer fee']") }
|
||||
get reviewAndSendBackButton() { return $("[data-testid='back-button']") }
|
||||
get sendButton() { return $("[data-testid='button']") }
|
||||
get transactionComplete() { return $("[data-testid='transaction-complete']") }
|
||||
get transactionCompleteRecipient() { return $("[data-testid='to-address']") }
|
||||
get transactionCompleteAmount() { return $("[data-testid='send-amount']") }
|
||||
get finishButton() { return $("[data-testid='button']") }
|
||||
|
||||
}
|
||||
|
||||
module.exports = new WalletSend()
|
||||
@@ -0,0 +1,12 @@
|
||||
class WallentUndelegate {
|
||||
|
||||
get transactionFee() { return $("[data-testid='fee-amount']") }
|
||||
get mixNodeRadioButton() { return $("[value='mixnode']") }
|
||||
get gatewayRadionButton() { return $("[value='gateway']") }
|
||||
get nodeIdentity() { return $("#mui-55011") }
|
||||
get identityHelper() { return $("#identity-helper-text") }
|
||||
get delegateButton() { return $("[data-testid='submit-button']") }
|
||||
|
||||
}
|
||||
|
||||
module.exports = new WallentUndelegate()
|
||||
@@ -0,0 +1,55 @@
|
||||
const userData = require('../../../common/data/user-data.json')
|
||||
const helper = require('../../../common/helpers/helper')
|
||||
const walletLogin = require('../../pages/wallet.login')
|
||||
const textConstants = require('../../../common/constants/text-constants')
|
||||
const walletHomepage = require('../../pages/wallet.homepage')
|
||||
const bondPage = require('../../pages/wallet.bond')
|
||||
|
||||
describe("bonding and unbonding nodes", () => {
|
||||
it("should have a node already bonded and validate no input fields are enabled", async () => {
|
||||
|
||||
const mnemonic = await helper.decodeBase(userData.mnemonic);
|
||||
|
||||
await walletLogin.enterMnemonic(mnemonic);
|
||||
|
||||
await helper.navigateAndClick(walletHomepage.bondButton);
|
||||
|
||||
await helper.scrollIntoView(bondPage.selectAdvancedOptions);
|
||||
|
||||
await bondPage.selectAdvancedOptions.click();
|
||||
|
||||
//as bond node is mixed expect all the fields to be disabled
|
||||
const getText = await bondPage.header.getText()
|
||||
const getIdentity = await bondPage.identityKey.isEnabled()
|
||||
const getSphinxKey = await bondPage.sphinxKey.isEnabled()
|
||||
const amountToBond = await bondPage.amountToBond.isEnabled()
|
||||
const hostInput = await bondPage.hostInput.isEnabled()
|
||||
const verlocPort = await bondPage.verlocPort.isEnabled()
|
||||
const httpApiPort = await bondPage.httpApiPort.isEnabled()
|
||||
const mixPort = await bondPage.mixPort.isEnabled()
|
||||
|
||||
//assert all field are not functional
|
||||
expect(getText).toEqual(textConstants.bondNodeHeaderText)
|
||||
expect(getIdentity).toEqual(false)
|
||||
expect(getSphinxKey).toEqual(false)
|
||||
expect(amountToBond).toEqual(false)
|
||||
expect(hostInput).toEqual(false)
|
||||
expect(verlocPort).toEqual(false)
|
||||
expect(httpApiPort).toEqual(false)
|
||||
expect(mixPort).toEqual(false)
|
||||
})
|
||||
|
||||
it("unbond mix monde screen should be present with the option to unbond", async () => {
|
||||
//we do not want to unbond our node, check that elements are selectable
|
||||
await helper.scrollIntoView(walletHomepage.unBondButton)
|
||||
await helper.navigateAndClick(walletHomepage.unBondButton)
|
||||
|
||||
const getText = await bondPage.header.getText()
|
||||
const unbondText = await bondPage.unBondWarning.getText()
|
||||
|
||||
await bondPage.unBondButton.isClickable()
|
||||
//assert all field are not functional
|
||||
expect(getText).toEqual(textConstants.unbondNodeHeaderText)
|
||||
expect(unbondText).toEqual(textConstants.unbondMixNodeText)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,90 @@
|
||||
const userData = require('../../../common/data/user-data.json')
|
||||
const helper = require('../../../common/helpers/helper')
|
||||
const walletLogin = require('../../pages/wallet.login')
|
||||
const textConstants = require('../../../common/constants/text-constants')
|
||||
const walletHomepage = require('../../pages/wallet.homepage')
|
||||
const delegatePage = require('../../pages/wallet.delegate')
|
||||
|
||||
describe("delegate to a mix node or gateway", () => {
|
||||
it("ensure that fields are enabled for existing user", async () => {
|
||||
|
||||
const mnemonic = await helper.decodeBase(userData.mnemonic)
|
||||
|
||||
await walletLogin.enterMnemonic(mnemonic)
|
||||
|
||||
await helper.navigateAndClick(walletHomepage.delegateButton)
|
||||
|
||||
const getText = await delegatePage.header.getText()
|
||||
|
||||
expect(getText).toEqual(textConstants.delegateHeaderText)
|
||||
})
|
||||
|
||||
it("submitting the form without input prompts validation errors", async () => {
|
||||
|
||||
await delegatePage.delegateStakeButton.click()
|
||||
|
||||
const getIdentityValidation = await delegatePage.identityValidation.getText()
|
||||
const getAmountValidation = await delegatePage.amountToDelegateValidation.getText()
|
||||
|
||||
expect(getIdentityValidation).toEqual(textConstants.nodeIdentityValidationText)
|
||||
expect(getAmountValidation).toEqual(textConstants.amountValidationText)
|
||||
|
||||
})
|
||||
|
||||
it("input delegate amount to a mix node then broadcast the transaction then check account balances", async () => {
|
||||
|
||||
const balanceText = await delegatePage.accountBalance.getText()
|
||||
|
||||
const getTransfeeAmount = await delegatePage.transactionFeeAmount.getText()
|
||||
|
||||
await delegatePage.nodeIdentity.setValue(userData.identity_key_to_delegate_mix_node)
|
||||
|
||||
await delegatePage.amountToDelegate.setValue(userData.delegate_amount)
|
||||
|
||||
//transfer fee + amount delegation
|
||||
const sumCost = await helper.calculateFees(balanceText, getTransfeeAmount, userData.delegate_amount, false)
|
||||
|
||||
await delegatePage.delegateStakeButton.click()
|
||||
|
||||
await delegatePage.successfullyDelegate.waitForClickable({ timeout: 10000 })
|
||||
|
||||
const getConfirmationText = await delegatePage.successfullyDelegate.getText()
|
||||
expect(getConfirmationText).toContain(textConstants.delegationComplete)
|
||||
|
||||
const availablePunk = await delegatePage.accountBalance.getText()
|
||||
//expect new account balance - the fee calculation above
|
||||
|
||||
await delegatePage.finishButton.click()
|
||||
|
||||
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost)
|
||||
|
||||
})
|
||||
|
||||
it("input amount to stake to a gateway then broadcast the transaction then check account balances", async () => {
|
||||
const balanceText = await delegatePage.accountBalance.getText()
|
||||
|
||||
const getTransfeeAmount = await delegatePage.transactionFeeAmount.getText()
|
||||
|
||||
await delegatePage.gateWayRadioButton.click()
|
||||
|
||||
await delegatePage.nodeIdentity.setValue(userData.identity_key_to_delegate_gateway)
|
||||
|
||||
await delegatePage.amountToDelegate.setValue(userData.delegate_amount)
|
||||
|
||||
//transfer fee + amount delegation
|
||||
|
||||
const sumCost = await helper.calculateFees(balanceText, getTransfeeAmount, userData.delegate_amount, false)
|
||||
|
||||
await delegatePage.delegateStakeButton.click()
|
||||
|
||||
await delegatePage.successfullyDelegate.waitForClickable({ timeout: 10000 })
|
||||
|
||||
const getConfirmationText = await delegatePage.successfullyDelegate.getText()
|
||||
expect(getConfirmationText).toContain(textConstants.delegationComplete)
|
||||
|
||||
const availablePunk = await delegatePage.accountBalance.getText()
|
||||
//expect new account balance - the fee calculation above
|
||||
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost)
|
||||
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
const userData = require('../../../common/data/user-data.json')
|
||||
const helper = require('../../../common/helpers/helper')
|
||||
const walletLogin = require('../../pages/wallet.login')
|
||||
const homepPage = require('../../pages/wallet.homepage')
|
||||
const textConstants = require('../../../common/constants/text-constants')
|
||||
|
||||
describe("wallet splash screen", () => {
|
||||
it("should have the sign in header present", async () => {
|
||||
|
||||
const signInText = await walletLogin.signInLabel.getText()
|
||||
expect(signInText).toEqual(textConstants.homePageSignIn)
|
||||
|
||||
})
|
||||
|
||||
it("submitting the sign in button with no input throws a validation error", async () => {
|
||||
|
||||
await walletLogin.signInButton.click()
|
||||
|
||||
const errorResponseText = await walletLogin.errorValidation.getText()
|
||||
expect(errorResponseText).toEqual(textConstants.homePageErrorMnemonic)
|
||||
})
|
||||
|
||||
//currently the punk_address is not fully displayed on the wallet UI
|
||||
//trim the punk address
|
||||
it("successfully input mnemonic and log in", async () => {
|
||||
const mnemonic = await helper.decodeBase(userData.mnemonic)
|
||||
|
||||
await walletLogin.enterMnemonic(mnemonic)
|
||||
|
||||
await walletLogin.walletAddress.waitForEnabled({ timeout: 5000 })
|
||||
|
||||
const getWalletAddress = await walletLogin.walletAddress.getText()
|
||||
//currently 35 characters are displayed along with three ...
|
||||
//current hack we can assume this is the correct wallet
|
||||
const walletTruncated = userData.punk_address.substring(0,35)
|
||||
|
||||
expect(walletTruncated + '...').toContain(getWalletAddress)
|
||||
|
||||
})
|
||||
|
||||
it("successfully log out the application", async () => {
|
||||
|
||||
await helper.scrollIntoView(homepPage.logOutButton)
|
||||
|
||||
await homepPage.logOutButton.click()
|
||||
|
||||
await walletLogin.signInLabel.waitForEnabled({ timeout: 1500 })
|
||||
expect(await walletLogin.signInLabel.isDisplayed()).toEqual(true)
|
||||
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
const userData = require('../../../common/data/user-data.json')
|
||||
const textConstants = require('../../../common/constants/text-constants')
|
||||
const helper = require('../../../common/helpers/helper')
|
||||
const walletLogin = require('../../pages/wallet.login')
|
||||
const receive = require('../../pages/wallet.receive')
|
||||
const walletHomepage = require('../../pages/wallet.homepage')
|
||||
|
||||
describe("provide the relevant information about a user nym wallet address", () => {
|
||||
it("should have the receivers address and a qr code present", async () => {
|
||||
|
||||
const mnemonic = await helper.decodeBase(userData.mnemonic)
|
||||
|
||||
await walletLogin.enterMnemonic(mnemonic)
|
||||
|
||||
await helper.navigateAndClick(walletHomepage.receiveButton)
|
||||
|
||||
await receive.receiveNymHeader.waitForDisplayed({ timeout: 1500 })
|
||||
|
||||
await receive.WaitForButtonChangeOnCopy()
|
||||
|
||||
const textHeader = await receive.receiveNymHeader.getText();
|
||||
const getInformationText = await receive.receiveNymText.getText()
|
||||
const getPunkAddress = await receive.walletAddress.getText()
|
||||
|
||||
expect(getPunkAddress).toEqual(userData.punk_address)
|
||||
expect(getInformationText).toEqual(textConstants.recievePageInformation)
|
||||
expect(textConstants.receivePageHeaderText).toEqual(textHeader)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
const userData = require('../../../common/data/user-data.json');
|
||||
const helper = require('../../../common/helpers/helper')
|
||||
const textConstants = require('../../../common/constants/text-constants')
|
||||
const walletLogin = require('../../pages/wallet.login')
|
||||
const sendWallet = require('../../pages/wallet.send')
|
||||
const walletHomepage = require('../../pages/wallet.homepage')
|
||||
|
||||
describe("send punk to another a wallet", () => {
|
||||
it("expect send screen to display the data", async () => {
|
||||
|
||||
const mnemonic = await helper.decodeBase(userData.mnemonic)
|
||||
|
||||
await walletLogin.enterMnemonic(mnemonic)
|
||||
|
||||
await helper.navigateAndClick(walletHomepage.sendButton)
|
||||
|
||||
const textHeader = await sendWallet.sendHeader.getText()
|
||||
|
||||
expect(textHeader).toContain(textConstants.sendPunk)
|
||||
|
||||
})
|
||||
|
||||
it("send funds correctly to another punk address", async () => {
|
||||
|
||||
//already logged in due to the previous test
|
||||
const getCurrentBalance = await walletHomepage.accountBalance.getText()
|
||||
|
||||
await sendWallet.toAddress.addValue(userData.receiver_address)
|
||||
|
||||
await sendWallet.amount.addValue(userData.amount_to_send)
|
||||
|
||||
await sendWallet.nextButton.waitForEnabled({ timeout: 3000 })
|
||||
|
||||
await sendWallet.nextButton.click()
|
||||
|
||||
const transFee = await sendWallet.transferFeeAmount.getText()
|
||||
|
||||
await sendWallet.sendButton.click()
|
||||
|
||||
await sendWallet.finishButton.waitForClickable({ timeout: 10000 })
|
||||
|
||||
let sumCost = await helper.calculateFees(getCurrentBalance, transFee, userData.amount_to_send, true)
|
||||
|
||||
await walletHomepage.accountBalance.isDisplayed()
|
||||
|
||||
const availablePunk = await walletHomepage.accountBalance.getText()
|
||||
|
||||
await sendWallet.finishButton.click()
|
||||
|
||||
//expect new account balance - the fee calculation above
|
||||
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost)
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
const userData = require('../../../common/data/user-data.json')
|
||||
const helper = require('../../../common/helpers/helper')
|
||||
const walletLogin = require('../../pages/wallet.login')
|
||||
const walletHomepage = require('../../pages/wallet.homepage')
|
||||
const unDelegatePage = require('../../pages/wallet.delegate')
|
||||
|
||||
describe("un-delegate a mix node or gateway", () => {
|
||||
it("ensure that fields are enabled for existing user", async () => {
|
||||
|
||||
//we are ensuring that the fields are selectable for undelegation
|
||||
//not proceeding to undelegate a node or gateway
|
||||
|
||||
const mnemonic = await helper.decodeBase(userData.mnemonic)
|
||||
|
||||
await walletLogin.enterMnemonic(mnemonic)
|
||||
|
||||
await helper.scrollIntoView(walletHomepage.unDelegateButton)
|
||||
|
||||
await helper.navigateAndClick(walletHomepage.unDelegateButton)
|
||||
|
||||
await unDelegatePage.unDelegateButton.waitForClickable({ timeout: 1500})
|
||||
|
||||
await unDelegatePage.unDelegateButton.isEnabled()
|
||||
|
||||
await unDelegatePage.unDelegateGatewayRadioButton.click()
|
||||
|
||||
await unDelegatePage.unDelegateGatewayRadioButton.isSelected()
|
||||
|
||||
const mixNodeRadioButton = await unDelegatePage.unMixNodeRadioButton.isSelected()
|
||||
expect(mixNodeRadioButton).toEqual(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
const walletLogin = require('../../pages/wallet.login')
|
||||
const walletSignUp = require('../../pages/wallet.create')
|
||||
const textConstants = require('../../../common/constants/text-constants')
|
||||
|
||||
describe("non existing wallet holder", () => {
|
||||
//wallet mnemonic gets pushed here
|
||||
const DATA = []
|
||||
it("create a new account and wallet", async () => {
|
||||
|
||||
const signInText = await walletLogin.signInLabel.getText();
|
||||
expect(signInText).toEqual(textConstants.homePageSignIn);
|
||||
|
||||
await walletSignUp.createAccount.click();
|
||||
|
||||
//wallet generation takes some time - apply wait
|
||||
await walletSignUp.create.click()
|
||||
|
||||
await walletSignUp.accountCreatedSuccessfully.waitForEnabled({ timeout: 10000 })
|
||||
|
||||
const getWalletText = await walletSignUp.punkAddress.getText()
|
||||
expect(getWalletText.length).toEqual(43)
|
||||
|
||||
const accountCreated = await walletSignUp.accountCreatedSuccessfully.getText()
|
||||
expect(accountCreated).toEqual(textConstants.walletSuccess)
|
||||
|
||||
const getMnemonic = await walletSignUp.walletMnemonicValue.getText()
|
||||
DATA.push(getMnemonic)
|
||||
})
|
||||
|
||||
it("navigate back to sign in screen and validate mnemonic works", async () => {
|
||||
|
||||
await walletSignUp.backToSignIn.click()
|
||||
|
||||
await walletLogin.enterMnemonic(DATA[0])
|
||||
|
||||
await walletLogin.walletAddress.isDisplayed()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,80 @@
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const { spawn, spawnSync } = require("child_process");
|
||||
//insert path to binary
|
||||
const nym_path = "../target/release/nym_wallet";
|
||||
|
||||
exports.config = {
|
||||
//run sequentially, as using one default user may cause issues for parallel test runs for now
|
||||
specs: [
|
||||
"./tests/specs/existinguser/test.wallet.home.js",
|
||||
"./tests/specs/existinguser/test.wallet.send.js",
|
||||
"./tests/specs/existinguser/test.wallet.receive.js",
|
||||
"./tests/specs/existinguser/test.wallet.bond.js",
|
||||
"./tests/specs/existinguser/test.wallet.delegate.js",
|
||||
"./tests/specs/newuser/test.wallet.create.js"
|
||||
],
|
||||
|
||||
//run tests by providing --suite {{login}}
|
||||
suites: {
|
||||
home: ["./tests/specs/existinguser/test.wallet.home.js"],
|
||||
sendreceive: ["./tests/specs/existinguser/test.wallet.send.js",
|
||||
"./tests/specs/existinguser/test.wallet.receive.js"],
|
||||
bond: ["./tests/specs/existinguser/test.wallet.bond.js"],
|
||||
delegate: ["./tests/specs/existinguser/test.wallet.delegate.js",
|
||||
"./tests/specs/existinguser/test.wallet.undelegate.js"],
|
||||
newuser: ["./tests/specs/newuser/test.wallet.create.js"]
|
||||
},
|
||||
maxInstances: 1,
|
||||
capabilities: [
|
||||
{
|
||||
maxInstances: 1,
|
||||
"tauri:options": {
|
||||
application: nym_path,
|
||||
},
|
||||
},
|
||||
],
|
||||
// ===================
|
||||
// Test Configurations
|
||||
// ===================
|
||||
// Define all options that are relevant for the WebdriverIO instance here
|
||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||
bail: 0,
|
||||
framework: 'mocha',
|
||||
reporters: ['spec'],
|
||||
mochaOpts: {
|
||||
ui: 'bdd',
|
||||
timeout: 60000
|
||||
},
|
||||
logLevel: 'silent',
|
||||
|
||||
// ===================
|
||||
// Test Reporters
|
||||
// ===================
|
||||
reporters: [['allure', {
|
||||
outputDir: 'allure-results',
|
||||
disableWebdriverStepsReporting: true,
|
||||
disableWebdriverScreenshotsReporting: true,
|
||||
}]],
|
||||
|
||||
// this is documentented in the readme - you will need to build the project first
|
||||
// ensure the rust project is built since we expect this binary to exist for the webdriver sessions
|
||||
//onPrepare: () => spawnSync("cargo", ["build", "--release"]),
|
||||
|
||||
// ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests
|
||||
beforeSession: () =>
|
||||
(tauriDriver = spawn(
|
||||
path.resolve(os.homedir(), ".cargo", "bin", "tauri-driver"),
|
||||
[],
|
||||
{ stdio: [null, process.stdout, process.stderr] }
|
||||
)),
|
||||
|
||||
afterTest: function (test, context, { error, result, duration, passed, retries }) {
|
||||
if (error) {
|
||||
browser.takeScreenshot()
|
||||
}
|
||||
},
|
||||
|
||||
// clean up the `tauri-driver` process we spawned at the start of the session
|
||||
afterSession: () => tauriDriver.kill()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ authors = [
|
||||
"Drazen Urch <durch@users.noreply.github.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
ALTER TABLE rewarding_report RENAME TO _rewarding_report_old;
|
||||
|
||||
CREATE TABLE rewarding_report
|
||||
(
|
||||
epoch_rewarding_id INTEGER NOT NULL,
|
||||
|
||||
eligible_mixnodes INTEGER NOT NULL,
|
||||
|
||||
possibly_unrewarded_mixnodes INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (epoch_rewarding_id) REFERENCES epoch_rewarding (id)
|
||||
);
|
||||
|
||||
INSERT INTO rewarding_report (epoch_rewarding_id, eligible_mixnodes, possibly_unrewarded_mixnodes)
|
||||
SELECT epoch_rewarding_id, eligible_mixnodes, possibly_unrewarded_mixnodes
|
||||
FROM _rewarding_report_old;
|
||||
|
||||
DROP TABLE _rewarding_report_old;
|
||||
DROP TABLE failed_gateway_reward_chunk;
|
||||
DROP TABLE possibly_unrewarded_gateway;
|
||||
Vendored
+1
-66
@@ -33,10 +33,7 @@ struct ValidatorCacheInner {
|
||||
gateways: RwLock<Cache<Vec<GatewayBond>>>,
|
||||
|
||||
active_mixnodes_available: AtomicBool,
|
||||
active_gateways_available: AtomicBool,
|
||||
|
||||
current_mixnode_active_set_size: AtomicUsize,
|
||||
current_gateway_active_set_size: AtomicUsize,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Clone)]
|
||||
@@ -131,7 +128,6 @@ impl ValidatorCache {
|
||||
routes::get_mixnodes,
|
||||
routes::get_gateways,
|
||||
routes::get_active_mixnodes,
|
||||
routes::get_active_gateways
|
||||
],
|
||||
)
|
||||
})
|
||||
@@ -155,28 +151,10 @@ impl ValidatorCache {
|
||||
true
|
||||
}
|
||||
|
||||
// TODO: check if all nodes can be compared together,
|
||||
// i.e. they all have the same denom for bonds and delegations
|
||||
fn verify_gateways(&self, gateways: &[GatewayBond]) -> bool {
|
||||
if gateways.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let expected_denom = &gateways[0].bond_amount.denom;
|
||||
for gateway in gateways {
|
||||
if &gateway.bond_amount.denom != expected_denom
|
||||
|| &gateway.total_delegation.denom != expected_denom
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
async fn update_cache(
|
||||
&self,
|
||||
mut mixnodes: Vec<MixNodeBond>,
|
||||
mut gateways: Vec<GatewayBond>,
|
||||
gateways: Vec<GatewayBond>,
|
||||
state: StateParams,
|
||||
) {
|
||||
// if our data is valid, it means the active sets are available,
|
||||
@@ -199,23 +177,6 @@ impl ValidatorCache {
|
||||
.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if self.verify_gateways(&gateways) {
|
||||
// partial_cmp can only fail if the nodes have different denomination,
|
||||
// but we just checked for that hence the unwraps are fine here
|
||||
// Note the reverse order of comparison so that the "highest" node would be first
|
||||
gateways.sort_by(|a, b| b.partial_cmp(a).unwrap());
|
||||
self.inner
|
||||
.active_gateways_available
|
||||
.store(true, Ordering::SeqCst);
|
||||
self.inner
|
||||
.current_gateway_active_set_size
|
||||
.store(state.gateway_active_set_size as usize, Ordering::SeqCst);
|
||||
} else {
|
||||
self.inner
|
||||
.active_gateways_available
|
||||
.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
self.inner.mixnodes.write().await.set(mixnodes);
|
||||
self.inner.gateways.write().await.set(gateways);
|
||||
}
|
||||
@@ -252,30 +213,6 @@ impl ValidatorCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn active_gateways(&self) -> Option<Cache<Vec<GatewayBond>>> {
|
||||
// if active set is available, it means it is already sorted
|
||||
if self.inner.active_gateways_available.load(Ordering::SeqCst) {
|
||||
let cache = self.inner.gateways.read().await;
|
||||
let timestamp = cache.as_at;
|
||||
let nodes = cache
|
||||
.value
|
||||
.iter()
|
||||
.take(
|
||||
self.inner
|
||||
.current_gateway_active_set_size
|
||||
.load(Ordering::SeqCst),
|
||||
)
|
||||
.cloned()
|
||||
.collect();
|
||||
Some(Cache {
|
||||
value: nodes,
|
||||
as_at: timestamp,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialised(&self) -> bool {
|
||||
self.inner.initialised.load(Ordering::Relaxed)
|
||||
}
|
||||
@@ -300,9 +237,7 @@ impl ValidatorCacheInner {
|
||||
mixnodes: RwLock::new(Cache::default()),
|
||||
gateways: RwLock::new(Cache::default()),
|
||||
active_mixnodes_available: AtomicBool::new(false),
|
||||
active_gateways_available: AtomicBool::new(false),
|
||||
current_mixnode_active_set_size: Default::default(),
|
||||
current_gateway_active_set_size: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-7
@@ -22,10 +22,3 @@ pub(crate) async fn get_active_mixnodes(
|
||||
) -> Option<Json<Vec<MixNodeBond>>> {
|
||||
cache.active_mixnodes().await.map(|cache| Json(cache.value))
|
||||
}
|
||||
|
||||
#[get("/gateways/active")]
|
||||
pub(crate) async fn get_active_gateways(
|
||||
cache: &State<ValidatorCache>,
|
||||
) -> Option<Json<Vec<GatewayBond>>> {
|
||||
cache.active_gateways().await.map(|cache| Json(cache.value))
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ use crate::cache::ValidatorCacheRefresher;
|
||||
use crate::config::Config;
|
||||
use crate::network_monitor::tested_network::good_topology::parse_topology_file;
|
||||
use crate::network_monitor::NetworkMonitorBuilder;
|
||||
use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater;
|
||||
use crate::nymd_client::Client;
|
||||
use crate::rewarding::epoch::Epoch;
|
||||
use crate::rewarding::Rewarder;
|
||||
use crate::storage::NodeStatusStorage;
|
||||
use crate::storage::ValidatorApiStorage;
|
||||
use ::config::NymConfig;
|
||||
use anyhow::Result;
|
||||
use cache::ValidatorCache;
|
||||
@@ -335,7 +336,7 @@ fn setup_network_monitor<'a>(
|
||||
}
|
||||
|
||||
// get instances of managed states
|
||||
let node_status_storage = rocket.state::<NodeStatusStorage>().unwrap().clone();
|
||||
let node_status_storage = rocket.state::<ValidatorApiStorage>().unwrap().clone();
|
||||
let validator_cache = rocket.state::<ValidatorCache>().unwrap().clone();
|
||||
|
||||
let v4_topology = parse_topology_file(config.get_v4_good_topology_file());
|
||||
@@ -367,7 +368,7 @@ fn setup_rewarder(
|
||||
) -> Option<Rewarder> {
|
||||
if config.get_rewarding_enabled() && config.get_network_monitor_enabled() {
|
||||
// get instances of managed states
|
||||
let node_status_storage = rocket.state::<NodeStatusStorage>().unwrap().clone();
|
||||
let node_status_storage = rocket.state::<ValidatorApiStorage>().unwrap().clone();
|
||||
let validator_cache = rocket.state::<ValidatorCache>().unwrap().clone();
|
||||
|
||||
let first_epoch = Epoch::new(
|
||||
@@ -475,6 +476,12 @@ async fn main() -> Result<()> {
|
||||
// spawn our cacher
|
||||
tokio::spawn(async move { validator_cache_refresher.run().await });
|
||||
|
||||
// setup our daily uptime updater. Note that if network monitor is disabled, then we have
|
||||
// no data for the updates and hence we don't need to start it up
|
||||
let storage = rocket.state::<ValidatorApiStorage>().unwrap().clone();
|
||||
let uptime_updater = HistoricalUptimeUpdater::new(storage);
|
||||
tokio::spawn(async move { uptime_updater.run().await });
|
||||
|
||||
if let Some(rewarder) = setup_rewarder(&config, &rocket, &nymd_client) {
|
||||
info!("Periodic rewarding is starting...");
|
||||
tokio::spawn(async move { rewarder.run().await });
|
||||
|
||||
@@ -31,7 +31,15 @@ use crate::network_monitor::monitor::receiver::{
|
||||
use crate::network_monitor::monitor::sender::PacketSender;
|
||||
use crate::network_monitor::monitor::summary_producer::SummaryProducer;
|
||||
use crate::network_monitor::tested_network::TestedNetwork;
|
||||
|
||||
use crate::storage::NodeStatusStorage;
|
||||
use crate::storage::ValidatorApiStorage;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use futures::channel::mpsc;
|
||||
use log::info;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use std::sync::Arc;
|
||||
use topology::NymTopology;
|
||||
|
||||
pub(crate) mod chunker;
|
||||
pub(crate) mod gateways_reader;
|
||||
@@ -42,7 +50,7 @@ pub(crate) mod tested_network;
|
||||
pub(crate) struct NetworkMonitorBuilder<'a> {
|
||||
config: &'a Config,
|
||||
tested_network: TestedNetwork,
|
||||
node_status_storage: NodeStatusStorage,
|
||||
node_status_storage: ValidatorApiStorage,
|
||||
validator_cache: ValidatorCache,
|
||||
}
|
||||
|
||||
@@ -51,7 +59,7 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
config: &'a Config,
|
||||
v4_topology: NymTopology,
|
||||
v6_topology: NymTopology,
|
||||
node_status_storage: NodeStatusStorage,
|
||||
node_status_storage: ValidatorApiStorage,
|
||||
validator_cache: ValidatorCache,
|
||||
) -> Self {
|
||||
let tested_network = TestedNetwork::new_good(v4_topology, v6_topology);
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::network_monitor::monitor::sender::PacketSender;
|
||||
use crate::network_monitor::monitor::summary_producer::{NodeResult, SummaryProducer, TestReport};
|
||||
use crate::network_monitor::test_packet::NodeType;
|
||||
use crate::network_monitor::tested_network::TestedNetwork;
|
||||
use crate::storage::NodeStatusStorage;
|
||||
use crate::storage::ValidatorApiStorage;
|
||||
use log::{debug, error, info, warn};
|
||||
use std::process;
|
||||
use tokio::time::{sleep, Duration, Instant};
|
||||
@@ -25,7 +25,7 @@ pub(super) struct Monitor {
|
||||
packet_sender: PacketSender,
|
||||
received_processor: ReceivedProcessor,
|
||||
summary_producer: SummaryProducer,
|
||||
node_status_storage: NodeStatusStorage,
|
||||
node_status_storage: ValidatorApiStorage,
|
||||
tested_network: TestedNetwork,
|
||||
run_interval: Duration,
|
||||
gateway_ping_interval: Duration,
|
||||
@@ -39,7 +39,7 @@ impl Monitor {
|
||||
packet_sender: PacketSender,
|
||||
received_processor: ReceivedProcessor,
|
||||
summary_producer: SummaryProducer,
|
||||
node_status_storage: NodeStatusStorage,
|
||||
node_status_storage: ValidatorApiStorage,
|
||||
tested_network: TestedNetwork,
|
||||
) -> Self {
|
||||
Monitor {
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::time::Duration;
|
||||
pub(crate) mod local_guard;
|
||||
pub(crate) mod models;
|
||||
pub(crate) mod routes;
|
||||
pub(crate) mod uptime_updater;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub(crate) const FIFTEEN_MINUTES: Duration = Duration::from_secs(900);
|
||||
@@ -18,7 +19,7 @@ pub(crate) const ONE_DAY: Duration = Duration::from_secs(86400);
|
||||
pub(crate) fn stage(database_path: PathBuf) -> AdHoc {
|
||||
AdHoc::on_ignite("SQLx Stage", |rocket| async {
|
||||
rocket
|
||||
.attach(storage::NodeStatusStorage::stage(database_path))
|
||||
.attach(storage::ValidatorApiStorage::stage(database_path))
|
||||
.mount(
|
||||
"/v1/status",
|
||||
routes![
|
||||
|
||||
@@ -213,12 +213,12 @@ pub struct HistoricalUptime {
|
||||
}
|
||||
|
||||
pub(crate) struct ErrorResponse {
|
||||
error: NodeStatusApiError,
|
||||
error: ValidatorApiStorageError,
|
||||
status: Status,
|
||||
}
|
||||
|
||||
impl ErrorResponse {
|
||||
pub(crate) fn new(error: NodeStatusApiError, status: Status) -> Self {
|
||||
pub(crate) fn new(error: ValidatorApiStorageError, status: Status) -> Self {
|
||||
ErrorResponse { error, status }
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,7 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for ErrorResponse {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NodeStatusApiError {
|
||||
pub enum ValidatorApiStorageError {
|
||||
MixnodeReportNotFound(String),
|
||||
GatewayReportNotFound(String),
|
||||
MixnodeUptimeHistoryNotFound(String),
|
||||
@@ -245,30 +245,30 @@ pub enum NodeStatusApiError {
|
||||
InternalDatabaseError,
|
||||
}
|
||||
|
||||
impl Display for NodeStatusApiError {
|
||||
impl Display for ValidatorApiStorageError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
NodeStatusApiError::MixnodeReportNotFound(identity) => write!(
|
||||
ValidatorApiStorageError::MixnodeReportNotFound(identity) => write!(
|
||||
f,
|
||||
"Could not find status report associated with mixnode {}",
|
||||
identity
|
||||
),
|
||||
NodeStatusApiError::GatewayReportNotFound(identity) => write!(
|
||||
ValidatorApiStorageError::GatewayReportNotFound(identity) => write!(
|
||||
f,
|
||||
"Could not find status report associated with gateway {}",
|
||||
identity
|
||||
),
|
||||
NodeStatusApiError::MixnodeUptimeHistoryNotFound(identity) => write!(
|
||||
ValidatorApiStorageError::MixnodeUptimeHistoryNotFound(identity) => write!(
|
||||
f,
|
||||
"Could not find uptime history associated with mixnode {}",
|
||||
identity
|
||||
),
|
||||
NodeStatusApiError::GatewayUptimeHistoryNotFound(identity) => write!(
|
||||
ValidatorApiStorageError::GatewayUptimeHistoryNotFound(identity) => write!(
|
||||
f,
|
||||
"Could not find uptime history associated with gateway {}",
|
||||
identity
|
||||
),
|
||||
NodeStatusApiError::InternalDatabaseError => {
|
||||
ValidatorApiStorageError::InternalDatabaseError => {
|
||||
write!(f, "The internal database has experienced an issue")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ use crate::node_status_api::models::{
|
||||
ErrorResponse, GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport,
|
||||
MixnodeUptimeHistory,
|
||||
};
|
||||
use crate::storage::NodeStatusStorage;
|
||||
use crate::storage::ValidatorApiStorage;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
#[get("/mixnode/<pubkey>/report")]
|
||||
pub(crate) async fn mixnode_report(
|
||||
storage: &State<NodeStatusStorage>,
|
||||
storage: &State<ValidatorApiStorage>,
|
||||
pubkey: &str,
|
||||
) -> Result<Json<MixnodeStatusReport>, ErrorResponse> {
|
||||
storage
|
||||
@@ -24,7 +24,7 @@ pub(crate) async fn mixnode_report(
|
||||
|
||||
#[get("/gateway/<pubkey>/report")]
|
||||
pub(crate) async fn gateway_report(
|
||||
storage: &State<NodeStatusStorage>,
|
||||
storage: &State<ValidatorApiStorage>,
|
||||
pubkey: &str,
|
||||
) -> Result<Json<GatewayStatusReport>, ErrorResponse> {
|
||||
storage
|
||||
@@ -36,7 +36,7 @@ pub(crate) async fn gateway_report(
|
||||
|
||||
#[get("/mixnode/<pubkey>/history")]
|
||||
pub(crate) async fn mixnode_uptime_history(
|
||||
storage: &State<NodeStatusStorage>,
|
||||
storage: &State<ValidatorApiStorage>,
|
||||
pubkey: &str,
|
||||
) -> Result<Json<MixnodeUptimeHistory>, ErrorResponse> {
|
||||
storage
|
||||
@@ -48,7 +48,7 @@ pub(crate) async fn mixnode_uptime_history(
|
||||
|
||||
#[get("/gateway/<pubkey>/history")]
|
||||
pub(crate) async fn gateway_uptime_history(
|
||||
storage: &State<NodeStatusStorage>,
|
||||
storage: &State<ValidatorApiStorage>,
|
||||
pubkey: &str,
|
||||
) -> Result<Json<GatewayUptimeHistory>, ErrorResponse> {
|
||||
storage
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user