Compare commits

...

127 Commits

Author SHA1 Message Date
Jon Häggblad e5d68a5e7f Don't set NYM_VPN_API to default (#4740) 2024-07-31 11:45:36 +02:00
Tommy Verrall 7ddd819ff3 Merge pull request #4739 from nymtech/tommy/add-wireguard-publish-binaries
Update publish-nym-binaries.yml
2024-07-30 11:41:43 +02:00
Tommy Verrall 83b416d12d amend build all binaries command 2024-07-30 11:38:07 +02:00
Tommy Verrall b9c775c3ae Update publish-nym-binaries.yml
add wireguard to builds
2024-07-30 11:27:50 +02:00
mx 6f669866e9 Max/doc link fix (#4737)
* fix broken link in header dropdown

---------

Co-authored-by: serinko <97586125+serinko@users.noreply.github.com>
Co-authored-by: mfahampshire <mfahampshire@pm.me>
2024-07-30 08:48:14 +00:00
Tommy Verrall 4e61fefec8 Merge pull request #4736 from nymtech/jon/nym-vpn-api-env
Add NYM_VPN_API to network config
2024-07-30 10:04:55 +02:00
Jon Häggblad b4514ecd83 update for wallet 2024-07-29 23:50:52 +02:00
Jon Häggblad 4f6902525e restore explorer-api 2024-07-29 23:30:09 +02:00
Jon Häggblad 881139e36f Add nym_vpn_api_url 2024-07-29 23:30:09 +02:00
Jon Häggblad 32e2557456 Fix tokio error in 1.39 (#4730)
* Fix tokio error in 1.39

Fix the error generated by tokio 1.39

72 | /             tokio::select! {
173 | |                 daemon_res = &mut fused_runner => {
174 | |                     warn!("the daemon has terminated by itself - was it a short lived command?");
175 | |                     let exit_status = daemon_res?;
...   |
179 | |                 event = &mut self.upgrade_plan_watcher.next() => {
    | |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use
...   |
201 | |                 }
202 | |             }
    | |             -
    | |             |
    | |_____________temporary value is freed at the end of this statement
    |               borrow later used here

and

62 | /         select! {
63 | |             connection_message = &mut mix_receiver.next() => {
   | |                                       ^^^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use
64 | |                 if let Some(connection_message) = connection_message {
65 | |                     if deal_with_message(connection_message, &mut writer, &local_destination_address, &remote_source_address, connection_id).await {
...  |
86 | |             }
87 | |         }
   | |         -
   | |         |
   | |_________temporary value is freed at the end of this statement
   |           borrow later used here

* Upgrade to tokio 1.39.1

* Simpler attempt

* Revert fixes and instead bump to tokio 1.39.2

* update

* bump msrv for nym-node-tester-wasm
2024-07-29 20:45:26 +02:00
Jon Häggblad 8b44820e51 Re-export RecipientFormattingError in nym sdk (#4735) 2024-07-29 19:20:26 +02:00
import this 5e6417f837 clarify syntax - PR ready (#4734) 2024-07-29 13:51:31 +00:00
Jędrzej Stuczyński dfb2a2f380 Merge pull request #4716 from nymtech/feature/vesting-purge-plus-ranged-cost-params
Feature/vesting purge plus ranged cost params
2024-07-26 18:01:29 +01:00
fmtabbara d1de751850 fix ci 2024-07-26 17:28:24 +01:00
Jędrzej Stuczyński ecee6ca863 chore: cargo fmt 2024-07-26 15:08:38 +01:00
fmtabbara 31ea3f92e2 update bonding oc and pm validation 2024-07-26 15:08:38 +01:00
fmtabbara f19c934fae finish migrate vested bonded node work 2024-07-26 15:08:38 +01:00
Mark Sinclair 10d6f20de7 wip: add profit margin and cost params into validation from mixnet contract via MainContext 2024-07-26 15:08:38 +01:00
Mark Sinclair 96b33bfbe4 Regenerate TS types 2024-07-26 15:08:38 +01:00
Mark Sinclair 444c787d0a Add kind prop to vesting contract migration modal 2024-07-26 15:08:38 +01:00
Mark Sinclair 61fcd4ac69 Dialog and mock for migrating vesting contract delegations 2024-07-26 15:08:38 +01:00
Jędrzej Stuczyński b76802e6eb exposed tauri operations for vesting migrations 2024-07-26 15:08:38 +01:00
Mark Sinclair 7d351029a4 Fix dependency issue 2024-07-26 15:08:37 +01:00
Jędrzej Stuczyński 4ee445c119 cargo fmt 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 61ddeea495 fixed post-rebasing imports 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 7b802033b3 missing test fixture 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński b484f47369 fix nym-cli 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 66979df10c update contract schema 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 82f161fb91 added associated [hacky] wallet types 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński 9d0fd681d4 introducing allowed range of operator interval operating cost 2024-07-26 15:05:47 +01:00
Jędrzej Stuczyński c2ab47a102 profit margin range validation 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 8704c21621 normalise node's profit margin during rewarding 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 03ffb25bf9 introduced the concept of allowed profit margin ranges 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 70db1ad062 fixed vesting contract tests 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 952ed9b642 fixed wallet vesting-related tests 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński f57fe79686 updated contract schema 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 9179f1c351 exposed migration commands to nym-cli + clippy 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński c4f7a1e09d implemented migration into non-vesting mixnodes/delegations 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 701012a968 ensure no pending proxy events when migrating 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 9767f72b8f removed all on_behalf mixnet contract methods 2024-07-26 15:05:46 +01:00
Jędrzej Stuczyński 7b10d92ca4 Merge pull request #4731 from nymtech/chore/1.80-lints
chore: fix 1.80 lint issues
2024-07-26 11:51:23 +01:00
Jędrzej Stuczyński 2c6e5eb673 cherry-pick: fix build issues 2024-07-26 11:11:52 +01:00
Jon Häggblad 02fde4e530 Handle clients with different versions in IPR (#4723)
* Add signable_request function

* Export key type in function signature

* Cargo.lock

* Track client version and respond using it

* Internally use v7 and then down convert if needed

* Local response type

* Streamline

* Strong type for client version

* Remove commented out code

* rustfmt

* Ignore sign verification fail for v6
2024-07-24 15:35:59 +02:00
import this cc25fc1f32 [DOCs/operators]: Changelog for v2024.8 wispa & guide syntax edits (#4728)
* changelog for release v2024.8-wispa

* clarify syntax

* typo fix
2024-07-24 12:38:25 +00:00
benedetta davico c971e486b5 Merge pull request #4726 from nymtech/release/2024.8-wispa
Release/2024.8 wispa into develop
2024-07-24 12:48:57 +02:00
import this 96a9eb6f6a [DOCs/docs]: Commnet out extra stubs (#4727)
* commnet out stubs

* fix broken links - ready to merge
2024-07-24 11:58:14 +02:00
benedetta davico 9eeb61ea0a Merge branch 'develop' into release/2024.8-wispa 2024-07-24 10:56:03 +02:00
John Smith 08042c61ad [DOCs/operators]: Update troubleshooting/vps-isp.md with manual IPv6 configuration (#4651)
* Update vps-isp.md

Added an extra diagnostic step, which helped me to debug lack of routability.

* Update vps-isp.md

Implementing serinko's comments

* Update vps-isp.md

Changed possibly to possible and added how to find IPv6 Gateway.

* Update vps-isp.md

Fixed ifup/ifdown link
2024-07-24 08:53:49 +00:00
Stefano Piermatteo 36c74f30e5 [DOCs/operators]: Syntax fix in setup.md (#4682) 2024-07-24 08:37:33 +00:00
Bogdan-Ștefan Neacşu fd1d437211 Add 1GB/day/user bandwidth cap (#4717)
* Add check for 1GB/day/user and remove stale check

* Use saturated_sub

* Remove from wg peers

* Use 10 seconds instead of 1

* Query bandwidth message

* Ad client query message too

* Keep stale check

* Make bandwidth cap value public

* Fix consumed vs available bug

* Don't overwrite existing registrations

* Use self pub key instead of peer's
2024-07-23 20:49:49 +02:00
Tommy Verrall 4956d13bdc fix conflicts 2024-07-23 17:32:49 +02:00
Jędrzej Stuczyński 6478736654 Merge pull request #4706 from nymtech/chore/remove-old-migration-code
removed mixnode/gateway config migration code and disabled cli without explicit flag
2024-07-23 15:11:36 +01:00
benedettadavico d9f6c0723e updating versions 2024-07-23 15:37:04 +02:00
Jon Häggblad f86050d916 Default construct NodeRole for backwards compatibility (#4722) 2024-07-22 16:04:29 +02:00
Tommy Verrall 52f5656190 Merge pull request #4721 from nymtech/jon/node-role-default
Default construct NodeRole
2024-07-22 15:09:27 +02:00
Jon Häggblad 21cd90f238 Default construct NodeRole for backwards compatibility 2024-07-22 14:59:18 +02:00
import this 4e51188d35 [DOCs/operators]: Guide to back up and restore nym-node (#4720)
* add node backup & node restore guides

* finished: ready to review

* finished: ready to review
2024-07-22 11:17:55 +00:00
John Smith 22eb199936 Update isp-sheet.csv (#4718)
* Update isp-sheet.csv

Added a few known VPS providers which (a) support crypto payment (b) allow TOR in some shape or form (c) more or less know for their stability. Will add more eventually.

* Update isp-sheet.csv

added a few more providers
2024-07-22 10:57:57 +00:00
Tommy Verrall a2fc1bbc96 Merge pull request #4719 from nymtech/serinko/bug-fix/wss-guide
[DOCs/operators]: BugFix - add ssl cert to WSS server block
2024-07-19 12:48:36 +02:00
import this 621599692f add ssl cert to WSS server block 2024-07-19 10:43:02 +00:00
import this 3ad3837c87 done: fix wrong URL and picture formatting (#4714) 2024-07-17 10:47:41 +00:00
import this 4d745e3b7e [DOCs/operators]: Correct ports for bonding (#4707)
* fix port issue for nym-node mixnode

* fix port issue for nym-node mixnode

* simplify language

* clarify moving node info

* syntax fix
2024-07-16 14:00:38 +00:00
Sachin Kamath 3a053b8dd6 fix links (#4712) 2024-07-15 18:06:56 +02:00
Bogdan-Ștefan Neacşu 1f144690da Add upgrades to nym-node for authenticator changes (#4703) (#4710)
* Add iterative upgrades to nym-node

* Authenticator correct configuration

* Add info log

* Enable auth opts on entry gw

* Move ephemeral config from exit_gateway

* Fix fmt

* Fix clippy

* Pass custom transceiver for authenticator

* Fix non-linux build

* Feature gate wg_api

* Change naming from semver to simple incremental

* Move opts unwrap inside the mutable function

* Remove unneeded authenticator_description
2024-07-12 14:45:59 +02:00
Tommy Verrall eec1895acc Merge pull request #4709 from nymtech/dependabot/npm_and_yarn/nym-wallet/webdriver/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3 in /nym-wallet/webdriver
2024-07-12 12:14:04 +02:00
Bogdan-Ștefan Neacşu 72e243042e Add upgrades to nym-node for authenticator changes (#4703)
* Add iterative upgrades to nym-node

* Authenticator correct configuration

* Add info log

* Enable auth opts on entry gw

* Move ephemeral config from exit_gateway

* Fix fmt

* Fix clippy

* Pass custom transceiver for authenticator

* Fix non-linux build

* Feature gate wg_api

* Change naming from semver to simple incremental

* Move opts unwrap inside the mutable function

* Remove unneeded authenticator_description
2024-07-12 12:02:22 +02:00
dependabot[bot] 99864cb7a9 Bump braces from 3.0.2 to 3.0.3 in /nym-wallet/webdriver
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 09:09:14 +00:00
Fouad 3155728119 fix explorer mui deps (#4708) 2024-07-12 10:08:29 +01:00
Jędrzej Stuczyński c253b22f69 fixed the positional argument 2024-07-11 16:20:20 +01:00
Jędrzej Stuczyński 66f3a3e9a8 removed mixnode/gateway config migration code and disabled commands without explicit flag 2024-07-11 15:53:10 +01:00
Sachin Kamath 65a1d6d91e switch to new vanity link (#4705) 2024-07-11 13:04:56 +01:00
mx 44cf9b054b Max/dev portal update (#4696)
* updated tutorials with archive + stub

* removed out of date faq pages

* added binary build instructions section

* removed clients from docs

* added clients to devportal

* moved nym-vs-others to docs from devportal

* removed ood quickstart stuff

* tweaked integration options page

* summary changes for new structure

* moved sdk to devportal

* removed sdk from docs

* changed summary file for new structure

* added intro client overview page

* added wallet gif

* fixed now broken links

* removed old comm pages

* added references to newer apps (oreowallet + zcash demo)

* updated darkfi irc socks5

* fixed broken link

---------

Co-authored-by: mfahampshire <mfahampshire@pm.me>
2024-07-10 13:54:13 +02:00
Tommy Verrall 39e2473ef3 Merge pull request #4702 from nymtech/release/2024.7-doubledecker
Release/2024.7 doubledecker
2024-07-10 13:25:11 +02:00
import this 93a108863c add node description and release changelog (#4701) 2024-07-10 10:59:47 +00:00
import this 0905593123 [DOCs/operators]: Test WSS for exit-gateway, write a tutorial & update reversed proxy page (#4694)
* initialise wss guide

* update reverse proxy guide to post smoosh

* finish draft - missing correct script and testing

* syntax edit

* syntax edit

* update WSS configs and script

* edit WSS configs and script

* dinish wss guide - ready to review

* dinish wss guide - ready to review
2024-07-10 10:59:22 +00:00
Tommy Verrall ed9223d5a3 Merge branch 'develop' into release/2024.7-doubledecker 2024-07-10 11:46:21 +02:00
benedettadavico c2ad4e5bb4 Update changelog and bump versions 2024-07-10 11:01:20 +02:00
Tommy Verrall 5f7f5ef92d Merge pull request #4699 from nymtech/release/2024.7-doubledecker
Release/2024.7 doubledecker
2024-07-10 10:46:03 +02:00
Tommy Verrall 962684ff56 Merge pull request #4667 from nymtech/feature/authenticator
Add authenticator
2024-07-10 10:14:58 +02:00
Tommy Verrall 7b3804c078 Merge pull request #4697 from nymtech/event-parsing
add event parsing to support cosmos_sdk > 0.50
2024-07-10 09:55:43 +02:00
Sachin Kamath 170f1823e1 fix tests 2024-07-09 21:54:57 +05:30
Sachin Kamath dc2020559a parse attributes from events instead of raw logs 2024-07-09 21:24:58 +05:30
Tommy Verrall 2b9444cce3 add an early return in parse_raw_str_logs for empty raw log strings.
this accommodates for the v50 chain upgrade
2024-07-09 20:36:36 +05:30
Bogdan-Ștefan Neacşu 68c1c068ac Add old config upgrade flow 2024-07-09 09:16:00 +00:00
Drazen Urch 3d0b70a237 Add mixnodes to self describing api cache (#4684)
* Add mixnodes to self describing api cache

* Use NodeRole enum

* Add route for described mixnodes

* Cleanup contract_cache

* Remove nodestatuscache

* wait_until_ready impl
2024-07-09 10:54:48 +02:00
Bogdan-Ștefan Neacşu 65a6edc78c Add authenticator debug to entry gateway config 2024-07-08 11:00:53 +00:00
Sachin Kamath 2ec8349897 update social links (#4695)
* replace vanity link

* fix links
2024-07-05 14:19:09 +00:00
Bogdan-Ștefan Neacşu 38a2d94f80 Fix clippy 2024-07-05 11:36:15 +00:00
Bogdan-Ștefan Neacşu c7fa910516 Fix add of req id 2024-07-05 10:36:18 +00:00
Bogdan-Ștefan Neacşu 2fe08274dd Add another layer for request id field 2024-07-05 10:20:56 +00:00
mx be89d848dc Max/try fix doc search (#4692)
* minimised dropdown bar

* update ci scripts

* theme changes to dev portal

* theme changes to operators

* theme changes to docs

* theme -> themes

* fixed theme -> themes import in book

* removed bak files

* remove logging from post_process + remove search feature from mdbook

---------

Co-authored-by: mfahampshire <mfahampshire@pm.me>
2024-07-05 12:19:14 +02:00
Bogdan-Ștefan Neacşu a230a9b8b9 Merge remote-tracking branch 'origin/develop' into feature/authenticator 2024-07-05 09:57:48 +00:00
Bogdan-Ștefan Neacşu 72eae7cdf3 Remove stale mid-registrations 2024-07-05 09:38:17 +00:00
Bogdan-Ștefan Neacşu 7cae195370 Typo 2024-07-05 07:54:29 +00:00
Bogdan-Ștefan Neacşu dfb16e385c Rand from workspace 2024-07-05 07:53:33 +00:00
Bogdan-Ștefan Neacşu 660e1cad0a Reconstruction msg 2024-07-04 14:26:37 +00:00
Bogdan-Ștefan Neacşu 7c1aa57a7e Add curr version 2024-07-04 14:15:25 +00:00
Bogdan-Ștefan Neacşu a06e496f78 Add final req creation 2024-07-04 13:54:13 +00:00
Jon Häggblad 70599b97b9 Send bandwidth status messages when connecting (#4691)
* Send bandwidth status messages when connecting

* Rename to task_client

* Move status message type to bandwidth controller
2024-07-04 13:10:16 +02:00
Bogdan-Ștefan Neacşu 02b194bde0 Function to create AuthReq 2024-07-04 10:40:53 +00:00
Bogdan-Ștefan Neacşu 20ec049db5 Remove more unnecessary structures 2024-07-04 09:41:17 +00:00
Tommy Verrall ebac4e8564 Update ci-build-upload-binaries.yml 2024-07-04 09:57:37 +02:00
benedettadavico da81664729 update versions and changelog 2024-07-04 09:36:09 +02:00
Bogdan-Ștefan Neacşu fec3d46b33 Include auth in self description 2024-07-03 13:50:44 +00:00
Bogdan-Ștefan Neacşu a4eb3a7dbf Named fork for better logging 2024-07-03 13:03:32 +00:00
Bogdan-Ștefan Neacşu 28d15f2c4f Remove unused import 2024-07-03 11:28:09 +00:00
Bogdan-Ștefan Neacşu c6f93e38f5 Remove unused post function 2024-07-03 09:34:03 +00:00
Bogdan-Ștefan Neacşu 2159f71888 Fix wg feature 2024-07-03 09:04:01 +00:00
Bogdan-Ștefan Neacşu a9abea3446 Fix macos build 2024-07-03 08:27:10 +00:00
Bogdan-Ștefan Neacşu 8e2713c9ba Remove unwrap 2024-07-03 08:21:58 +00:00
Bogdan-Ștefan Neacşu 2ba0ef0e35 Remove unused wg http endpoint 2024-07-02 14:09:34 +00:00
Bogdan-Ștefan Neacşu d3713cbc79 Fix user agent arg 2024-07-02 16:40:11 +03:00
Bogdan-Ștefan Neacşu 4d3fb2b585 Merge remote-tracking branch 'origin/develop' into feature/authenticator 2024-07-02 16:25:29 +03:00
benedetta davico ebfb9c4bc1 Merge pull request #4686 from nymtech/bugfix/chain-upgrade-raw-logs
Add an early return in `parse_raw_str_logs` for empty raw log strings.
2024-07-02 12:29:48 +02:00
Tommy Verrall 8e7918cc45 add an early return in parse_raw_str_logs for empty raw log strings.
this accommodates for the v50 chain upgrade
2024-07-02 11:19:50 +02:00
Bogdan-Ștefan Neacşu c465eb3efc Fix error type 2024-07-02 08:58:48 +00:00
Bogdan-Ștefan Neacşu b90136ac4e Uniformise non-linux wg function 2024-07-01 15:10:36 +00:00
Bogdan-Ștefan Neacşu ae5373168d Fmt 2024-07-01 14:42:10 +00:00
Bogdan-Ștefan Neacşu 6f3942f6b7 Move implementation for final request 2024-07-01 14:38:48 +00:00
Bogdan-Ștefan Neacşu 13f38343aa Finalize cli build 2024-07-01 13:44:59 +00:00
Bogdan-Ștefan Neacşu f75b4843e8 Merge remote-tracking branch 'origin/develop' into feature/authenticator 2024-07-01 09:56:34 +00:00
benedetta davico 008afe7a85 Merge pull request #4676 from nymtech/release/2024.6-chomp
Release/2024.6-chomp to master
2024-06-27 11:47:55 +02:00
Bogdan-Ștefan Neacşu b43844bd7a Embed into gateway 2024-06-21 15:51:02 +00:00
Bogdan-Ștefan Neacşu cd89feb57e Fix path 2024-06-20 15:15:05 +00:00
Bogdan-Ștefan Neacşu 17553d606e Requests and responses 2024-06-20 16:58:21 +03:00
Bogdan-Ștefan Neacşu b6d9ed960b Specify version 2024-06-19 11:37:09 +03:00
Bogdan-Ștefan Neacşu 1d89a887fb Add authenticator 2024-06-18 19:05:23 +03:00
384 changed files with 9663 additions and 10533 deletions
@@ -104,8 +104,6 @@ jobs:
name: nym-binaries-artifacts
path: |
target/release/nym-client
target/release/nym-gateway
target/release/nym-mixnode
target/release/nym-socks5-client
target/release/nym-api
target/release/nym-network-requester
@@ -123,8 +121,6 @@ jobs:
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
run: |
cp target/release/nym-client $OUTPUT_DIR
cp target/release/nym-gateway $OUTPUT_DIR
cp target/release/nym-mixnode $OUTPUT_DIR
cp target/release/nym-socks5-client $OUTPUT_DIR
cp target/release/nym-api $OUTPUT_DIR
cp target/release/nym-network-requester $OUTPUT_DIR
+6 -2
View File
@@ -51,6 +51,10 @@ jobs:
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Set CARGO_FEATURES
run: |
echo 'CARGO_FEATURES=--features wireguard' >> $GITHUB_ENV
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
@@ -60,8 +64,8 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release
args: --workspace --release ${{ env.CARGO_FEATURES }}
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
+56 -1
View File
@@ -4,6 +4,62 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2024.8-wispa] (2024-07-10)
- add event parsing to support cosmos_sdk > 0.50 ([#4697])
- Fix NR config compatibility ([#4690])
- Remove UserAgent constructor since it's weakly typed ([#4689])
- [bugfix]: Node_api_check CLI looked over roles on blacklisted nodes ([#4687])
- Add mixnodes to self describing api cache ([#4684])
- Move and whole bump of crates to workspace and upgrade some ([#4680])
- Remove code that refers to removed nym-network-statistics ([#4679])
- Remove nym-network-statistics ([#4678])
- Create UserAgent that can be passed from the binary to the nym api client ([#4677])
- Add authenticator ([#4667])
[#4697]: https://github.com/nymtech/nym/pull/4697
[#4690]: https://github.com/nymtech/nym/pull/4690
[#4689]: https://github.com/nymtech/nym/pull/4689
[#4687]: https://github.com/nymtech/nym/pull/4687
[#4684]: https://github.com/nymtech/nym/pull/4684
[#4680]: https://github.com/nymtech/nym/pull/4680
[#4679]: https://github.com/nymtech/nym/pull/4679
[#4678]: https://github.com/nymtech/nym/pull/4678
[#4677]: https://github.com/nymtech/nym/pull/4677
[#4667]: https://github.com/nymtech/nym/pull/4667
## [2024.7-doubledecker] (2024-07-04)
- Add an early return in `parse_raw_str_logs` for empty raw log strings. ([#4686])
- Bump braces from 3.0.2 to 3.0.3 in /wasm/mix-fetch/internal-dev ([#4672])
- add expiry returned on import ([#4670])
- [bugfix] missing rustls feature ([#4666])
- Bump ws from 8.13.0 to 8.17.1 in /wasm/client/internal-dev-node ([#4665])
- Bump braces from 3.0.2 to 3.0.3 in /clients/native/examples/js-examples/websocket ([#4663])
- Bump ws from 8.14.2 to 8.17.1 in /sdk/typescript/packages/nodejs-client ([#4662])
- Update setup.md ([#4661])
- New clippy lints ([#4660])
- Bump braces from 3.0.2 to 3.0.3 in /nym-api/tests ([#4659])
- Bump braces from 3.0.2 to 3.0.3 in /docker/typescript_client/upload_contract ([#4658])
- Update vps-setup.md ([#4656])
- Update configuration.md ([#4655])
- Remove old PR template ([#4639])
[#4686]: https://github.com/nymtech/nym/pull/4686
[#4672]: https://github.com/nymtech/nym/pull/4672
[#4670]: https://github.com/nymtech/nym/pull/4670
[#4666]: https://github.com/nymtech/nym/pull/4666
[#4665]: https://github.com/nymtech/nym/pull/4665
[#4663]: https://github.com/nymtech/nym/pull/4663
[#4662]: https://github.com/nymtech/nym/pull/4662
[#4661]: https://github.com/nymtech/nym/pull/4661
[#4660]: https://github.com/nymtech/nym/pull/4660
[#4659]: https://github.com/nymtech/nym/pull/4659
[#4658]: https://github.com/nymtech/nym/pull/4658
[#4656]: https://github.com/nymtech/nym/pull/4656
[#4655]: https://github.com/nymtech/nym/pull/4655
[#4639]: https://github.com/nymtech/nym/pull/4639
## [2024.6-chomp] (2024-06-25)
- Remove additional code as part of Ephemera Purge and SP and contracts ([#4650])
@@ -481,7 +537,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#3187]: https://github.com/nymtech/nym/issues/3187
[#3203]: https://github.com/nymtech/nym/pull/3203
[#3199]: https://github.com/nymtech/nym/pull/3199
>>>>>>> master
## [v1.1.13] (2023-03-15)
Generated
+83 -19
View File
@@ -1378,7 +1378,7 @@ dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
"mio 0.8.11",
"parking_lot 0.12.2",
"signal-hook",
"signal-hook-mio",
@@ -1394,7 +1394,7 @@ dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
"mio 0.8.11",
"parking_lot 0.12.2",
"signal-hook",
"signal-hook-mio",
@@ -2093,7 +2093,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.35"
version = "1.1.37"
dependencies = [
"chrono",
"clap 4.5.4",
@@ -3596,6 +3596,18 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
[[package]]
name = "mix-fetch-wasm"
version = "1.3.0-rc.0"
@@ -3777,7 +3789,7 @@ dependencies = [
"inotify",
"kqueue",
"libc",
"mio",
"mio 0.8.11",
"walkdir",
"windows-sys 0.45.0",
]
@@ -3849,7 +3861,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.39"
version = "1.1.41"
dependencies = [
"anyhow",
"async-trait",
@@ -3950,6 +3962,54 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-authenticator"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"bs58 0.5.1",
"bytes",
"clap 4.5.4",
"fastrand 2.1.0",
"futures",
"ipnetwork 0.16.0",
"log",
"nym-authenticator-requests",
"nym-bin-common",
"nym-client-core",
"nym-config",
"nym-crypto",
"nym-id",
"nym-network-defaults",
"nym-sdk",
"nym-service-providers-common",
"nym-sphinx",
"nym-task",
"nym-types",
"nym-wireguard",
"nym-wireguard-types",
"rand 0.8.5",
"serde",
"serde_json",
"thiserror",
"tokio",
"tokio-stream",
"tokio-util",
"url",
]
[[package]]
name = "nym-authenticator-requests"
version = "0.1.0"
dependencies = [
"bincode",
"nym-sphinx",
"nym-wireguard-types",
"rand 0.8.5",
"serde",
]
[[package]]
name = "nym-bandwidth-controller"
version = "0.1.0"
@@ -4009,7 +4069,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.37"
version = "1.1.39"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -4088,7 +4148,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.36"
version = "1.1.38"
dependencies = [
"bs58 0.5.1",
"clap 4.5.4",
@@ -4514,6 +4574,7 @@ dependencies = [
"ipnetwork 0.16.0",
"log",
"nym-api-requests",
"nym-authenticator",
"nym-bin-common",
"nym-config",
"nym-credentials",
@@ -4901,7 +4962,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.37"
version = "1.1.39"
dependencies = [
"addr",
"anyhow",
@@ -4952,7 +5013,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.1.3"
version = "1.1.5"
dependencies = [
"anyhow",
"bip39",
@@ -4964,6 +5025,7 @@ dependencies = [
"cupid",
"humantime-serde",
"ipnetwork 0.16.0",
"nym-authenticator",
"nym-bin-common",
"nym-client-core-config-types",
"nym-config",
@@ -5214,7 +5276,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.36"
version = "1.1.38"
dependencies = [
"bs58 0.5.1",
"clap 4.5.4",
@@ -5692,6 +5754,7 @@ name = "nym-wireguard"
version = "0.1.0"
dependencies = [
"base64 0.21.7",
"chrono",
"dashmap",
"defguard_wireguard_rs",
"ip_network",
@@ -5700,6 +5763,7 @@ dependencies = [
"nym-network-defaults",
"nym-task",
"nym-wireguard-types",
"thiserror",
"tokio",
"tokio-stream",
"x25519-dalek",
@@ -5727,7 +5791,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.2"
version = "0.1.4"
dependencies = [
"anyhow",
"bytes",
@@ -7693,7 +7757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"mio 0.8.11",
"signal-hook",
]
@@ -8402,22 +8466,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.37.0"
version = "1.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"mio 1.0.1",
"parking_lot 0.12.2",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"tracing",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -8432,9 +8495,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.2.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
@@ -9387,6 +9450,7 @@ dependencies = [
name = "wasm-utils"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"futures",
"getrandom 0.2.15",
"gloo-net",
+6 -4
View File
@@ -20,6 +20,7 @@ members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
"common/authenticator-requests",
"common/async-file-watcher",
"common/bandwidth-controller",
"common/bin-common",
@@ -95,6 +96,7 @@ members = [
"mixnode",
"sdk/lib/socks5-listener",
"sdk/rust/nym-sdk",
"service-providers/authenticator",
"service-providers/common",
"service-providers/ip-packet-router",
"service-providers/network-requester",
@@ -275,11 +277,11 @@ tar = "0.4.40"
tempfile = "3.5.0"
thiserror = "1.0.48"
time = "0.3.30"
tokio = "1.33.0"
tokio-stream = "0.1.14"
tokio-test = "0.4.2"
tokio = "1.39"
tokio-stream = "0.1.15"
tokio-test = "0.4.4"
tokio-tungstenite = { version = "0.20.1" }
tokio-util = "0.7.10"
tokio-util = "0.7.11"
toml = "0.8.14"
tower = "0.4.13"
tower-http = "0.5.2"
+1 -1
View File
@@ -52,7 +52,7 @@ References for developers:
You can chat to us in two places:
* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat)
* The various developer channels on [Discord](discord.gg/nymproject)
* The various developer channels on [Discord](https://nymtech.net/go/discord)
### Tokenomics & Rewards
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.36"
version = "1.1.38"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.36"
version = "1.1.38"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "nym-authenticator-requests"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
bincode = { workspace = true }
rand = { workspace = true }
serde = { workspace = true, features = ["derive"] }
nym-sphinx = { path = "../nymsphinx" }
nym-wireguard-types = { path = "../wireguard-types" }
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod v1;
pub const CURRENT_VERSION: u8 = 1;
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
bincode::DefaultOptions::new()
.with_big_endian()
.with_varint_encoding()
}
@@ -0,0 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
const VERSION: u8 = 1;
@@ -0,0 +1,84 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::Recipient;
use nym_wireguard_types::{GatewayClient, InitMessage, PeerPublicKey};
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::VERSION;
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthenticatorRequest {
pub version: u8,
pub data: AuthenticatorRequestData,
pub reply_to: Recipient,
pub request_id: u64,
}
impl AuthenticatorRequest {
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn new_initial_request(init_message: InitMessage, reply_to: Recipient) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: VERSION,
data: AuthenticatorRequestData::Initial(init_message),
reply_to,
request_id,
},
request_id,
)
}
pub fn new_final_request(gateway_client: GatewayClient, reply_to: Recipient) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: VERSION,
data: AuthenticatorRequestData::Final(gateway_client),
reply_to,
request_id,
},
request_id,
)
}
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: VERSION,
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
reply_to,
request_id,
},
request_id,
)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AuthenticatorRequestData {
Initial(InitMessage),
Final(GatewayClient),
QueryBandwidth(PeerPublicKey),
}
@@ -0,0 +1,119 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::Recipient;
use nym_wireguard_types::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthenticatorResponse {
pub version: u8,
pub data: AuthenticatorResponseData,
pub reply_to: Recipient,
}
impl AuthenticatorResponse {
pub fn new_pending_registration_success(
registration_data: RegistrationData,
request_id: u64,
reply_to: Recipient,
) -> Self {
Self {
version: VERSION,
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
reply: registration_data,
reply_to,
request_id,
}),
reply_to,
}
}
pub fn new_registered(
registred_data: RegistredData,
reply_to: Recipient,
request_id: u64,
) -> Self {
Self {
version: VERSION,
data: AuthenticatorResponseData::Registered(RegisteredResponse {
reply: registred_data,
reply_to,
request_id,
}),
reply_to,
}
}
pub fn new_remaining_bandwidth(
remaining_bandwidth_data: Option<RemainingBandwidthData>,
reply_to: Recipient,
request_id: u64,
) -> Self {
Self {
version: VERSION,
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
reply: remaining_bandwidth_data,
reply_to,
request_id,
}),
reply_to,
}
}
pub fn recipient(&self) -> Recipient {
self.reply_to
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn id(&self) -> Option<u64> {
match &self.data {
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AuthenticatorResponseData {
PendingRegistration(PendingRegistrationResponse),
Registered(RegisteredResponse),
RemainingBandwidth(RemainingBandwidthResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PendingRegistrationResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: RegistrationData,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RegisteredResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RemainingBandwidthResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: Option<RemainingBandwidthData>,
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// See other comments for other TaskStatus message enumds about abusing the Error trait when we
// should have a new trait for TaskStatus messages
#[derive(Debug, thiserror::Error)]
pub enum BandwidthStatusMessage {
#[error("remaining bandwidth: {0}")]
RemainingBandwidth(i64),
#[error("no bandwidth left")]
NoBandwidth,
}
+3
View File
@@ -14,8 +14,11 @@ use nym_validator_client::coconut::all_coconut_api_clients;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
pub use event::BandwidthStatusMessage;
pub mod acquire;
pub mod error;
mod event;
mod utils;
#[derive(Debug)]
+15 -12
View File
@@ -11,7 +11,7 @@ use crate::traits::GatewayPacketRouter;
use crate::{cleanup_socket_message, try_decrypt_binary_message};
use futures::{SinkExt, StreamExt};
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_bandwidth_controller::{BandwidthController, BandwidthStatusMessage};
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_credentials::CredentialSpendingData;
@@ -105,8 +105,8 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
// currently unused (but populated)
negotiated_protocol: Option<u8>,
/// Listen to shutdown messages.
shutdown: TaskClient,
/// Listen to shutdown messages and send notifications back to the task manager
task_client: TaskClient,
}
impl<C, St> GatewayClient<C, St> {
@@ -117,7 +117,7 @@ impl<C, St> GatewayClient<C, St> {
shared_key: Option<Arc<SharedKeys>>,
packet_router: PacketRouter,
bandwidth_controller: Option<BandwidthController<C, St>>,
shutdown: TaskClient,
task_client: TaskClient,
) -> Self {
GatewayClient {
authenticated: false,
@@ -135,7 +135,7 @@ impl<C, St> GatewayClient<C, St> {
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
negotiated_protocol: None,
shutdown,
task_client,
}
}
@@ -299,7 +299,7 @@ impl<C, St> GatewayClient<C, St> {
loop {
tokio::select! {
_ = self.shutdown.recv() => {
_ = self.task_client.recv() => {
log::trace!("GatewayClient control response: Received shutdown");
log::debug!("GatewayClient control response: Exiting");
break Err(GatewayClientError::ConnectionClosedGatewayShutdown);
@@ -540,6 +540,9 @@ impl<C, St> GatewayClient<C, St> {
self.bandwidth_remaining = bandwidth_remaining;
self.negotiated_protocol = protocol_version;
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
self.task_client.send_status_msg(Box::new(
BandwidthStatusMessage::RemainingBandwidth(bandwidth_remaining),
));
Ok(())
}
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
@@ -805,7 +808,7 @@ impl<C, St> GatewayClient<C, St> {
.as_ref()
.expect("no shared key present even though we're authenticated!"),
),
self.shutdown.clone(),
self.task_client.clone(),
)
}
_ => unreachable!(),
@@ -879,8 +882,8 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
// perfectly fine here, because it's not meant to be used
let (ack_tx, _) = mpsc::unbounded();
let (mix_tx, _) = mpsc::unbounded();
let shutdown = TaskClient::dummy();
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
let task_client = TaskClient::dummy();
let packet_router = PacketRouter::new(ack_tx, mix_tx, task_client.clone());
GatewayClient {
authenticated: false,
@@ -898,7 +901,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
negotiated_protocol: None,
shutdown,
task_client,
}
}
@@ -906,7 +909,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
self,
packet_router: PacketRouter,
bandwidth_controller: Option<BandwidthController<C, St>>,
shutdown: TaskClient,
task_client: TaskClient,
) -> GatewayClient<C, St> {
// invariants that can't be broken
// (unless somebody decided to expose some field that wasn't meant to be exposed)
@@ -930,7 +933,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
reconnection_attempts: self.reconnection_attempts,
reconnection_backoff: self.reconnection_backoff,
negotiated_protocol: self.negotiated_protocol,
shutdown,
task_client,
}
}
}
@@ -683,6 +683,24 @@ pub trait MixnetSigningClient {
.await
}
async fn migrate_vested_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::MigrateVestedMixNode {}, vec![])
.await
}
async fn migrate_vested_delegation(
&self,
mix_id: MixId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::MigrateVestedDelegation { mix_id },
vec![],
)
.await
}
#[cfg(feature = "contract-testing")]
async fn testing_resolve_all_pending_events(
&self,
@@ -928,6 +946,12 @@ mod tests {
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => client
.withdraw_delegator_reward_on_behalf(owner.parse().unwrap(), mix_id, None)
.ignore(),
MixnetExecuteMsg::MigrateVestedMixNode { .. } => {
client.migrate_vested_mixnode(None).ignore()
}
MixnetExecuteMsg::MigrateVestedDelegation { mix_id } => {
client.migrate_vested_delegation(mix_id, None).ignore()
}
#[cfg(feature = "contract-testing")]
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
@@ -437,6 +437,7 @@ where
mod tests {
use super::*;
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
use nym_vesting_contract_common::ExecuteMsg;
// it's enough that this compiles and clippy is happy about it
#[allow(dead_code)]
@@ -560,6 +561,9 @@ mod tests {
VestingExecuteMsg::UpdateLockedPledgeCap { address, cap } => client
.update_locked_pledge_cap(address.parse().unwrap(), cap, None)
.ignore(),
// those will never be manually called by clients
ExecuteMsg::TrackMigratedMixnode { .. } => "explicitly_ignored".ignore(),
ExecuteMsg::TrackMigratedDelegation { .. } => "explicitly_ignored".ignore(),
};
}
}
@@ -3,7 +3,7 @@
use crate::nyxd::cosmwasm_client::client_traits::CosmWasmClient;
use crate::nyxd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
use crate::nyxd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nyxd::cosmwasm_client::logs::parse_raw_logs;
use crate::nyxd::cosmwasm_client::types::*;
use crate::nyxd::error::NyxdError;
use crate::nyxd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
@@ -19,6 +19,7 @@ use cosmrs::feegrant::{
};
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tendermint::abci::{Event, EventAttribute};
use cosmrs::tx::{self, Msg};
use cosmrs::{cosmwasm, AccountId, Any, Tx};
use log::debug;
@@ -51,6 +52,20 @@ fn single_unspecified_signer_auth(
}
.auth_info(empty_fee())
}
// Searches in events for an event of the given event type which contains an
// attribute for with the given key.
fn find_attribute<'a>(
events: &'a [Event],
event_type: &str,
attr_key: &str,
) -> Option<&'a EventAttribute> {
events
.iter()
.find(|attr| attr.kind == event_type)?
.attributes
.iter()
.find(|attr| attr.key == attr_key)
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
@@ -118,6 +133,7 @@ where
.check_response()?;
let logs = parse_raw_logs(tx_res.tx_result.log)?;
let events = tx_res.tx_result.events;
let gas_info = GasInfo {
gas_wanted: tx_res.tx_result.gas_wanted.try_into().unwrap_or_default(),
gas_used: tx_res.tx_result.gas_used.try_into().unwrap_or_default(),
@@ -127,7 +143,7 @@ where
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or code_id is not a number, there's no way we can recover, we're probably connected
// to wrong validator or something
let code_id = logs::find_attribute(&logs, "store_code", "code_id")
let code_id = find_attribute(&events, "store_code", "code_id")
.unwrap()
.value
.parse()
@@ -140,6 +156,7 @@ where
compressed_checksum,
code_id,
logs,
events,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -182,6 +199,7 @@ where
.check_response()?;
let logs = parse_raw_logs(tx_res.tx_result.log)?;
let events = tx_res.tx_result.events;
let gas_info = GasInfo {
gas_wanted: tx_res.tx_result.gas_wanted.try_into().unwrap_or_default(),
gas_used: tx_res.tx_result.gas_used.try_into().unwrap_or_default(),
@@ -190,7 +208,7 @@ where
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or address is malformed, there's no way we can recover, we're probably connected
// to wrong validator or something
let contract_address = logs::find_attribute(&logs, "instantiate", "_contract_address")
let contract_address = find_attribute(&events, "instantiate", "_contract_address")
.unwrap()
.value
.parse()
@@ -199,6 +217,7 @@ where
Ok(InstantiateResult {
contract_address,
logs,
events,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -231,6 +250,7 @@ where
};
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
events: tx_res.tx_result.events,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -261,6 +281,7 @@ where
};
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
events: tx_res.tx_result.events,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -298,6 +319,7 @@ where
};
Ok(MigrateResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
events: tx_res.tx_result.events,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -335,6 +357,7 @@ where
};
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
events: tx_res.tx_result.events,
data: tx_res.tx_result.data.into(),
transaction_hash: tx_res.hash,
gas_info,
@@ -378,6 +401,7 @@ where
};
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.tx_result.log)?,
events: tx_res.tx_result.events,
data: tx_res.tx_result.data.into(),
transaction_hash: tx_res.hash,
gas_info,
@@ -9,16 +9,12 @@ pub use nym_coconut_bandwidth_contract_common::event_attributes::*;
pub use nym_coconut_dkg_common::event_attributes::*;
// it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response)
// as theirs logs
// as their logs
#[derive(Debug, Serialize, Deserialize)]
pub struct Log {
#[serde(default)]
// weird thing is that the first msg_index seems to always be undefined on the raw logs
pub msg_index: usize,
// unless I'm missing something obvious, the "log" type in cosmjs is always an empty string
// and launchpad cosmos validator was setting it to what essentially is just the raw version of what
// we received (and we don't care about launchpad, we, as the time of writing this, work on the stargate)
// log: String,
pub events: Vec<cosmwasm_std::Event>,
}
@@ -37,8 +33,13 @@ pub fn find_attribute<'a>(
.find(|attr| attr.key == attribute_key)
}
// those two functions were separated so that the internal logic could actually be tested
// these two functions were separated so that the internal logic could actually be tested
fn parse_raw_str_logs(raw: &str) -> Result<Vec<Log>, NyxdError> {
// From Cosmos SDK > 0.50 onwards, log field is not populated
if raw.is_empty() {
return Ok(Vec::new());
}
let logs: Vec<Log> = serde_json::from_str(raw).map_err(|_| NyxdError::MalformedLogString)?;
if logs.len() != logs.iter().unique_by(|log| log.msg_index).count() {
// this check is only here because I don't yet fully understand raw log string generation and
@@ -1,6 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TEMPORARY WORKAROUND:
// those features are expected as the below should only get activated whenever
// the corresponding features in tendermint-rpc are enabled transitively
#![allow(unexpected_cfgs)]
use crate::nyxd::cosmwasm_client::client_traits::SigningCosmWasmClient;
use crate::nyxd::error::NyxdError;
use crate::nyxd::{Config, GasPrice, Hash, Height};
@@ -232,6 +232,8 @@ pub struct UploadResult {
pub logs: Vec<Log>,
pub events: Vec<abci::Event>,
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: Hash,
@@ -269,6 +271,8 @@ pub struct InstantiateResult {
pub logs: Vec<Log>,
pub events: Vec<abci::Event>,
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: Hash,
@@ -279,6 +283,8 @@ pub struct InstantiateResult {
pub struct ChangeAdminResult {
pub logs: Vec<Log>,
pub events: Vec<abci::Event>,
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: Hash,
@@ -289,6 +295,8 @@ pub struct ChangeAdminResult {
pub struct MigrateResult {
pub logs: Vec<Log>,
pub events: Vec<abci::Event>,
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: Hash,
@@ -301,6 +309,8 @@ pub struct ExecuteResult {
pub data: Vec<u8>,
pub events: Vec<abci::Event>,
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: Hash,
@@ -1,6 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TEMPORARY WORKAROUND:
// those features are expected as the below should only get activated whenever
// the corresponding features in tendermint-rpc are enabled transitively
#![allow(unexpected_cfgs)]
use crate::nyxd::contract_traits::{NymContractsProvider, TypedNymContracts};
use crate::nyxd::cosmwasm_client::types::{
ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult,
@@ -1,6 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TEMPORARY WORKAROUND:
// those features are expected as the below should only get activated whenever
// the corresponding features in tendermint-rpc are enabled transitively
#![allow(unexpected_cfgs)]
use async_trait::async_trait;
use cosmrs::tendermint::{self, abci, block::Height, evidence::Evidence, Genesis, Hash};
use serde::{de::DeserializeOwned, Serialize};
@@ -1,15 +1,26 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use log::{debug, info};
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent};
use nym_validator_client::nyxd::AccountId;
use log::{debug, info};
use nym_mixnet_contract_common::{
InitialRewardingParams, InstantiateMsg, OperatingCostRange, Percent, ProfitMarginRange,
};
use nym_network_defaults::mainnet::MIX_DENOM;
use nym_network_defaults::TOTAL_SUPPLY;
use nym_validator_client::nyxd::{AccountId, Coin};
use std::str::FromStr;
use std::time::Duration;
pub fn default_maximum_operating_cost() -> Coin {
Coin::new(TOTAL_SUPPLY, MIX_DENOM.base)
}
pub fn default_minimum_operating_cost() -> Coin {
Coin::new(0, MIX_DENOM.base)
}
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
@@ -50,6 +61,18 @@ pub struct Args {
#[clap(long, default_value_t = 240)]
pub active_set_size: u32,
#[clap(long, default_value_t = Percent::zero())]
pub minimum_profit_margin_percent: Percent,
#[clap(long, default_value_t = Percent::hundred())]
pub maximum_profit_margin_percent: Percent,
#[clap(long, default_value_t = default_minimum_operating_cost())]
pub minimum_interval_operating_cost: Coin,
#[clap(long, default_value_t = default_maximum_operating_cost())]
pub maximum_interval_operating_cost: Coin,
}
pub async fn generate(args: Args) {
@@ -97,6 +120,10 @@ pub async fn generate(args: Args) {
.expect("Rewarding (mix) denom has to be set")
});
if args.minimum_interval_operating_cost.denom != args.maximum_interval_operating_cost.denom {
panic!("different denoms for operating cost bounds")
}
let instantiate_msg = InstantiateMsg {
rewarding_validator_address: rewarding_validator_address.to_string(),
vesting_contract_address: vesting_contract_address.to_string(),
@@ -104,6 +131,14 @@ pub async fn generate(args: Args) {
epochs_in_interval: args.epochs_in_interval,
epoch_duration: Duration::from_secs(args.epoch_duration),
initial_rewarding_params,
profit_margin: ProfitMarginRange {
minimum: args.minimum_profit_margin_percent,
maximum: args.maximum_profit_margin_percent,
},
interval_operating_cost: OperatingCostRange {
minimum: args.minimum_interval_operating_cost.amount.into(),
maximum: args.maximum_interval_operating_cost.amount.into(),
},
};
debug!("instantiate_msg: {:?}", instantiate_msg);
@@ -0,0 +1,42 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::MixId;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn migrate_vested_delegation(args: Args, client: SigningClient) {
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.mixnode_details
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.migrate_vested_delegation(mix_id, None)
.await
.expect("failed to migrate delegation!");
info!("migration result: {:?}", res)
}
@@ -7,6 +7,7 @@ pub mod rewards;
pub mod delegate_to_mixnode;
pub mod delegate_to_multiple_mixnodes;
pub mod migrate_vested_delegation;
pub mod query_for_delegations;
pub mod undelegate_from_mixnode;
pub mod vesting_delegate_to_mixnode;
@@ -35,4 +36,6 @@ pub enum MixnetDelegatorsCommands {
DelegateVesting(vesting_delegate_to_mixnode::Args),
/// Undelegate from a mixnode (when originally using locked tokens)
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
/// Migrate the delegation to use liquid tokens
MigrateVestedDelegation(migrate_vested_delegation::Args),
}
@@ -96,6 +96,7 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
mix_id,
amount,
proxy,
..
} => {
if owner.as_str() == client.nyxd.address().as_ref() {
table.add_row(vec![
@@ -111,6 +112,7 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
owner,
mix_id,
proxy,
..
} => {
if owner.as_str() == client.nyxd.address().as_ref() {
table.add_row(vec![
@@ -8,7 +8,7 @@ use cosmwasm_std::Coin;
use nym_bin_common::output_format::OutputFormat;
use nym_mixnet_contract_common::construct_gateway_bonding_sign_payload;
use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -39,10 +39,6 @@ pub struct Args {
)]
pub amount: u128,
/// Indicates whether the gateway is going to get bonded via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -74,15 +70,8 @@ pub async fn create_payload(args: Args, client: SigningClient) {
};
let address = account_id_to_cw_addr(&client.address());
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(
client.vesting_contract_address().unwrap(),
))
} else {
None
};
let payload = construct_gateway_bonding_sign_payload(nonce, address, proxy, coin, gateway);
let payload = construct_gateway_bonding_sign_payload(nonce, address, coin, gateway);
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -5,33 +5,21 @@ use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// Label that is going to be used for creating the family
#[arg(long)]
pub family_label: String,
/// Indicates whether the family is going to get created via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn create_family(args: Args, client: SigningClient) {
info!("Create family");
let res = if args.with_vesting_account {
client
.vesting_create_family(args.family_label, None)
.await
.expect("failed to create family with vesting account")
} else {
client
.create_family(args.family_label, None)
.await
.expect("failed to create family")
};
let res = client
.create_family(args.family_label, None)
.await
.expect("failed to create family");
info!("Family creation result: {:?}", res);
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::context::QueryClient;
use crate::utils::{account_id_to_cw_addr, DataWrapper};
use crate::utils::DataWrapper;
use clap::Parser;
use cosmrs::AccountId;
use log::info;
@@ -10,7 +10,7 @@ use nym_bin_common::output_format::OutputFormat;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::construct_family_join_permit;
use nym_mixnet_contract_common::families::FamilyHead;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -18,10 +18,6 @@ pub struct Args {
#[arg(long)]
pub address: AccountId,
/// Indicates whether the member joining the family is going to use the vesting account for joining.
#[arg(long)]
pub with_vesting_account: bool,
// might as well validate the value when parsing the arguments
/// Identity of the member for whom we're issuing the permit
#[arg(long)]
@@ -68,18 +64,9 @@ pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryCli
}
};
// let address = account_id_to_cw_addr(&args.address);
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(
client.vesting_contract_address().unwrap(),
))
} else {
None
};
let head = FamilyHead::new(mixnode.bond_information.identity());
let payload = construct_family_join_permit(nonce, head, proxy, args.member.to_base58_string());
let payload = construct_family_join_permit(nonce, head, args.member.to_base58_string());
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -8,7 +8,6 @@ use nym_contracts_common::signing::MessageSignature;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::families::FamilyHead;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -16,10 +15,6 @@ pub struct Args {
#[arg(long)]
pub family_head: identity::PublicKey,
/// Indicates whether the member joining the family is going to do so via the vesting contract
#[arg(long)]
pub with_vesting_account: bool,
/// Permission, as provided by the family head, for joining the family
#[arg(long)]
pub join_permit: MessageSignature,
@@ -30,17 +25,10 @@ pub async fn join_family(args: Args, client: SigningClient) {
let family_head = FamilyHead::new(args.family_head.to_base58_string());
let res = if args.with_vesting_account {
client
.vesting_join_family(args.join_permit, family_head, None)
.await
.expect("failed to join family with vesting account")
} else {
client
.join_family(args.join_permit, family_head, None)
.await
.expect("failed to join family")
};
let res = client
.join_family(args.join_permit, family_head, None)
.await
.expect("failed to join family");
info!("Family join result: {:?}", res);
}
@@ -7,17 +7,12 @@ use log::info;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::families::FamilyHead;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// The head of the family that we intend to leave
#[arg(long)]
pub family_head: identity::PublicKey,
/// Indicates whether we joined the family via the vesting contract
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn leave_family(args: Args, client: SigningClient) {
@@ -25,17 +20,10 @@ pub async fn leave_family(args: Args, client: SigningClient) {
let family_head = FamilyHead::new(args.family_head.to_base58_string());
let res = if args.with_vesting_account {
client
.vesting_leave_family(family_head, None)
.await
.expect("failed to leave family with vesting account")
} else {
client
.leave_family(family_head, None)
.await
.expect("failed to leave family")
};
let res = client
.leave_family(family_head, None)
.await
.expect("failed to leave family");
info!("Family leave result: {:?}", res);
}
@@ -0,0 +1,19 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
pub async fn migrate_vested_mixnode(_args: Args, client: SigningClient) {
let res = client
.migrate_vested_mixnode(None)
.await
.expect("failed to migrate mixnode!");
info!("migration result: {:?}", res)
}
@@ -11,7 +11,7 @@ use nym_mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNode
use nym_network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
use nym_validator_client::nyxd::CosmWasmCoin;
#[derive(Debug, Parser)]
@@ -52,10 +52,6 @@ pub struct Args {
)]
pub amount: u128,
/// Indicates whether the mixnode is going to get bonded via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -100,16 +96,9 @@ pub async fn create_payload(args: Args, client: SigningClient) {
};
let address = account_id_to_cw_addr(&client.address());
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(
client.vesting_contract_address().unwrap(),
))
} else {
None
};
let payload =
construct_mixnode_bonding_sign_payload(nonce, address, proxy, coin, mixnode, cost_params);
construct_mixnode_bonding_sign_payload(nonce, address, coin, mixnode, cost_params);
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -7,6 +7,7 @@ pub mod bond_mixnode;
pub mod decrease_pledge;
pub mod families;
pub mod keys;
pub mod migrate_vested_mixnode;
pub mod mixnode_bonding_sign_payload;
pub mod pledge_more;
pub mod rewards;
@@ -52,4 +53,6 @@ pub enum MixnetOperatorsMixnodeCommands {
DecreasePledge(decrease_pledge::Args),
/// Decrease pledge with locked tokens
DecreasePledgeVesting(vesting_decrease_pledge::Args),
/// Migrate the mixnode to use liquid tokens
MigrateVestedNode(migrate_vested_mixnode::Args),
}
@@ -218,7 +218,6 @@ where
#[derive(Serialize)]
pub struct ContractMessageContent<T> {
pub sender: Addr,
pub proxy: Option<Addr>,
pub funds: Vec<Coin>,
pub data: T,
}
@@ -233,25 +232,17 @@ where
}
impl<T> ContractMessageContent<T> {
pub fn new(sender: Addr, proxy: Option<Addr>, funds: Vec<Coin>, data: T) -> Self {
pub fn new(sender: Addr, funds: Vec<Coin>, data: T) -> Self {
ContractMessageContent {
sender,
proxy,
funds,
data,
}
}
pub fn new_with_info(info: MessageInfo, signer: Addr, data: T) -> Self {
let proxy = if info.sender == signer {
None
} else {
Some(info.sender)
};
ContractMessageContent {
sender: signer,
proxy,
funds: info.funds,
data,
}
@@ -65,7 +65,6 @@ impl Delegation {
cumulative_reward_ratio: Decimal,
amount: Coin,
height: u64,
proxy: Option<Addr>,
) -> Self {
assert!(
amount.amount <= TOKEN_SUPPLY,
@@ -78,7 +77,7 @@ impl Delegation {
cumulative_reward_ratio,
amount,
height,
proxy,
proxy: None,
}
}
@@ -1,8 +1,9 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{EpochEventId, EpochState, IdentityKey, MixId};
use crate::{EpochEventId, EpochState, IdentityKey, MixId, OperatingCostRange, ProfitMarginRange};
use contracts_common::signing::verifier::ApiVerifierError;
use contracts_common::Percent;
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use thiserror::Error;
@@ -76,21 +77,11 @@ pub enum MixnetContractError {
#[error("Received multiple coin types during staking")]
MultipleDenoms,
#[error("Proxy address mismatch, expected {existing}, got {incoming}")]
ProxyMismatch { existing: String, incoming: String },
#[error("Proxy address ({received}) is not set to the vesting contract ({vesting_contract})")]
ProxyIsNotVestingContract {
received: Addr,
vesting_contract: Addr,
},
#[error(
"Sender of this message ({received}) is not the vesting contract ({vesting_contract})"
)]
SenderIsNotVestingContract {
received: Addr,
vesting_contract: Addr,
},
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
@@ -239,6 +230,30 @@ pub enum MixnetContractError {
#[from]
source: ApiVerifierError,
},
#[error("this operation is no longer allowed to be performed with vesting tokens. please move them to your liquid balance and try again")]
DisabledVestingOperation,
#[error(
"this mixnode has not been bonded with the vesting tokens or has already been migrated"
)]
NotAVestingMixnode,
#[error("this delegation has not been performed with the vesting tokens or has already been migrated")]
NotAVestingDelegation,
#[error("the provided profit margin ({provided}) is outside the allowed range: {range}")]
ProfitMarginOutsideRange {
provided: Percent,
range: ProfitMarginRange,
},
#[error("the provided interval operating cost ({provided}{denom}) is outside the allowed range: {range}")]
OperatingCostOutsideRange {
denom: String,
provided: Uint128,
range: OperatingCostRange,
},
}
impl MixnetContractError {
@@ -103,7 +103,6 @@ impl Display for MixnetEventType {
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
pub const AMOUNT_KEY: &str = "amount";
pub const PROXY_KEY: &str = "proxy";
// event-specific attributes
@@ -163,7 +162,6 @@ pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval";
pub fn new_delegation_event(
created_at: BlockHeight,
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_id: MixId,
unit_reward: Decimal,
@@ -171,58 +169,34 @@ pub fn new_delegation_event(
Event::new(MixnetEventType::Delegation)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
.add_attribute(UNIT_REWARD_KEY, unit_reward.to_string())
}
pub fn new_delegation_on_unbonded_node_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_id: MixId,
) -> Event {
pub fn new_delegation_on_unbonded_node_event(delegator: &Addr, mix_id: MixId) -> Event {
Event::new(MixnetEventType::Delegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_pending_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_id: MixId,
) -> Event {
pub fn new_pending_delegation_event(delegator: &Addr, amount: &Coin, mix_id: MixId) -> Event {
Event::new(MixnetEventType::PendingDelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_withdraw_operator_reward_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: Coin,
mix_id: MixId,
) -> Event {
pub fn new_withdraw_operator_reward_event(owner: &Addr, amount: Coin, mix_id: MixId) -> Event {
Event::new(MixnetEventType::WithdrawOperatorReward)
.add_attribute(OWNER_KEY, owner.as_str())
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_withdraw_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Coin,
mix_id: MixId,
) -> Event {
pub fn new_withdraw_delegator_reward_event(delegator: &Addr, amount: Coin, mix_id: MixId) -> Event {
Event::new(MixnetEventType::WithdrawDelegatorReward)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
@@ -278,59 +252,43 @@ pub fn new_pending_rewarding_params_update_event(
)
}
pub fn new_undelegation_event(
created_at: BlockHeight,
delegator: &Addr,
proxy: &Option<Addr>,
mix_id: MixId,
) -> Event {
pub fn new_undelegation_event(created_at: BlockHeight, delegator: &Addr, mix_id: MixId) -> Event {
Event::new(MixnetEventType::Undelegation)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_pending_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_id: MixId,
) -> Event {
pub fn new_pending_undelegation_event(delegator: &Addr, mix_id: MixId) -> Event {
Event::new(MixnetEventType::PendingUndelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_gateway_bonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::GatewayBonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_gateway_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::GatewayUnbonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_bonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
@@ -341,7 +299,6 @@ pub fn new_mixnode_bonding_event(
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(ASSIGNED_LAYER_KEY, assigned_layer)
.add_attribute(AMOUNT_KEY, amount.to_string())
}
@@ -380,7 +337,6 @@ pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Ev
pub fn new_pending_mixnode_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Event {
@@ -388,43 +344,33 @@ pub fn new_pending_mixnode_unbonding_event(
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
}
pub fn new_mixnode_config_update_event(
mix_id: MixId,
owner: &Addr,
proxy: &Option<Addr>,
update: &MixNodeConfigUpdate,
) -> Event {
Event::new(MixnetEventType::MixnodeConfigUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
}
pub fn new_gateway_config_update_event(
owner: &Addr,
proxy: &Option<Addr>,
update: &GatewayConfigUpdate,
) -> Event {
pub fn new_gateway_config_update_event(owner: &Addr, update: &GatewayConfigUpdate) -> Event {
Event::new(MixnetEventType::GatewayConfigUpdate)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_GATEWAY_CONFIG_KEY, update.to_inline_json())
}
pub fn new_mixnode_pending_cost_params_update_event(
mix_id: MixId,
owner: &Addr,
proxy: &Option<Addr>,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
@@ -3,7 +3,6 @@
use crate::{IdentityKey, IdentityKeyRef};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
@@ -84,10 +83,10 @@ impl FamilyHead {
}
impl Family {
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: String) -> Self {
pub fn new(head: FamilyHead, label: String) -> Self {
Family {
head,
proxy: proxy.map(|p| p.to_string()),
proxy: None,
label,
}
}
@@ -55,19 +55,13 @@ pub struct GatewayBond {
}
impl GatewayBond {
pub fn new(
pledge_amount: Coin,
owner: Addr,
block_height: u64,
gateway: Gateway,
proxy: Option<Addr>,
) -> Self {
pub fn new(pledge_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
GatewayBond {
pledge_amount,
owner,
block_height,
gateway,
proxy,
proxy: None,
}
}
@@ -10,7 +10,10 @@ use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use crate::{
Delegation, EpochEventId, EpochId, IdentityKey, MixId, OperatingCostRange, Percent,
ProfitMarginRange, SphinxKey,
};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use schemars::JsonSchema;
@@ -152,6 +155,16 @@ impl MixNodeRewarding {
})
}
pub fn normalise_profit_margin(&mut self, allowed_range: ProfitMarginRange) {
self.cost_params.profit_margin_percent =
allowed_range.normalise(self.cost_params.profit_margin_percent)
}
pub fn normalise_operating_cost(&mut self, allowed_range: OperatingCostRange) {
self.cost_params.interval_operating_cost.amount =
allowed_range.normalise(self.cost_params.interval_operating_cost.amount)
}
/// Determines whether this node is still bonded. This is performed via a simple check,
/// if there are no tokens left associated with the operator, it means they have unbonded
/// and those params only exist for the purposes of calculating rewards for delegators that
@@ -518,7 +531,6 @@ impl MixNodeBond {
original_pledge: Coin,
layer: Layer,
mix_node: MixNode,
proxy: Option<Addr>,
bonding_height: u64,
) -> Self {
MixNodeBond {
@@ -527,7 +539,7 @@ impl MixNodeBond {
original_pledge,
layer,
mix_node,
proxy,
proxy: None,
bonding_height,
is_unbonding: false,
}
@@ -1,4 +1,4 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::delegation::{self, OwnerProxySubKey};
@@ -12,6 +12,7 @@ use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
};
use crate::types::{ContractStateParams, LayerAssignment, MixId};
use crate::{OperatingCostRange, ProfitMarginRange};
use contracts_common::{signing::MessageSignature, IdentityKey, Percent};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, Decimal};
@@ -57,6 +58,12 @@ pub struct InstantiateMsg {
pub epochs_in_interval: u32,
pub epoch_duration: Duration,
pub initial_rewarding_params: InitialRewardingParams,
#[serde(default)]
pub profit_margin: ProfitMarginRange,
#[serde(default)]
pub interval_operating_cost: OperatingCostRange,
}
#[cw_serde]
@@ -269,6 +276,12 @@ pub enum ExecuteMsg {
owner: String,
},
// vesting migration:
MigrateVestedMixNode {},
MigrateVestedDelegation {
mix_id: MixId,
},
// testing-only
#[cfg(feature = "contract-testing")]
TestingResolveAllPendingEvents {
@@ -381,6 +394,9 @@ impl ExecuteMsg {
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => {
format!("withdrawing delegator reward from mixnode {mix_id} on behalf")
}
ExecuteMsg::MigrateVestedMixNode { .. } => "migrate vested mixnode".into(),
ExecuteMsg::MigrateVestedDelegation { .. } => "migrate vested delegation".to_string(),
#[cfg(feature = "contract-testing")]
ExecuteMsg::TestingResolveAllPendingEvents { .. } => {
"resolving all pending events".into()
@@ -38,6 +38,7 @@ pub enum PendingEpochEventKind {
/// Request to create a delegation towards particular mixnode.
/// Note that if such delegation already exists, it will get updated with the provided token amount.
#[serde(alias = "Delegate")]
#[non_exhaustive]
Delegate {
/// The address of the owner of the delegation.
owner: Addr,
@@ -55,6 +56,7 @@ pub enum PendingEpochEventKind {
/// Request to remove delegation from particular mixnode.
#[serde(alias = "Undelegate")]
#[non_exhaustive]
Undelegate {
/// The address of the owner of the delegation.
owner: Addr,
@@ -109,6 +111,23 @@ impl PendingEpochEventKind {
kind: self,
}
}
pub fn new_delegate(owner: Addr, mix_id: MixId, amount: Coin) -> Self {
PendingEpochEventKind::Delegate {
owner,
mix_id,
amount,
proxy: None,
}
}
pub fn new_undelegate(owner: Addr, mix_id: MixId) -> Self {
PendingEpochEventKind::Undelegate {
owner,
mix_id,
proxy: None,
}
}
}
impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
@@ -47,7 +47,6 @@ impl SimulatedNode {
self.rewarding_details.total_unit_reward,
delegation,
42,
None,
);
self.delegations.insert(delegator, delegation);
@@ -37,13 +37,12 @@ impl SigningPurpose for MixnodeBondingPayload {
pub fn construct_mixnode_bonding_sign_payload(
nonce: Nonce,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
mix_node: MixNode,
cost_params: MixNodeCostParams,
) -> SignableMixNodeBondingMsg {
let payload = MixnodeBondingPayload::new(mix_node, cost_params);
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
let content = ContractMessageContent::new(sender, vec![pledge], payload);
SignableMessage::new(nonce, content)
}
@@ -68,12 +67,11 @@ impl SigningPurpose for GatewayBondingPayload {
pub fn construct_gateway_bonding_sign_payload(
nonce: Nonce,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
gateway: Gateway,
) -> SignableGatewayBondingMsg {
let payload = GatewayBondingPayload::new(gateway);
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
let content = ContractMessageContent::new(sender, vec![pledge], payload);
SignableMessage::new(nonce, content)
}
@@ -82,17 +80,14 @@ pub fn construct_gateway_bonding_sign_payload(
pub struct FamilyJoinPermit {
// the granter of this permit
family_head: FamilyHead,
// whether the **member** will want to join via the proxy (i.e. vesting contract)
proxy: Option<Addr>,
// the actual member we want to permit to join
member_node: IdentityKey,
}
impl FamilyJoinPermit {
pub fn new(family_head: FamilyHead, proxy: Option<Addr>, member_node: IdentityKey) -> Self {
pub fn new(family_head: FamilyHead, member_node: IdentityKey) -> Self {
Self {
family_head,
proxy,
member_node,
}
}
@@ -107,10 +102,9 @@ impl SigningPurpose for FamilyJoinPermit {
pub fn construct_family_join_permit(
nonce: Nonce,
family_head: FamilyHead,
proxy: Option<Addr>,
member_node: IdentityKey,
) -> SignableFamilyJoinPermitMsg {
let payload = FamilyJoinPermit::new(family_head, proxy, member_node);
let payload = FamilyJoinPermit::new(family_head, member_node);
// note: we're NOT wrapping it in `ContractMessageContent` because the family head is not going to be the one
// sending the message to the contract
@@ -3,9 +3,11 @@
use crate::error::MixnetContractError;
use crate::Layer;
use contracts_common::Percent;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
use cosmwasm_std::{Addr, Uint128};
use std::fmt::{Display, Formatter};
use std::ops::Index;
// type aliases for better reasoning about available data
@@ -15,6 +17,65 @@ pub type SphinxKeyRef<'a> = &'a str;
pub type MixId = u32;
pub type BlockHeight = u64;
#[cw_serde]
pub struct RangedValue<T> {
pub minimum: T,
pub maximum: T,
}
impl<T> Copy for RangedValue<T> where T: Copy {}
pub type ProfitMarginRange = RangedValue<Percent>;
pub type OperatingCostRange = RangedValue<Uint128>;
impl<T> Display for RangedValue<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} - {}", self.minimum, self.maximum)
}
}
impl Default for ProfitMarginRange {
fn default() -> Self {
ProfitMarginRange {
minimum: Percent::zero(),
maximum: Percent::hundred(),
}
}
}
impl Default for OperatingCostRange {
fn default() -> Self {
OperatingCostRange {
minimum: Uint128::zero(),
// 1 billion (native tokens, i.e. 1 billion * 1'000'000 base tokens) - the total supply
maximum: Uint128::new(1_000_000_000_000_000),
}
}
}
impl<T> RangedValue<T>
where
T: Copy + PartialOrd + PartialEq,
{
pub fn normalise(&self, value: T) -> T {
if value < self.minimum {
self.minimum
} else if value > self.maximum {
self.maximum
} else {
value
}
}
pub fn within_range(&self, value: T) -> bool {
value >= self.minimum && value <= self.maximum
}
}
/// Specifies layer assignment for the given mixnode.
#[cw_serde]
pub struct LayerAssignment {
@@ -154,4 +215,14 @@ pub struct ContractStateParams {
/// Minimum amount a gateway must pledge to get into the system.
pub minimum_gateway_pledge: Coin,
/// Defines the allowed profit margin range of operators.
/// default: 0% - 100%
#[serde(default)]
pub profit_margin: ProfitMarginRange,
/// Defines the allowed interval operating cost range of operators.
/// default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)
#[serde(default)]
pub interval_operating_cost: OperatingCostRange,
}
@@ -167,3 +167,11 @@ pub fn new_track_undelegation_event() -> Event {
pub fn new_track_reward_event() -> Event {
Event::new(TRACK_REWARD_EVENT_TYPE)
}
pub fn new_track_migrate_mixnode_event() -> Event {
Event::new("track_migrate_vesting_mixnode")
}
pub fn new_track_migrate_delegation_event() -> Event {
Event::new("track_migrate_vesting_delegation")
}
@@ -136,6 +136,14 @@ pub enum ExecuteMsg {
address: String,
cap: PledgeCap,
},
TrackMigratedMixnode {
owner: String,
},
// no need to track migrated gateways as there are no vesting gateways on mainnet
TrackMigratedDelegation {
owner: String,
mix_id: MixId,
},
}
impl ExecuteMsg {
@@ -171,6 +179,10 @@ impl ExecuteMsg {
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
ExecuteMsg::TrackMigratedMixnode { .. } => "VestingExecuteMsg::TrackMigratedMixnode",
ExecuteMsg::TrackMigratedDelegation { .. } => {
"VestingExecuteMsg::TrackMigratedDelegation"
}
}
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ bytes = { workspace = true }
nym-bin-common = { path = "../bin-common" }
nym-crypto = { path = "../crypto" }
nym-sphinx = { path = "../nymsphinx" }
rand = "0.8.5"
rand = { workspace = true }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
time = { workspace = true }
@@ -0,0 +1,69 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{v6, v7};
impl From<v7::response::StaticConnectFailureReason> for v6::response::StaticConnectFailureReason {
fn from(failure: v7::response::StaticConnectFailureReason) -> Self {
match failure {
v7::response::StaticConnectFailureReason::RequestedIpAlreadyInUse => {
v6::response::StaticConnectFailureReason::RequestedIpAlreadyInUse
}
v7::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse => {
v6::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse
}
v7::response::StaticConnectFailureReason::OutOfDateTimestamp => {
v6::response::StaticConnectFailureReason::Other("out of date timestamp".to_string())
}
v7::response::StaticConnectFailureReason::Other(reason) => {
v6::response::StaticConnectFailureReason::Other(reason)
}
}
}
}
impl From<v7::response::DynamicConnectFailureReason> for v6::response::DynamicConnectFailureReason {
fn from(failure: v7::response::DynamicConnectFailureReason) -> Self {
match failure {
v7::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse => {
v6::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse
}
v7::response::DynamicConnectFailureReason::NoAvailableIp => {
v6::response::DynamicConnectFailureReason::NoAvailableIp
}
v7::response::DynamicConnectFailureReason::Other(err) => {
v6::response::DynamicConnectFailureReason::Other(err)
}
}
}
}
impl From<v7::response::InfoResponseReply> for v6::response::InfoResponseReply {
fn from(reply: v7::response::InfoResponseReply) -> Self {
match reply {
v7::response::InfoResponseReply::Generic { msg } => {
v6::response::InfoResponseReply::Generic { msg }
}
v7::response::InfoResponseReply::VersionMismatch {
request_version,
response_version,
} => v6::response::InfoResponseReply::VersionMismatch {
request_version,
response_version,
},
v7::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
v6::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst }
}
}
}
}
impl From<v7::response::InfoLevel> for v6::response::InfoLevel {
fn from(level: v7::response::InfoLevel) -> Self {
match level {
v7::response::InfoLevel::Info => v6::response::InfoLevel::Info,
v7::response::InfoLevel::Warn => v6::response::InfoLevel::Warn,
v7::response::InfoLevel::Error => v6::response::InfoLevel::Error,
}
}
}
+1
View File
@@ -1,3 +1,4 @@
pub mod conversion;
pub mod request;
pub mod response;
@@ -198,6 +198,17 @@ impl IpPacketRequestData {
| IpPacketRequestData::Health(_) => None,
}
}
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
match self {
IpPacketRequestData::StaticConnect(request) => Some(request.request()),
IpPacketRequestData::DynamicConnect(request) => Some(request.request()),
IpPacketRequestData::Disconnect(request) => Some(request.request()),
IpPacketRequestData::Data(_) => None,
IpPacketRequestData::Ping(_) => None,
IpPacketRequestData::Health(_) => None,
}
}
}
// A static connect request is when the client provides the internal IP address it will use on the
+17
View File
@@ -42,6 +42,7 @@ pub struct NymNetworkDetails {
pub endpoints: Vec<ValidatorDetails>,
pub contracts: NymContracts,
pub explorer_api: Option<String>,
pub nym_vpn_api_url: Option<String>,
}
// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms
@@ -71,6 +72,7 @@ impl NymNetworkDetails {
endpoints: Default::default(),
contracts: Default::default(),
explorer_api: Default::default(),
nym_vpn_api_url: Default::default(),
}
}
@@ -126,6 +128,7 @@ impl NymNetworkDetails {
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
.with_explorer_api(get_optional_env(var_names::EXPLORER_API))
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
}
pub fn new_mainnet() -> Self {
@@ -155,6 +158,7 @@ impl NymNetworkDetails {
),
},
explorer_api: parse_optional_str(mainnet::EXPLORER_API),
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
}
}
@@ -263,6 +267,19 @@ impl NymNetworkDetails {
self.explorer_api = endpoint.map(Into::into);
self
}
#[must_use]
pub fn with_nym_vpn_api_url<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
self.nym_vpn_api_url = endpoint.map(Into::into);
self
}
pub fn nym_vpn_api_url(&self) -> Option<Url> {
self.nym_vpn_api_url.as_ref().map(|url| {
url.parse()
.expect("the provided nym-vpn api url is invalid!")
})
}
}
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
+2
View File
@@ -31,6 +31,7 @@ pub const NYXD_URL: &str = "https://rpc.nymtech.net";
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
pub const NYM_VPN_API: &str = "https://nymvpn.net/api/";
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
pub const EXIT_POLICY_URL: &str =
@@ -114,6 +115,7 @@ pub fn export_to_env() {
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
}
pub fn export_to_env_if_not_set() {
+1
View File
@@ -24,6 +24,7 @@ pub const NYM_API: &str = "NYM_API";
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
pub const EXPLORER_API: &str = "EXPLORER_API";
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
pub const NYM_VPN_API: &str = "NYM_VPN_API";
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
-1
View File
@@ -44,7 +44,6 @@ pub enum NymTopologyError {
PayloadBuilder,
#[error("Outfox: {0}")]
#[cfg(feature = "outfox")]
Outfox(#[from] nym_sphinx_types::OutfoxError),
#[error("{0}")]
@@ -7,6 +7,7 @@ use nym_sphinx_routing::SphinxRouteMaker;
use nym_sphinx_types::Node;
use rand::{CryptoRng, Rng};
#[allow(dead_code)]
pub struct NymTopologyRouteProvider<R> {
rng: R,
inner: NymTopology,
+4
View File
@@ -1,3 +1,4 @@
use nym_mixnet_contract_common::ContractsCommonError;
use nym_validator_client::error::TendermintRpcError;
use nym_validator_client::nym_api::error::NymAPIError;
use nym_validator_client::{nyxd::error::NyxdError, ValidatorClientError};
@@ -8,6 +9,9 @@ use thiserror::Error;
// TODO: ask @MS why this even exists
#[derive(Error, Debug)]
pub enum TypesError {
#[error(transparent)]
ContractsCommon(#[from] ContractsCommonError),
#[error("{source}")]
NyxdError {
#[from]
+2
View File
@@ -84,6 +84,7 @@ impl PendingEpochEventData {
mix_id,
amount,
proxy,
..
} => Ok(PendingEpochEventData::Delegate {
owner: owner.into_string(),
mix_id,
@@ -94,6 +95,7 @@ impl PendingEpochEventData {
owner,
mix_id,
proxy,
..
} => Ok(PendingEpochEventData::Undelegate {
owner: owner.into_string(),
mix_id,
+3 -1
View File
@@ -17,6 +17,8 @@ gloo-utils = { workspace = true }
gloo-net = { workspace = true, features = ["websocket"], optional = true }
#gloo-net = { path = "../../../../gloo/crates/net", features = ["websocket"], optional = true }
console_error_panic_hook = { workspace = true, optional = true }
# we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums
[dependencies.tungstenite]
workspace = true
@@ -28,7 +30,7 @@ workspace = true
optional = true
[features]
default = ["sleep"]
default = ["sleep", "console_error_panic_hook"]
sleep = ["web-sys", "web-sys/Window"]
websocket = [
"getrandom",
+1 -2
View File
@@ -10,8 +10,7 @@ pub use config::Config;
pub use error::Error;
pub use public_key::PeerPublicKey;
pub use registration::{
ClientMac, ClientMessage, ClientRegistrationResponse, GatewayClient, GatewayClientRegistry,
InitMessage, Nonce,
ClientMac, ClientMessage, GatewayClient, GatewayClientRegistry, InitMessage, Nonce,
};
#[cfg(feature = "verify")]
+19 -11
View File
@@ -7,6 +7,7 @@ use base64::{engine::general_purpose, Engine};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use std::time::SystemTime;
use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
@@ -18,13 +19,15 @@ use sha2::Sha256;
pub type GatewayClientRegistry = DashMap<PeerPublicKey, GatewayClient>;
pub type PendingRegistrations = DashMap<PeerPublicKey, RegistrationData>;
pub type PrivateIPs = DashMap<IpAddr, Free>;
pub type PrivateIPs = DashMap<IpAddr, Taken>;
#[cfg(feature = "verify")]
pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Free = bool;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "camelCase")]
@@ -32,6 +35,7 @@ pub type Free = bool;
pub enum ClientMessage {
Initial(InitMessage),
Final(GatewayClient),
Query(PeerPublicKey),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -53,21 +57,25 @@ impl InitMessage {
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "camelCase")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub enum ClientRegistrationResponse {
PendingRegistration(RegistrationData),
Registered,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct RegistrationData {
pub nonce: u64,
pub gateway_data: GatewayClient,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ip: IpAddr,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RemainingBandwidthData {
pub available_bandwidth: u64,
pub suspended: bool,
}
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
/// Gateway/Nym node can then verify pub_key payload using the same process
#[derive(Serialize, Deserialize, Debug, Clone)]
+2
View File
@@ -12,6 +12,7 @@ license.workspace = true
[dependencies]
base64 = { workspace = true }
chrono = { workspace = true }
dashmap = { workspace = true }
defguard_wireguard_rs = { workspace = true }
# The latest version on crates.io at the time of writing this (6.0.0) has a
@@ -24,5 +25,6 @@ nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-network-defaults = { path = "../network-defaults" }
nym-task = { path = "../task" }
nym-wireguard-types = { path = "../wireguard-types" }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
tokio-stream = { workspace = true }
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("peers in wireguard don't match with in-memory ")]
PeerMismatch,
#[error("{0}")]
Defguard(#[from] defguard_wireguard_rs::error::WireguardInterfaceError),
}
+25 -14
View File
@@ -1,3 +1,6 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
// #![warn(clippy::pedantic)]
// #![warn(clippy::expect_used)]
@@ -6,13 +9,14 @@
use dashmap::DashMap;
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi};
use nym_crypto::asymmetric::encryption::KeyPair;
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry};
use peer_controller::PeerControlMessage;
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry, PeerPublicKey};
use peer_controller::PeerControlRequest;
use std::sync::Arc;
use tokio::sync::mpsc::{self, UnboundedReceiver};
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
const WG_TUN_NAME: &str = "nymwg";
pub(crate) mod error;
pub mod peer_controller;
pub struct WgApiWrapper {
@@ -39,14 +43,14 @@ pub struct WireguardGatewayData {
config: Config,
keypair: Arc<KeyPair>,
client_registry: Arc<GatewayClientRegistry>,
peer_tx: mpsc::UnboundedSender<PeerControlMessage>,
peer_tx: UnboundedSender<PeerControlRequest>,
}
impl WireguardGatewayData {
pub fn new(
config: Config,
keypair: Arc<KeyPair>,
) -> (Self, mpsc::UnboundedReceiver<PeerControlMessage>) {
) -> (Self, UnboundedReceiver<PeerControlRequest>) {
let (peer_tx, peer_rx) = mpsc::unbounded_channel();
(
WireguardGatewayData {
@@ -75,20 +79,26 @@ impl WireguardGatewayData {
let mut peer = Peer::new(Key::new(client.pub_key.to_bytes()));
peer.allowed_ips
.push(IpAddrMask::new(client.private_ip, 32));
let msg = PeerControlMessage::AddPeer(peer);
let msg = PeerControlRequest::AddPeer(peer);
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
}
pub fn remove_peer(&self, client: &GatewayClient) -> Result<(), Error> {
let key = Key::new(client.pub_key().to_bytes());
let msg = PeerControlMessage::RemovePeer(key);
let msg = PeerControlRequest::RemovePeer(key);
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
}
pub fn query_bandwidth(&self, peer_public_key: PeerPublicKey) -> Result<(), Error> {
let key = Key::new(peer_public_key.to_bytes());
let msg = PeerControlRequest::QueryBandwidth(key);
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
}
}
pub struct WireguardData {
pub inner: WireguardGatewayData,
pub peer_rx: UnboundedReceiver<PeerControlMessage>,
pub peer_rx: UnboundedReceiver<PeerControlRequest>,
}
/// Start wireguard device
@@ -96,6 +106,7 @@ pub struct WireguardData {
pub async fn start_wireguard(
task_client: nym_task::TaskClient,
wireguard_data: WireguardData,
control_tx: UnboundedSender<peer_controller::PeerControlResponse>,
) -> Result<std::sync::Arc<WgApiWrapper>, Box<dyn std::error::Error + Send + Sync + 'static>> {
use base64::{prelude::BASE64_STANDARD, Engine};
use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi};
@@ -135,13 +146,13 @@ pub async fn start_wireguard(
wg_api.configure_peer_routing(&[catch_all_peer])?;
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
let mut controller = PeerController::new(wg_api.clone(), wireguard_data.peer_rx);
let mut controller = PeerController::new(
wg_api.clone(),
interface_config.peers,
wireguard_data.peer_rx,
control_tx,
);
tokio::spawn(async move { controller.run(task_client).await });
Ok(wg_api)
}
#[cfg(not(target_os = "linux"))]
pub async fn start_wireguard() {
todo!("WireGuard is currently only supported on Linux");
}
+128 -37
View File
@@ -1,91 +1,182 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::{
sync::Arc,
time::{Duration, SystemTime},
};
use defguard_wireguard_rs::{
host::{Host, Peer},
key::Key,
WGApi, WireguardInterfaceApi,
};
use chrono::{Timelike, Utc};
use defguard_wireguard_rs::{host::Peer, key::Key, WireguardInterfaceApi};
use nym_wireguard_types::registration::{RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY};
use std::time::SystemTime;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::mpsc;
use tokio_stream::{wrappers::IntervalStream, StreamExt};
use crate::error::Error;
use crate::WgApiWrapper;
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60); // 1 hour
// To avoid any problems, keep this stale check time bigger (>2x) then the bandwidth cap
// reset time (currently that one is 24h, at UTC midnight)
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60 * 24 * 3); // 3 days
const DEFAULT_PEER_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute
pub enum PeerControlMessage {
pub enum PeerControlRequest {
AddPeer(Peer),
RemovePeer(Key),
QueryBandwidth(Key),
}
pub enum PeerControlResponse {
AddPeer {
success: bool,
},
RemovePeer {
success: bool,
},
QueryBandwidth {
bandwidth_data: Option<RemainingBandwidthData>,
},
}
pub struct PeerController {
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
wg_api: Arc<WgApiWrapper>,
timeout_check_interval: IntervalStream,
active_peers: HashMap<Key, Peer>,
suspended_peers: HashMap<Key, Peer>,
last_seen_bandwidth: HashMap<Key, u64>,
}
impl PeerController {
pub fn new(
wg_api: Arc<WgApiWrapper>,
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
peers: Vec<Peer>,
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
) -> Self {
let timeout_check_interval = tokio_stream::wrappers::IntervalStream::new(
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
);
let active_peers = peers
.into_iter()
.map(|peer| (peer.public_key.clone(), peer))
.collect();
PeerController {
wg_api,
peer_rx,
request_rx,
response_tx,
timeout_check_interval,
active_peers,
suspended_peers: HashMap::new(),
last_seen_bandwidth: HashMap::new(),
}
}
fn remove_stale_peers(wg_api: &WGApi, host: Host) {
let current_timestamp = SystemTime::now();
for (key, peer) in host.peers.iter() {
if let Some(timestamp) = peer.last_handshake {
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
if let Err(e) = wg_api.remove_peer(key) {
log::error!("Could not remove stale peer: {:?}", e);
} else {
log::debug!("Removed stale peer {:?}", key);
}
}
fn check_stale_peer(&self, peer: &Peer, current_timestamp: SystemTime) -> Result<bool, Error> {
if let Some(timestamp) = peer.last_handshake {
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
self.wg_api.inner.remove_peer(&peer.public_key)?;
return Ok(true);
}
}
}
Ok(false)
}
fn check_suspend_peer(&mut self, peer: &Peer) -> Result<(), Error> {
let prev_peer = self
.active_peers
.get(&peer.public_key)
.ok_or(Error::PeerMismatch)?;
let data_usage =
(peer.rx_bytes + peer.tx_bytes).saturating_sub(prev_peer.rx_bytes + prev_peer.tx_bytes);
if data_usage > BANDWIDTH_CAP_PER_DAY {
self.wg_api.inner.remove_peer(&peer.public_key)?;
let (moved_key, moved_peer) = self
.active_peers
.remove_entry(&peer.public_key)
.ok_or(Error::PeerMismatch)?;
self.suspended_peers.insert(moved_key, moved_peer);
}
Ok(())
}
fn check_peers(&mut self) -> Result<(), Error> {
// Add 10 seconds to cover edge cases. At worst, we give ten free seconds worth of bandwidth
// by resetting the bandwidth twice
let reset = Utc::now().num_seconds_from_midnight() as u64
<= DEFAULT_PEER_TIMEOUT_CHECK.as_secs() + 10;
if reset {
for (_, peer) in self.suspended_peers.drain() {
self.wg_api.inner.configure_peer(&peer)?;
}
}
let host = self.wg_api.inner.read_interface_data()?;
self.last_seen_bandwidth = host
.peers
.iter()
.map(|(key, peer)| (key.clone(), peer.rx_bytes + peer.tx_bytes))
.collect();
if reset {
self.active_peers = host.peers;
} else {
let current_timestamp = SystemTime::now();
for peer in host.peers.values() {
if !self.check_stale_peer(peer, current_timestamp)? {
self.check_suspend_peer(peer)?;
}
}
}
Ok(())
}
pub async fn run(&mut self, mut task_client: nym_task::TaskClient) {
loop {
tokio::select! {
_ = self.timeout_check_interval.next() => {
match self.wg_api.inner.read_interface_data() {
Ok(host) => Self::remove_stale_peers(&self.wg_api.inner, host),
Err(e) => { log::error!("Could not read peer data: {:?}", e); },
if let Err(e) = self.check_peers() {
log::error!("Error while periodically checking peers: {:?}", e);
}
}
_ = task_client.recv() => {
log::trace!("PeerController handler: Received shutdown");
break;
}
msg = self.peer_rx.recv() => {
msg = self.request_rx.recv() => {
match msg {
Some(PeerControlMessage::AddPeer(peer)) => {
if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
Some(PeerControlRequest::AddPeer(peer)) => {
let success = if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
log::error!("Could not configure peer: {:?}", e);
}
false
} else {
self.active_peers.insert(peer.public_key.clone(), peer);
true
};
self.response_tx.send(PeerControlResponse::AddPeer { success }).ok();
}
Some(PeerControlMessage::RemovePeer(peer_pubkey)) => {
if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
Some(PeerControlRequest::RemovePeer(peer_pubkey)) => {
let success = if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
log::error!("Could not remove peer: {:?}", e);
}
false
} else {
self.active_peers.remove(&peer_pubkey);
self.suspended_peers.remove(&peer_pubkey);
true
};
self.response_tx.send(PeerControlResponse::RemovePeer { success }).ok();
}
Some(PeerControlRequest::QueryBandwidth(peer_pubkey)) => {
let msg = if self.suspended_peers.contains_key(&peer_pubkey) {
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: 0, suspended: true }) }
} else if let Some(&consumed_bandwidth) = self.last_seen_bandwidth.get(&peer_pubkey) {
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: BANDWIDTH_CAP_PER_DAY - consumed_bandwidth, suspended: false })}
} else {
PeerControlResponse::QueryBandwidth { bandwidth_data: None }
};
self.response_tx.send(msg).ok();
}
None => {
log::trace!("PeerController [main loop]: stopping since channel closed");
@@ -1,239 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::helpers::{mix_coin, mix_coins, vesting_owner};
use crate::support::setup::{TestSetup, MIX_DENOM};
use cosmwasm_std::Addr;
use cw_multi_test::Executor;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::error::MixnetContractError;
use nym_mixnet_contract_common::{ContractStateParams, MixNodeCostParams};
use nym_mixnet_contract_common::{MixOwnershipResponse, QueryMsg as MixnetQueryMsg};
use nym_vesting_contract_common::{ExecuteMsg as VestingExecuteMsg, VestingContractError};
#[test]
fn decrease_mixnode_pledge_from_vesting_account_with_minimum_pledge() {
let mut test = TestSetup::new_simple();
let vesting_account = "vesting-account";
// 0. get the minimum pledge amount
let state_params: ContractStateParams = test
.app
.wrap()
.query_wasm_smart(test.mixnet_contract(), &MixnetQueryMsg::GetStateParams {})
.unwrap();
let minimum_pledge = state_params.minimum_mixnode_pledge;
// 1. create vesting account
let create_msg = VestingExecuteMsg::CreateAccount {
owner_address: vesting_account.to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
test.app
.execute_contract(
vesting_owner(),
test.vesting_contract(),
&create_msg,
&mix_coins(1_000_000_000),
)
.unwrap();
// 2. bond mixnode with the vesting account
let pledge = minimum_pledge.clone();
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: mix_coin(40_000_000),
};
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
vesting_account,
Some(test.vesting_contract()),
cost_params.clone(),
pledge.clone(),
);
let bond_msg = VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount: pledge.clone(),
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&bond_msg,
&[],
)
.unwrap();
// 3. try to decrease the pledge
// trying to decrease by a zero amount - not valid
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: mix_coin(0),
};
let res_zero = test
.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap_err();
assert_eq!(
VestingContractError::EmptyFunds,
res_zero.downcast().unwrap()
);
// trying to go below the cap - also not valid
let amount = mix_coin(50_000);
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: amount.clone(),
};
let res_below = test
.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap_err();
assert_eq!(
MixnetContractError::InvalidPledgeReduction {
current: pledge.amount,
decrease_by: amount.amount,
minimum: minimum_pledge.amount,
denom: minimum_pledge.denom
},
res_below.downcast().unwrap()
)
}
#[test]
fn decrease_mixnode_pledge_from_vesting_account_with_sufficient_pledge() {
let mut test = TestSetup::new_simple();
let vesting_account = "vesting-account";
// 1. create vesting account
let create_msg = VestingExecuteMsg::CreateAccount {
owner_address: vesting_account.to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
test.app
.execute_contract(
vesting_owner(),
test.vesting_contract(),
&create_msg,
&mix_coins(10_000_000_000),
)
.unwrap();
// 2. bond mixnode with the vesting account
let pledge = mix_coin(150_000_000);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: mix_coin(40_000_000),
};
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
vesting_account,
Some(test.vesting_contract()),
cost_params.clone(),
pledge.clone(),
);
let bond_msg = VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount: pledge,
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&bond_msg,
&[],
)
.unwrap();
// 3. try to decrease the pledge
let before: MixOwnershipResponse = test
.app
.wrap()
.query_wasm_smart(
test.mixnet_contract(),
&MixnetQueryMsg::GetOwnedMixnode {
address: vesting_account.to_string(),
},
)
.unwrap();
let balance_before = test
.app
.wrap()
.query_balance(test.vesting_contract(), MIX_DENOM)
.unwrap();
assert_eq!(balance_before.amount.u128(), 9_850_000_000);
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: mix_coin(50_000_000),
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap();
let after_decrease: MixOwnershipResponse = test
.app
.wrap()
.query_wasm_smart(
test.mixnet_contract(),
&MixnetQueryMsg::GetOwnedMixnode {
address: vesting_account.to_string(),
},
)
.unwrap();
// note: nothing has changed with the pledge because the event hasn't been resolved yet!
assert_eq!(before.address, after_decrease.address);
let before_details = before.mixnode_details.unwrap();
let after_details = after_decrease.mixnode_details.unwrap();
assert_eq!(
before_details.rewarding_details,
after_details.rewarding_details
);
assert_eq!(
before_details.bond_information,
after_details.bond_information
);
// but we have the pending change saved now!
assert!(before_details.pending_changes.pledge_change.is_none());
assert_eq!(Some(1), after_details.pending_changes.pledge_change);
// 4. resolve events
test.advance_mixnet_epoch();
let balance_after = test
.app
.wrap()
.query_balance(test.vesting_contract(), MIX_DENOM)
.unwrap();
assert_eq!(balance_after.amount.u128(), 9_900_000_000);
}
@@ -24,5 +24,7 @@ pub fn default_mixnet_init_msg() -> nym_mixnet_contract_common::InstantiateMsg {
rewarded_set_size: 240,
active_set_size: 100,
},
profit_margin: Default::default(),
interval_operating_cost: Default::default(),
}
}
@@ -12,27 +12,33 @@ pub fn mixnet_owner() -> Addr {
Addr::unchecked(MIXNET_OWNER)
}
#[allow(unused)]
pub fn vesting_owner() -> Addr {
Addr::unchecked(VESTING_OWNER)
}
#[allow(unused)]
pub fn rewarding_validator() -> Addr {
Addr::unchecked(REWARDING_VALIDATOR)
}
#[allow(unused)]
pub fn mix_coins(amount: u128) -> Vec<Coin> {
coins(amount, MIX_DENOM)
}
#[allow(unused)]
pub fn mix_coin(amount: u128) -> Coin {
coin(amount, MIX_DENOM)
}
#[allow(unused)]
pub fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
ChaCha20Rng::from_seed(dummy_seed)
}
#[allow(unused)]
pub fn mixnet_contract_wrapper() -> Box<dyn Contract<Empty>> {
Box::new(
ContractWrapper::new(
@@ -26,6 +26,7 @@ pub const VESTING_OWNER: &str = "vesting-owner";
pub const REWARDING_VALIDATOR: &str = "rewarding-validator";
pub const MIX_DENOM: &str = "unym";
#[allow(unused)]
pub struct ContractInstantiationResult {
mixnet_contract_address: Addr,
vesting_contract_address: Addr,
@@ -69,14 +70,15 @@ impl TestSetupBuilder {
}
}
#[allow(unused)]
pub struct TestSetup {
pub app: App,
pub rng: ChaCha20Rng,
pub mixnet_contract: Addr,
pub vesting_contract: Addr,
}
#[allow(unused)]
impl TestSetup {
pub fn new_simple() -> Self {
TestSetup::new(Default::default(), fixtures::default_mixnet_init_msg())
@@ -91,7 +93,6 @@ impl TestSetup {
app,
rng: test_rng(),
mixnet_contract: contracts.mixnet_contract_address,
vesting_contract: contracts.vesting_contract_address,
}
}
@@ -99,10 +100,6 @@ impl TestSetup {
self.mixnet_contract.clone()
}
pub fn vesting_contract(&self) -> Addr {
self.vesting_contract.clone()
}
pub fn skip_to_current_epoch_end(&mut self) {
let current_interval: CurrentIntervalResponse = self
.app
@@ -209,7 +206,6 @@ impl TestSetup {
pub fn valid_mixnode_with_sig(
&mut self,
owner: &str,
proxy: Option<Addr>,
cost_params: MixNodeCostParams,
stake: Coin,
) -> (MixNode, MessageSignature) {
@@ -239,8 +235,7 @@ impl TestSetup {
};
let payload = MixnodeBondingPayload::new(mixnode.clone(), cost_params);
let content =
ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload);
let content = ContractMessageContent::new(Addr::unchecked(owner), vec![stake], payload);
let sign_payload = SignableMixNodeBondingMsg::new(signing_nonce, content);
let plaintext = sign_payload.to_plaintext().unwrap();
let signature = keypair.private_key().sign(plaintext);
@@ -1,5 +1,4 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod decrease_mixnode_pledge;
mod support;
@@ -26,6 +26,28 @@
"initial_rewarding_params": {
"$ref": "#/definitions/InitialRewardingParams"
},
"interval_operating_cost": {
"default": {
"maximum": "1000000000000000",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Uint128"
}
]
},
"profit_margin": {
"default": {
"maximum": "1",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Percent"
}
]
},
"rewarding_denom": {
"type": "string"
},
@@ -112,6 +134,42 @@
"$ref": "#/definitions/Decimal"
}
]
},
"RangedValue_for_Percent": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Percent"
},
"minimum": {
"$ref": "#/definitions/Percent"
}
},
"additionalProperties": false
},
"RangedValue_for_Uint128": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Uint128"
},
"minimum": {
"$ref": "#/definitions/Uint128"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
},
@@ -1146,6 +1204,42 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"migrate_vested_mix_node"
],
"properties": {
"migrate_vested_mix_node": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"migrate_vested_delegation"
],
"properties": {
"migrate_vested_delegation": {
"type": "object",
"required": [
"mix_id"
],
"properties": {
"mix_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
@@ -1172,6 +1266,18 @@
"minimum_mixnode_pledge"
],
"properties": {
"interval_operating_cost": {
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
"default": {
"maximum": "1000000000000000",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Uint128"
}
]
},
"minimum_gateway_pledge": {
"description": "Minimum amount a gateway must pledge to get into the system.",
"allOf": [
@@ -1198,6 +1304,18 @@
"$ref": "#/definitions/Coin"
}
]
},
"profit_margin": {
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
"default": {
"maximum": "1",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Percent"
}
]
}
},
"additionalProperties": false
@@ -1532,6 +1650,38 @@
}
]
},
"RangedValue_for_Percent": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Percent"
},
"minimum": {
"$ref": "#/definitions/Percent"
}
},
"additionalProperties": false
},
"RangedValue_for_Uint128": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Uint128"
},
"minimum": {
"$ref": "#/definitions/Uint128"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
@@ -8063,6 +8213,18 @@
"minimum_mixnode_pledge"
],
"properties": {
"interval_operating_cost": {
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
"default": {
"maximum": "1000000000000000",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Uint128"
}
]
},
"minimum_gateway_pledge": {
"description": "Minimum amount a gateway must pledge to get into the system.",
"allOf": [
@@ -8089,6 +8251,62 @@
"$ref": "#/definitions/Coin"
}
]
},
"profit_margin": {
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
"default": {
"maximum": "1",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Percent"
}
]
}
},
"additionalProperties": false
},
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
"allOf": [
{
"$ref": "#/definitions/Decimal"
}
]
},
"RangedValue_for_Percent": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Percent"
},
"minimum": {
"$ref": "#/definitions/Percent"
}
},
"additionalProperties": false
},
"RangedValue_for_Uint128": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Uint128"
},
"minimum": {
"$ref": "#/definitions/Uint128"
}
},
"additionalProperties": false
@@ -8109,6 +8327,18 @@
"minimum_mixnode_pledge"
],
"properties": {
"interval_operating_cost": {
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
"default": {
"maximum": "1000000000000000",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Uint128"
}
]
},
"minimum_gateway_pledge": {
"description": "Minimum amount a gateway must pledge to get into the system.",
"allOf": [
@@ -8135,6 +8365,18 @@
"$ref": "#/definitions/Coin"
}
]
},
"profit_margin": {
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
"default": {
"maximum": "1",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Percent"
}
]
}
},
"additionalProperties": false,
@@ -8154,6 +8396,50 @@
}
}
},
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
"allOf": [
{
"$ref": "#/definitions/Decimal"
}
]
},
"RangedValue_for_Percent": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Percent"
},
"minimum": {
"$ref": "#/definitions/Percent"
}
},
"additionalProperties": false
},
"RangedValue_for_Uint128": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Uint128"
},
"minimum": {
"$ref": "#/definitions/Uint128"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
+92
View File
@@ -1029,6 +1029,42 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"migrate_vested_mix_node"
],
"properties": {
"migrate_vested_mix_node": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"migrate_vested_delegation"
],
"properties": {
"migrate_vested_delegation": {
"type": "object",
"required": [
"mix_id"
],
"properties": {
"mix_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
@@ -1055,6 +1091,18 @@
"minimum_mixnode_pledge"
],
"properties": {
"interval_operating_cost": {
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
"default": {
"maximum": "1000000000000000",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Uint128"
}
]
},
"minimum_gateway_pledge": {
"description": "Minimum amount a gateway must pledge to get into the system.",
"allOf": [
@@ -1081,6 +1129,18 @@
"$ref": "#/definitions/Coin"
}
]
},
"profit_margin": {
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
"default": {
"maximum": "1",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Percent"
}
]
}
},
"additionalProperties": false
@@ -1415,6 +1475,38 @@
}
]
},
"RangedValue_for_Percent": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Percent"
},
"minimum": {
"$ref": "#/definitions/Percent"
}
},
"additionalProperties": false
},
"RangedValue_for_Uint128": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Uint128"
},
"minimum": {
"$ref": "#/definitions/Uint128"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
@@ -22,6 +22,28 @@
"initial_rewarding_params": {
"$ref": "#/definitions/InitialRewardingParams"
},
"interval_operating_cost": {
"default": {
"maximum": "1000000000000000",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Uint128"
}
]
},
"profit_margin": {
"default": {
"maximum": "1",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Percent"
}
]
},
"rewarding_denom": {
"type": "string"
},
@@ -108,6 +130,42 @@
"$ref": "#/definitions/Decimal"
}
]
},
"RangedValue_for_Percent": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Percent"
},
"minimum": {
"$ref": "#/definitions/Percent"
}
},
"additionalProperties": false
},
"RangedValue_for_Uint128": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Uint128"
},
"minimum": {
"$ref": "#/definitions/Uint128"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
}
@@ -77,6 +77,18 @@
"minimum_mixnode_pledge"
],
"properties": {
"interval_operating_cost": {
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
"default": {
"maximum": "1000000000000000",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Uint128"
}
]
},
"minimum_gateway_pledge": {
"description": "Minimum amount a gateway must pledge to get into the system.",
"allOf": [
@@ -103,6 +115,62 @@
"$ref": "#/definitions/Coin"
}
]
},
"profit_margin": {
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
"default": {
"maximum": "1",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Percent"
}
]
}
},
"additionalProperties": false
},
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
"allOf": [
{
"$ref": "#/definitions/Decimal"
}
]
},
"RangedValue_for_Percent": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Percent"
},
"minimum": {
"$ref": "#/definitions/Percent"
}
},
"additionalProperties": false
},
"RangedValue_for_Uint128": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Uint128"
},
"minimum": {
"$ref": "#/definitions/Uint128"
}
},
"additionalProperties": false
@@ -8,6 +8,18 @@
"minimum_mixnode_pledge"
],
"properties": {
"interval_operating_cost": {
"description": "Defines the allowed interval operating cost range of operators. default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)",
"default": {
"maximum": "1000000000000000",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Uint128"
}
]
},
"minimum_gateway_pledge": {
"description": "Minimum amount a gateway must pledge to get into the system.",
"allOf": [
@@ -34,6 +46,18 @@
"$ref": "#/definitions/Coin"
}
]
},
"profit_margin": {
"description": "Defines the allowed profit margin range of operators. default: 0% - 100%",
"default": {
"maximum": "1",
"minimum": "0"
},
"allOf": [
{
"$ref": "#/definitions/RangedValue_for_Percent"
}
]
}
},
"additionalProperties": false,
@@ -53,6 +77,50 @@
}
}
},
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
"allOf": [
{
"$ref": "#/definitions/Decimal"
}
]
},
"RangedValue_for_Percent": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Percent"
},
"minimum": {
"$ref": "#/definitions/Percent"
}
},
"additionalProperties": false
},
"RangedValue_for_Uint128": {
"type": "object",
"required": [
"maximum",
"minimum"
],
"properties": {
"maximum": {
"$ref": "#/definitions/Uint128"
},
"minimum": {
"$ref": "#/definitions/Uint128"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
+56 -117
View File
@@ -11,7 +11,8 @@ use cosmwasm_std::{
};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg,
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg,
OperatingCostRange, ProfitMarginRange, QueryMsg,
};
use nym_contracts_common::set_build_information;
@@ -24,6 +25,8 @@ fn default_initial_state(
rewarding_validator_address: Addr,
rewarding_denom: String,
vesting_contract_address: Addr,
profit_margin: ProfitMarginRange,
interval_operating_cost: OperatingCostRange,
) -> ContractState {
ContractState {
owner,
@@ -40,6 +43,8 @@ fn default_initial_state(
denom: rewarding_denom,
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
},
profit_margin,
interval_operating_cost,
},
}
}
@@ -71,6 +76,8 @@ pub fn instantiate(
rewarding_validator_address.clone(),
msg.rewarding_denom,
vesting_contract_address,
msg.profit_margin,
msg.interval_operating_cost,
);
let starting_interval =
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
@@ -118,44 +125,6 @@ pub fn execute(
ExecuteMsg::KickFamilyMember { member } => {
crate::families::transactions::try_head_kick_member(deps, info, member)
}
ExecuteMsg::CreateFamilyOnBehalf {
owner_address,
label,
} => crate::families::transactions::try_create_family_on_behalf(
deps,
info,
owner_address,
label,
),
ExecuteMsg::JoinFamilyOnBehalf {
member_address,
join_permit,
family_head,
} => crate::families::transactions::try_join_family_on_behalf(
deps,
info,
member_address,
join_permit,
family_head,
),
ExecuteMsg::LeaveFamilyOnBehalf {
member_address,
family_head,
} => crate::families::transactions::try_leave_family_on_behalf(
deps,
info,
member_address,
family_head,
),
ExecuteMsg::KickFamilyMemberOnBehalf {
head_address,
member,
} => crate::families::transactions::try_head_kick_member_on_behalf(
deps,
info,
head_address,
member,
),
// state/sys-params-related
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address(
@@ -232,62 +201,23 @@ pub fn execute(
cost_params,
owner_signature,
),
ExecuteMsg::BondMixnodeOnBehalf {
mix_node,
cost_params,
owner,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
deps,
env,
info,
mix_node,
cost_params,
owner,
owner_signature,
),
ExecuteMsg::PledgeMore {} => {
crate::mixnodes::transactions::try_increase_pledge(deps, env, info)
}
ExecuteMsg::PledgeMoreOnBehalf { owner } => {
crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, owner)
}
ExecuteMsg::DecreasePledge { decrease_by } => {
crate::mixnodes::transactions::try_decrease_pledge(deps, env, info, decrease_by)
}
ExecuteMsg::DecreasePledgeOnBehalf { owner, decrease_by } => {
crate::mixnodes::transactions::try_decrease_pledge_on_behalf(
deps,
env,
info,
decrease_by,
owner,
)
}
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
}
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, env, info, owner)
}
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
crate::mixnodes::transactions::try_update_mixnode_cost_params(
deps, env, info, new_costs,
)
}
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner } => {
crate::mixnodes::transactions::try_update_mixnode_cost_params_on_behalf(
deps, env, info, new_costs, owner,
)
}
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
crate::mixnodes::transactions::try_update_mixnode_config(deps, info, new_config)
}
ExecuteMsg::UpdateMixnodeConfigOnBehalf { new_config, owner } => {
crate::mixnodes::transactions::try_update_mixnode_config_on_behalf(
deps, info, new_config, owner,
)
}
// gateway-related:
ExecuteMsg::BondGateway {
@@ -300,52 +230,22 @@ pub fn execute(
gateway,
owner_signature,
),
ExecuteMsg::BondGatewayOnBehalf {
gateway,
owner,
owner_signature,
} => crate::gateways::transactions::try_add_gateway_on_behalf(
deps,
env,
info,
gateway,
owner,
owner_signature,
),
ExecuteMsg::UnbondGateway {} => {
crate::gateways::transactions::try_remove_gateway(deps, info)
}
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
}
ExecuteMsg::UpdateGatewayConfig { new_config } => {
crate::gateways::transactions::try_update_gateway_config(deps, info, new_config)
}
ExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner } => {
crate::gateways::transactions::try_update_gateway_config_on_behalf(
deps, info, new_config, owner,
)
}
// delegation-related:
ExecuteMsg::DelegateToMixnode { mix_id } => {
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_id)
}
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate } => {
crate::delegations::transactions::try_delegate_to_mixnode_on_behalf(
deps, env, info, mix_id, delegate,
)
}
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode(
deps, env, info, mix_id,
)
}
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, delegate } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
deps, env, info, mix_id, delegate,
)
}
// reward-related
ExecuteMsg::RewardMixnode {
@@ -356,16 +256,37 @@ pub fn execute(
ExecuteMsg::WithdrawOperatorReward {} => {
crate::rewards::transactions::try_withdraw_operator_reward(deps, info)
}
ExecuteMsg::WithdrawOperatorRewardOnBehalf { owner } => {
crate::rewards::transactions::try_withdraw_operator_reward_on_behalf(deps, info, owner)
}
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id)
}
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => {
crate::rewards::transactions::try_withdraw_delegator_reward_on_behalf(
deps, info, mix_id, owner,
)
// vesting migration:
ExecuteMsg::MigrateVestedMixNode { .. } => {
crate::vesting_migration::try_migrate_vested_mixnode(deps, info)
}
ExecuteMsg::MigrateVestedDelegation { mix_id } => {
crate::vesting_migration::try_migrate_vested_delegation(deps, info, mix_id)
}
// legacy vesting
ExecuteMsg::CreateFamilyOnBehalf { .. }
| ExecuteMsg::JoinFamilyOnBehalf { .. }
| ExecuteMsg::LeaveFamilyOnBehalf { .. }
| ExecuteMsg::KickFamilyMemberOnBehalf { .. }
| ExecuteMsg::BondMixnodeOnBehalf { .. }
| ExecuteMsg::PledgeMoreOnBehalf { .. }
| ExecuteMsg::DecreasePledgeOnBehalf { .. }
| ExecuteMsg::UnbondMixnodeOnBehalf { .. }
| ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. }
| ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. }
| ExecuteMsg::BondGatewayOnBehalf { .. }
| ExecuteMsg::UnbondGatewayOnBehalf { .. }
| ExecuteMsg::UpdateGatewayConfigOnBehalf { .. }
| ExecuteMsg::DelegateToMixnodeOnBehalf { .. }
| ExecuteMsg::UndelegateFromMixnodeOnBehalf { .. }
| ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. }
| ExecuteMsg::WithdrawDelegatorRewardOnBehalf { .. } => {
Err(MixnetContractError::DisabledVestingOperation)
}
// testing-only
@@ -607,13 +528,15 @@ pub fn query(
#[entry_point]
pub fn migrate(
deps: DepsMut<'_>,
mut deps: DepsMut<'_>,
_env: Env,
msg: MigrateMsg,
) -> Result<Response, MixnetContractError> {
set_build_information!(deps.storage)?;
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
crate::queued_migrations::vesting_purge(deps.branch())?;
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
// environment, one of the contracts will HAVE TO go through a migration
@@ -631,7 +554,7 @@ pub fn migrate(
mod tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::Decimal;
use cosmwasm_std::{Decimal, Uint128};
use mixnet_contract_common::reward_params::{IntervalRewardParams, RewardingParams};
use mixnet_contract_common::{InitialRewardingParams, Percent};
use std::time::Duration;
@@ -657,6 +580,14 @@ mod tests {
rewarded_set_size: 543,
active_set_size: 123,
},
profit_margin: ProfitMarginRange {
minimum: "0.05".parse().unwrap(),
maximum: "0.95".parse().unwrap(),
},
interval_operating_cost: OperatingCostRange {
minimum: "1000".parse().unwrap(),
maximum: "10000".parse().unwrap(),
},
};
let sender = mock_info("sender", &[]);
@@ -678,6 +609,14 @@ mod tests {
denom: "uatom".into(),
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
},
profit_margin: ProfitMarginRange {
minimum: Percent::from_percentage_value(5).unwrap(),
maximum: Percent::from_percentage_value(95).unwrap(),
},
interval_operating_cost: OperatingCostRange {
minimum: Uint128::new(1000),
maximum: Uint128::new(10000),
},
},
};
+2 -73
View File
@@ -302,10 +302,7 @@ mod tests {
mod delegator_delegations {
use super::*;
use crate::delegations::transactions::try_delegate_to_mixnode_on_behalf;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, Addr};
use cosmwasm_std::Addr;
#[test]
fn obeys_limits() {
@@ -453,25 +450,10 @@ mod tests {
#[test]
fn all_retrieved_delegations_are_from_the_specified_delegator() {
let mut test = TestSetup::new();
let env = test.env();
// it means we have, for example, delegation from "delegator1" towards mix1, mix2, ...., from "delegator2" towards mix1, mix2, ...., etc
add_dummy_mixes_with_delegations(&mut test, 50, 100);
// add some proxies while we're at it to make sure they're queried for separately
let with_proxy = "delegator42";
let vesting_contract = test.vesting_contract();
for mix_id in 1..=25 {
try_delegate_to_mixnode_on_behalf(
test.deps_mut(),
env.clone(),
mock_info(vesting_contract.as_ref(), &[coin(100_000, TEST_COIN_DENOM)]),
mix_id,
with_proxy.into(),
)
.unwrap();
}
test.execute_all_pending_events();
// make few queries
let res1 =
query_delegator_delegations_paged(test.deps(), "delegator2".into(), None, None)
@@ -490,59 +472,6 @@ mod tests {
.delegations
.into_iter()
.all(|d| d.owner == Addr::unchecked("delegator35")));
let with_proxy_full =
query_delegator_delegations_paged(test.deps(), with_proxy.into(), None, None)
.unwrap();
assert_eq!(with_proxy_full.delegations.len(), 125);
// all delegations have correct owner
assert!(with_proxy_full
.delegations
.iter()
.all(|d| d.owner == Addr::unchecked(with_proxy)));
// and we have 100 delegations without proxy and 25 with
let no_proxy = with_proxy_full
.delegations
.iter()
.filter(|d| d.proxy.is_none())
.count();
assert_eq!(no_proxy, 100);
let proxy = with_proxy_full
.delegations
.iter()
.filter(|d| d.proxy.is_some())
.count();
assert_eq!(proxy, 25);
assert!(with_proxy_full
.delegations
.iter()
.filter(|d| d.proxy.is_some())
.all(|d| d.proxy.as_ref().unwrap() == vesting_contract));
// now make sure that if we do it in paged manner, we'll get exactly the same result
let per_page = Some(15);
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = query_delegator_delegations_paged(
test.deps(),
with_proxy.into(),
start_after,
per_page,
)
.unwrap();
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
assert_eq!(with_proxy_full.delegations, delegations)
}
}
+13 -154
View File
@@ -1,14 +1,12 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::interval::storage as interval_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::helpers::{
ensure_epoch_in_progress_state, ensure_sent_by_vesting_contract, validate_delegation_stake,
};
use cosmwasm_std::{Addr, Coin, DepsMut, Env, MessageInfo, Response};
use crate::support::helpers::{ensure_epoch_in_progress_state, validate_delegation_stake};
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_pending_delegation_event, new_pending_undelegation_event,
@@ -21,30 +19,6 @@ pub(crate) fn try_delegate_to_mixnode(
env: Env,
info: MessageInfo,
mix_id: MixId,
) -> Result<Response, MixnetContractError> {
_try_delegate_to_mixnode(deps, env, mix_id, info.sender, info.funds, None)
}
pub(crate) fn try_delegate_to_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_id: MixId,
delegate: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let delegate = deps.api.addr_validate(&delegate)?;
_try_delegate_to_mixnode(deps, env, mix_id, delegate, info.funds, Some(info.sender))
}
pub(crate) fn _try_delegate_to_mixnode(
deps: DepsMut<'_>,
env: Env,
mix_id: MixId,
delegate: Addr,
amount: Vec<Coin>,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// delegation is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
@@ -52,7 +26,7 @@ pub(crate) fn _try_delegate_to_mixnode(
// check if the delegation contains any funds of the appropriate denomination
let contract_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
let delegation = validate_delegation_stake(
amount,
info.funds,
contract_state.params.minimum_mixnode_delegation,
contract_state.rewarding_denom,
)?;
@@ -67,14 +41,9 @@ pub(crate) fn _try_delegate_to_mixnode(
}
// push the event onto the queue and wait for it to be picked up at the end of the epoch
let cosmos_event = new_pending_delegation_event(&delegate, &proxy, &delegation, mix_id);
let cosmos_event = new_pending_delegation_event(&info.sender, &delegation, mix_id);
let epoch_event = PendingEpochEventKind::Delegate {
owner: delegate,
mix_id,
amount: delegation,
proxy,
};
let epoch_event = PendingEpochEventKind::new_delegate(info.sender, mix_id, delegation);
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
Ok(Response::new().add_event(cosmos_event))
@@ -85,35 +54,12 @@ pub(crate) fn try_remove_delegation_from_mixnode(
env: Env,
info: MessageInfo,
mix_id: MixId,
) -> Result<Response, MixnetContractError> {
_try_remove_delegation_from_mixnode(deps, env, mix_id, info.sender, None)
}
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_id: MixId,
delegate: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let delegate = deps.api.addr_validate(&delegate)?;
_try_remove_delegation_from_mixnode(deps, env, mix_id, delegate, Some(info.sender))
}
pub(crate) fn _try_remove_delegation_from_mixnode(
deps: DepsMut<'_>,
env: Env,
mix_id: MixId,
delegate: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// undelegation is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
// see if the delegation even exists
let storage_key = Delegation::generate_storage_key(mix_id, &delegate, proxy.as_ref());
let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
if storage::delegations()
.may_load(deps.storage, storage_key)?
@@ -121,19 +67,15 @@ pub(crate) fn _try_remove_delegation_from_mixnode(
{
return Err(MixnetContractError::NoMixnodeDelegationFound {
mix_id,
address: delegate.into_string(),
proxy: proxy.map(Addr::into_string),
address: info.sender.into_string(),
proxy: None,
});
}
// push the event onto the queue and wait for it to be picked up at the end of the epoch
let cosmos_event = new_pending_undelegation_event(&delegate, &proxy, mix_id);
let cosmos_event = new_pending_undelegation_event(&info.sender, mix_id);
let epoch_event = PendingEpochEventKind::Undelegate {
owner: delegate,
mix_id,
proxy,
};
let epoch_event = PendingEpochEventKind::new_undelegate(info.sender, mix_id);
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
Ok(Response::new().add_event(cosmos_event))
@@ -151,7 +93,7 @@ mod tests {
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, Decimal};
use cosmwasm_std::{coin, Addr, Decimal};
use mixnet_contract_common::{EpochState, EpochStatus};
#[test]
@@ -368,65 +310,17 @@ mod tests {
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
let amount2 = coin(50_000_000, TEST_COIN_DENOM);
let sender1 = mock_info(owner, &[amount1.clone()]);
let sender2 = mock_info(test.vesting_contract().as_str(), &[amount2.clone()]);
try_delegate_to_mixnode(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
try_delegate_to_mixnode_on_behalf(test.deps_mut(), env, sender2, mix_id, owner.into())
.unwrap();
let events = test.pending_epoch_events();
assert_eq!(
events[0].kind,
PendingEpochEventKind::Delegate {
owner: Addr::unchecked(owner),
mix_id,
amount: amount1,
proxy: None
}
PendingEpochEventKind::new_delegate(Addr::unchecked(owner), mix_id, amount1,)
);
assert_eq!(
events[1].kind,
PendingEpochEventKind::Delegate {
owner: Addr::unchecked(owner),
mix_id,
amount: amount2,
proxy: Some(test.vesting_contract())
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "delegator";
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let res = try_delegate_to_mixnode_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
mix_id,
owner.into(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
@@ -573,40 +467,5 @@ mod tests {
);
assert!(res.is_ok());
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "delegator";
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.add_immediate_delegation_with_illegal_proxy(
owner,
10000u32,
mix_id,
illegal_proxy.clone(),
);
let res = try_remove_delegation_from_mixnode_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
mix_id,
owner.into(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
}
@@ -4,7 +4,7 @@
use crate::mixnodes::storage as mixnodes_storage;
use crate::signing::storage as signing_storage;
use crate::support::helpers::decode_ed25519_identity_key;
use cosmwasm_std::{Addr, Deps};
use cosmwasm_std::Deps;
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::{construct_family_join_permit, IdentityKeyRef};
@@ -13,7 +13,6 @@ use nym_contracts_common::signing::{MessageSignature, Verifier};
pub(crate) fn verify_family_join_permit(
deps: Deps<'_>,
granter: FamilyHead,
proxy: Option<Addr>,
member: IdentityKeyRef,
signature: MessageSignature,
) -> Result<(), MixnetContractError> {
@@ -32,7 +31,7 @@ pub(crate) fn verify_family_join_permit(
});
};
let nonce = signing_storage::get_signing_nonce(deps.storage, head_mixnode.owner)?;
let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned());
let msg = construct_family_join_permit(nonce, granter, member.to_owned());
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
+15 -285
View File
@@ -1,4 +1,4 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage::{
@@ -7,41 +7,20 @@ use super::storage::{
};
use crate::families::queries::get_family_by_label;
use crate::families::signature_helpers::verify_family_join_permit;
use crate::support::helpers::{ensure_bonded, ensure_sent_by_vesting_contract};
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
use crate::support::helpers::ensure_bonded;
use cosmwasm_std::{DepsMut, MessageInfo, Response};
use mixnet_contract_common::families::{Family, FamilyHead};
use mixnet_contract_common::{error::MixnetContractError, IdentityKey};
use nym_contracts_common::signing::MessageSignature;
/// Creates a new MixNode family with senders node as head
pub fn try_create_family(
pub(crate) fn try_create_family(
deps: DepsMut,
info: MessageInfo,
label: String,
) -> Result<Response, MixnetContractError> {
_try_create_family(deps, &info.sender, label, None)
}
pub fn try_create_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
owner_address: String,
label: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner_address = deps.api.addr_validate(&owner_address)?;
_try_create_family(deps, &owner_address, label, Some(info.sender))
}
fn _try_create_family(
deps: DepsMut,
owner: &Addr,
label: String,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
ensure_bonded(&existing_bond)?;
@@ -60,43 +39,19 @@ fn _try_create_family(
return Err(MixnetContractError::FamilyWithLabelExists(label));
}
let family = Family::new(family_head, proxy, label);
let family = Family::new(family_head, label);
save_family(&family, deps.storage)?;
Ok(Response::default())
}
pub fn try_join_family(
pub(crate) fn try_join_family(
deps: DepsMut,
info: MessageInfo,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
_try_join_family(deps, &info.sender, join_permit, family_head, None)
}
pub fn try_join_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
member_address: String,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let member_address = deps.api.addr_validate(&member_address)?;
let proxy = Some(info.sender);
_try_join_family(deps, &member_address, join_permit, family_head, proxy)
}
fn _try_join_family(
deps: DepsMut,
owner: &Addr,
join_permit: MessageSignature,
family_head: FamilyHead,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
ensure_bonded(&existing_bond)?;
@@ -116,7 +71,6 @@ fn _try_join_family(
verify_family_join_permit(
deps.as_ref(),
family_head.clone(),
proxy,
existing_bond.identity(),
join_permit,
)?;
@@ -128,33 +82,13 @@ fn _try_join_family(
Ok(Response::default())
}
pub fn try_leave_family(
pub(crate) fn try_leave_family(
deps: DepsMut,
info: MessageInfo,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
_try_leave_family(deps, &info.sender, family_head)
}
pub fn try_leave_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
member_address: String,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let member_address = deps.api.addr_validate(&member_address)?;
_try_leave_family(deps, &member_address, family_head)
}
fn _try_leave_family(
deps: DepsMut,
owner: &Addr,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
ensure_bonded(&existing_bond)?;
@@ -178,32 +112,13 @@ fn _try_leave_family(
Ok(Response::default())
}
pub fn try_head_kick_member(
pub(crate) fn try_head_kick_member(
deps: DepsMut,
info: MessageInfo,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
_try_head_kick_member(deps, &info.sender, member)
}
pub fn try_head_kick_member_on_behalf(
deps: DepsMut,
info: MessageInfo,
head_address: String,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let head_address = deps.api.addr_validate(&head_address)?;
_try_head_kick_member(deps, &head_address, member)
}
fn _try_head_kick_member(
deps: DepsMut,
owner: &Addr,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
let head_bond = crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
let head_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
// make sure we're still in the mixnet
ensure_bonded(&head_bond)?;
@@ -321,7 +236,7 @@ mod test {
assert_eq!(family.head_identity(), family_head.identity());
let join_permit =
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
try_join_family(
test.deps_mut(),
@@ -345,7 +260,7 @@ mod test {
);
let new_join_permit =
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
try_join_family(
test.deps_mut(),
@@ -373,189 +288,4 @@ mod test {
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
}
#[cfg(test)]
mod creating_family {
use super::*;
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let head = "alice";
test.add_dummy_mixnode(head, None);
let res = try_create_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
head.to_string(),
"label".to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
#[cfg(test)]
mod joining_family {
use super::*;
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let head = "alice";
let label = "family";
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
false,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(head_identity);
let res = try_join_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
new_member.to_string(),
join_permit,
family_head,
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
#[cfg(test)]
mod leaving_family {
use super::*;
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let head = "alice";
let label = "family";
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
true,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(head_identity);
try_join_family_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
new_member.to_string(),
join_permit,
family_head.clone(),
)
.unwrap();
let res = try_leave_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
new_member.to_string(),
family_head,
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
#[cfg(test)]
mod kicking_family_member {
use super::*;
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let head = "alice";
let label = "family";
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
true,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(head_identity);
try_join_family_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
new_member.to_string(),
join_permit,
family_head,
)
.unwrap();
let res = try_head_kick_member_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
head.to_string(),
member_keys.public_key().to_base58_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
}
@@ -12,7 +12,6 @@ use nym_contracts_common::signing::Verifier;
pub(crate) fn verify_gateway_bonding_signature(
deps: Deps<'_>,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
gateway: Gateway,
signature: MessageSignature,
@@ -22,7 +21,7 @@ pub(crate) fn verify_gateway_bonding_signature(
// reconstruct the payload
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
let msg = construct_gateway_bonding_sign_payload(nonce, sender, proxy, pledge, gateway);
let msg = construct_gateway_bonding_sign_payload(nonce, sender, pledge, gateway);
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
+28 -253
View File
@@ -1,4 +1,4 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::helpers::must_get_gateway_bond_by_owner;
@@ -6,10 +6,8 @@ use super::storage;
use crate::gateways::signature_helpers::verify_gateway_bonding_signature;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{
ensure_no_existing_bond, ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
};
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
use crate::support::helpers::{ensure_no_existing_bond, validate_pledge};
use cosmwasm_std::{BankMsg, DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_gateway_bonding_event, new_gateway_config_update_event, new_gateway_unbonding_event,
@@ -17,72 +15,28 @@ use mixnet_contract_common::events::{
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::{Gateway, GatewayBond};
use nym_contracts_common::signing::MessageSignature;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
pub fn try_add_gateway(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
gateway: Gateway,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
_try_add_gateway(
deps,
env,
gateway,
info.funds,
info.sender,
owner_signature,
None,
)
}
pub fn try_add_gateway_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
gateway: Gateway,
owner: String,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_add_gateway(
deps,
env,
gateway,
info.funds,
owner,
owner_signature,
Some(proxy),
)
}
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
// so that we could return a better error message if it doesn't match?
pub(crate) fn _try_add_gateway(
pub(crate) fn try_add_gateway(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
gateway: Gateway,
pledge: Vec<Coin>,
owner: Addr,
owner_signature: MessageSignature,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::minimum_gateway_pledge(deps.storage)?;
let pledge = validate_pledge(pledge, minimum_pledge)?;
let pledge = validate_pledge(info.funds, minimum_pledge)?;
// if the client has an active bonded mixnode or gateway, don't allow bonding
ensure_no_existing_bond(&owner, deps.storage)?;
ensure_no_existing_bond(&info.sender, deps.storage)?;
// check if somebody else has already bonded a gateway with this identity
if let Some(existing_bond) =
storage::gateways().may_load(deps.storage, &gateway.identity_key)?
{
if existing_bond.owner != owner {
if existing_bond.owner != info.sender {
return Err(MixnetContractError::DuplicateGateway {
owner: existing_bond.owner,
});
@@ -92,105 +46,62 @@ pub(crate) fn _try_add_gateway(
// check if this sender actually owns the gateway by checking the signature
verify_gateway_bonding_signature(
deps.as_ref(),
owner.clone(),
proxy.clone(),
info.sender.clone(),
pledge.clone(),
gateway.clone(),
owner_signature,
)?;
// update the signing nonce associated with this sender so that the future signature would be made on the new value
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?;
let gateway_identity = gateway.identity_key.clone();
let bond = GatewayBond::new(
pledge.clone(),
owner.clone(),
info.sender.clone(),
env.block.height,
gateway,
proxy.clone(),
);
storage::gateways().save(deps.storage, bond.identity(), &bond)?;
Ok(Response::new().add_event(new_gateway_bonding_event(
&owner,
&proxy,
&info.sender,
&pledge,
&gateway_identity,
)))
}
pub fn try_remove_gateway_on_behalf(
pub(crate) fn try_remove_gateway(
deps: DepsMut<'_>,
info: MessageInfo,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_remove_gateway(deps, owner, Some(proxy))
}
pub fn try_remove_gateway(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
_try_remove_gateway(deps, info.sender, None)
}
pub(crate) fn _try_remove_gateway(
deps: DepsMut<'_>,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// try to find the node of the sender
let gateway_bond = match storage::gateways()
.idx
.owner
.item(deps.storage, owner.clone())?
.item(deps.storage, info.sender.clone())?
{
Some(record) => record.1,
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner }),
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner: info.sender }),
};
if proxy != gateway_bond.proxy {
return Err(MixnetContractError::ProxyMismatch {
existing: gateway_bond
.proxy
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
});
}
// send bonded funds back to the bond owner
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
to_address: info.sender.to_string(),
amount: vec![gateway_bond.pledge_amount()],
};
// remove the bond
storage::gateways().remove(deps.storage, gateway_bond.identity())?;
let mut response = Response::new().add_message(return_tokens);
if let Some(proxy) = &proxy {
let msg = VestingContractExecuteMsg::TrackUnbondGateway {
owner: owner.as_str().to_string(),
amount: gateway_bond.pledge_amount(),
};
let track_unbond_message = wasm_execute(proxy, &msg, vec![])?;
response = response.add_message(track_unbond_message);
}
Ok(response.add_event(new_gateway_unbonding_event(
&owner,
&proxy,
&gateway_bond.pledge_amount,
gateway_bond.identity(),
)))
Ok(Response::new()
.add_message(return_tokens)
.add_event(new_gateway_unbonding_event(
&info.sender,
&gateway_bond.pledge_amount,
gateway_bond.identity(),
)))
}
pub(crate) fn try_update_gateway_config(
@@ -198,36 +109,9 @@ pub(crate) fn try_update_gateway_config(
info: MessageInfo,
new_config: GatewayConfigUpdate,
) -> Result<Response, MixnetContractError> {
let owner = info.sender;
_try_update_gateway_config(deps, new_config, owner, None)
}
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &info.sender)?;
let cfg_update_event = new_gateway_config_update_event(&info.sender, &new_config);
pub(crate) fn try_update_gateway_config_on_behalf(
deps: DepsMut,
info: MessageInfo,
new_config: GatewayConfigUpdate,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner = deps.api.addr_validate(&owner)?;
let proxy = info.sender;
_try_update_gateway_config(deps, new_config, owner, Some(proxy))
}
pub(crate) fn _try_update_gateway_config(
deps: DepsMut,
new_config: GatewayConfigUpdate,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &owner)?;
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
let cfg_update_event = new_gateway_config_update_event(&owner, &proxy, &new_config);
// clippy beta 1.70.0-beta.1 false positive
#[allow(clippy::redundant_clone)]
let mut updated_bond = existing_bond.clone();
updated_bond.gateway.host = new_config.host;
updated_bond.gateway.mix_port = new_config.mix_port;
@@ -254,10 +138,10 @@ pub mod tests {
use crate::mixnet_contract_settings::storage::minimum_gateway_pledge;
use crate::support::tests;
use crate::support::tests::fixtures;
use crate::support::tests::fixtures::{good_gateway_pledge, good_mixnode_pledge};
use crate::support::tests::fixtures::good_mixnode_pledge;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Uint128;
use cosmwasm_std::{Addr, Uint128};
use mixnet_contract_common::ExecuteMsg;
#[test]
@@ -392,42 +276,12 @@ pub mod tests {
.unwrap();
assert_eq!(1, updated_nonce);
_try_remove_gateway(test.deps_mut(), Addr::unchecked(sender), None).unwrap();
try_remove_gateway(test.deps_mut(), info.clone()).unwrap();
let res = try_add_gateway(test.deps_mut(), env, info, gateway, signature);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
}
#[test]
fn gateway_add_with_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
let (gateway, sig) = test.gateway_with_signature(owner, None);
let res = try_add_gateway_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()),
gateway,
owner.to_string(),
sig,
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
#[test]
fn gateway_remove() {
let mut test = TestSetup::new();
@@ -495,7 +349,6 @@ pub mod tests {
.add_message(expected_message)
.add_event(new_gateway_unbonding_event(
&Addr::unchecked("fred"),
&None,
&tests::fixtures::good_gateway_pledge()[0],
&fred_identity,
));
@@ -510,33 +363,6 @@ pub mod tests {
assert_eq!(&Addr::unchecked("bob"), nodes[0].owner());
}
#[test]
fn gateway_remove_with_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
let res = try_remove_gateway_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
#[test]
fn update_gateway_config() {
let mut test = TestSetup::new();
@@ -561,22 +387,6 @@ pub mod tests {
);
test.add_dummy_gateway(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_update_gateway_config_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
update.clone(),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string()
})
);
// "normal" update succeeds
let res = try_update_gateway_config(test.deps_mut(), info, update.clone());
@@ -591,39 +401,4 @@ pub mod tests {
assert_eq!(bond.gateway.location, update.location);
assert_eq!(bond.gateway.version, update.version);
}
#[test]
fn updating_gateway_config_with_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
let update = GatewayConfigUpdate {
host: "1.1.1.1:1234".to_string(),
mix_port: 1234,
clients_port: 1235,
location: "at home".to_string(),
version: "v1.2.3".to_string(),
};
let res = try_update_gateway_config_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
update,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
+41 -441
View File
@@ -24,7 +24,7 @@ use crate::interval::storage;
use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_details_by_id};
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::storage as rewards_storage;
use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking};
use crate::support::helpers::AttachSendTokens;
pub(crate) trait ContractExecutableEvent {
// note: the error only means a HARD error like we failed to read from storage.
@@ -40,7 +40,6 @@ pub(crate) fn delegate(
owner: Addr,
mix_id: MixId,
amount: Coin,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// check if the target node still exists (it might have unbonded between this event getting created
// and being executed). Do note that it's absolutely possible for a mixnode to get immediately
@@ -56,20 +55,9 @@ pub(crate) fn delegate(
_ => {
// if mixnode is no longer bonded or in the process of unbonding, return the tokens back to the
// delegator;
// (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`)
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![amount.clone()]);
let response = Response::new()
.add_message(return_tokens)
.add_event(new_delegation_on_unbonded_node_event(
&owner, &proxy, mix_id,
))
.maybe_add_track_vesting_undelegation_message(
deps.storage,
proxy,
owner.to_string(),
mix_id,
amount,
)?;
.send_tokens(&owner, amount.clone())
.add_event(new_delegation_on_unbonded_node_event(&owner, mix_id));
return Ok(response);
}
@@ -84,7 +72,7 @@ pub(crate) fn delegate(
// if there's an existing delegation, then withdraw the full reward and create a new delegation
// with the sum of both
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
let storage_key = Delegation::generate_storage_key(mix_id, &owner, None);
let old_delegation = if let Some(existing_delegation) =
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
{
@@ -106,7 +94,6 @@ pub(crate) fn delegate(
let cosmos_event = new_delegation_event(
created_at,
&owner,
&proxy,
&new_delegation_amount,
mix_id,
mix_rewarding.total_unit_reward,
@@ -118,7 +105,6 @@ pub(crate) fn delegate(
mix_rewarding.total_unit_reward,
stored_delegation_amount,
env.block.height,
proxy,
);
// save on reading since `.save()` would have attempted to read old data that we already have on hand
@@ -138,11 +124,10 @@ pub(crate) fn undelegate(
created_at: BlockHeight,
owner: Addr,
mix_id: MixId,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// see if the delegation still exists (in case of impatient user who decided to send multiple
// undelegation requests in an epoch)
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
let storage_key = Delegation::generate_storage_key(mix_id, &owner, None);
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
None => return Ok(Response::default()),
Some(delegation) => delegation,
@@ -155,18 +140,9 @@ pub(crate) fn undelegate(
let tokens_to_return =
delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?;
// (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`)
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![tokens_to_return.clone()]);
let response = Response::new()
.add_message(return_tokens)
.add_event(new_undelegation_event(created_at, &owner, &proxy, mix_id))
.maybe_add_track_vesting_undelegation_message(
deps.storage,
proxy,
owner.to_string(),
mix_id,
tokens_to_return,
)?;
.send_tokens(&owner, tokens_to_return.clone())
.add_event(new_undelegation_event(created_at, &owner, mix_id));
Ok(response)
}
@@ -197,25 +173,15 @@ pub(crate) fn unbond_mixnode(
.rewarding_details
.operator_pledge_with_reward(rewarding_denom);
let proxy = &node_details.bond_information.proxy;
let owner = &node_details.bond_information.owner;
// send bonded funds (alongside all earned rewards) to the bond owner
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![tokens.clone()]);
// remove the bond and if there are no delegations left, also the rewarding information
// decrement the associated layer count
cleanup_post_unbond_mixnode_storage(deps.storage, env, &node_details)?;
let response = Response::new()
.add_message(return_tokens)
.add_event(new_mixnode_unbonding_event(created_at, mix_id))
.maybe_add_track_vesting_unbond_mixnode_message(
deps.storage,
proxy.clone(),
owner.clone().into_string(),
tokens,
)?;
.send_tokens(owner, tokens.clone())
.add_event(new_mixnode_unbonding_event(created_at, mix_id));
Ok(response)
}
@@ -311,12 +277,8 @@ pub(crate) fn decrease_pledge(
updated_bond.original_pledge.amount -= decrease_by.amount;
updated_rewarding.decrease_operator_uint128(decrease_by.amount)?;
let proxy = &mix_details.bond_information.proxy;
let owner = &mix_details.bond_information.owner;
// send the removed tokens back to the operator
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![decrease_by.clone()]);
// update all: bond information, rewarding details and pending pledge changes
mixnodes_storage::mixnode_bonds().replace(
deps.storage,
@@ -328,14 +290,8 @@ pub(crate) fn decrease_pledge(
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
let response = Response::new()
.add_message(return_tokens)
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by))
.maybe_add_track_vesting_decrease_mixnode_pledge(
deps.storage,
proxy.clone(),
owner.clone().to_string(),
decrease_by,
)?;
.send_tokens(owner, decrease_by.clone())
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by));
Ok(response)
}
@@ -349,13 +305,11 @@ impl ContractExecutableEvent for PendingEpochEventData {
owner,
mix_id,
amount,
proxy,
} => delegate(deps, env, self.created_at, owner, mix_id, amount, proxy),
PendingEpochEventKind::Undelegate {
owner,
mix_id,
proxy,
} => undelegate(deps, self.created_at, owner, mix_id, proxy),
..
} => delegate(deps, env, self.created_at, owner, mix_id, amount),
PendingEpochEventKind::Undelegate { owner, mix_id, .. } => {
undelegate(deps, self.created_at, owner, mix_id)
}
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
increase_pledge(deps, self.created_at, mix_id, amount)
}
@@ -472,33 +426,25 @@ impl ContractExecutableEvent for PendingIntervalEventData {
#[cfg(test)]
mod tests {
use std::time::Duration;
use cosmwasm_std::Decimal;
use mixnet_contract_common::Percent;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use super::*;
use crate::support::tests::test_helpers;
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
use super::*;
use cosmwasm_std::Decimal;
use mixnet_contract_common::Percent;
use std::time::Duration;
// note that authorization and basic validation has already been performed for all of those
// before being pushed onto the event queues
#[cfg(test)]
mod delegating {
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
use crate::mixnodes::transactions::try_remove_mixnode;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use super::*;
use cosmwasm_std::coin;
use cosmwasm_std::testing::mock_info;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn returns_the_tokens_if_mixnode_has_unbonded() {
@@ -523,7 +469,6 @@ mod tests {
Addr::unchecked(owner1),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
@@ -549,7 +494,6 @@ mod tests {
Addr::unchecked(owner2),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
let storage_key =
@@ -588,7 +532,6 @@ mod tests {
Addr::unchecked(owner1),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
@@ -614,7 +557,6 @@ mod tests {
Addr::unchecked(owner2),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
let storage_key =
@@ -650,7 +592,6 @@ mod tests {
Addr::unchecked(owner),
mix_id,
delegation_coin_new,
None,
)
.unwrap();
@@ -725,7 +666,6 @@ mod tests {
Addr::unchecked(owner),
mix_id,
delegation_coin_new,
None,
)
.unwrap();
@@ -797,7 +737,6 @@ mod tests {
Addr::unchecked(owner),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
assert!(get_bank_send_msg(&res).is_none());
@@ -816,117 +755,13 @@ mod tests {
Decimal::from_atomics(delegation, 0).unwrap()
)
}
#[test]
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegation = 120_000_000u128;
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
let owner = "delegator";
let env = test.env();
unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
let vesting_contract = test.vesting_contract();
// for a fresh delegation, nothing was added to the storage either
let res_vesting = delegate(
test.deps_mut(),
&env,
123,
Addr::unchecked(owner),
mix_id,
delegation_coin.clone(),
Some(vesting_contract.clone()),
)
.unwrap();
let storage_key = Delegation::generate_storage_key(
mix_id,
&Addr::unchecked(owner),
Some(vesting_contract.clone()).as_ref(),
);
assert!(delegations_storage::delegations()
.may_load(test.deps().storage, storage_key)
.unwrap()
.is_none());
// and all tokens are returned back to the proxy
let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap();
assert_eq!(receiver, vesting_contract.as_str());
assert_eq!(sent_amount[0], delegation_coin);
// and we get appropriate track message
let mut found_track = true;
for msg in &res_vesting.messages {
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msg.msg
{
found_track = true;
assert_eq!(contract_addr, vesting_contract.as_str());
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation {
owner: owner.to_string(),
mix_id,
amount: delegation_coin.clone(),
})
.unwrap();
assert_eq!(&expected_msg, msg);
assert!(funds.is_empty())
}
}
assert!(found_track);
}
#[test]
fn returns_error_for_illegal_proxy() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegation = 120_000_000u128;
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
let owner = "delegator";
let dummy_proxy = Addr::unchecked("not-vesting-contract");
let env = test.env();
unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
let vesting_contract = test.vesting_contract();
// try to add illegal delegation (with invalid proxy)
let res_other_proxy = delegate(
test.deps_mut(),
&env,
123,
Addr::unchecked(owner),
mix_id,
delegation_coin,
Some(dummy_proxy.clone()),
)
.unwrap_err();
assert_eq!(
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
}
);
}
}
#[cfg(test)]
mod undelegating {
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use super::*;
use crate::support::tests::test_helpers::get_bank_send_msg;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn doesnt_return_any_tokens_if_it_doesnt_exist() {
@@ -935,7 +770,7 @@ mod tests {
let owner = Addr::unchecked("delegator");
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None).unwrap();
let res = undelegate(test.deps_mut(), 123, owner, mix_id).unwrap();
assert!(get_bank_send_msg(&res).is_none());
}
@@ -950,7 +785,7 @@ mod tests {
// this should never happen in actual code, but if we manually messed something up,
// lets make sure this throws an error
rewards_storage::MIXNODE_REWARDING.remove(test.deps_mut().storage, mix_id);
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None);
let res = undelegate(test.deps_mut(), 123, owner, mix_id);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
@@ -996,8 +831,7 @@ mod tests {
let expected_return = delegation + truncated_reward.u128();
let res =
undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id, None).unwrap();
let res = undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id).unwrap();
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
assert_eq!(receiver, owner);
assert_eq!(sent_amount[0].amount.u128(), expected_return);
@@ -1015,117 +849,19 @@ mod tests {
assert!(rewarding.delegates.is_zero());
assert_eq!(rewarding.unique_delegations, 0);
}
#[test]
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegation = 120_000_000u128;
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
let owner = "delegator";
let vesting_contract = test.vesting_contract();
test.add_immediate_delegation_with_legal_proxy(owner, delegation, mix_id);
let res_vesting = undelegate(
test.deps_mut(),
123,
Addr::unchecked(owner),
mix_id,
Some(vesting_contract.clone()),
)
.unwrap();
let storage_key = Delegation::generate_storage_key(
mix_id,
&Addr::unchecked(owner),
Some(vesting_contract.clone()).as_ref(),
);
assert!(delegations_storage::delegations()
.may_load(test.deps().storage, storage_key)
.unwrap()
.is_none());
// and all tokens are returned back to the proxy
let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap();
assert_eq!(receiver, vesting_contract.as_str());
assert_eq!(sent_amount[0], delegation_coin);
// and we get appropriate track message
let mut found_track = true;
for msg in &res_vesting.messages {
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msg.msg
{
found_track = true;
assert_eq!(contract_addr, vesting_contract.as_str());
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation {
owner: owner.to_string(),
mix_id,
amount: delegation_coin.clone(),
})
.unwrap();
assert_eq!(&expected_msg, msg);
assert!(funds.is_empty())
}
}
assert!(found_track);
}
#[test]
fn returns_error_for_illegal_proxy() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegation = 120_000_000u128;
let owner = "delegator1";
let vesting_contract = test.vesting_contract();
let dummy_proxy = Addr::unchecked("not-vesting-contract");
test.add_immediate_delegation_with_illegal_proxy(
owner,
delegation,
mix_id,
dummy_proxy.clone(),
);
let res_other_proxy = undelegate(
test.deps_mut(),
123,
Addr::unchecked(owner),
mix_id,
Some(dummy_proxy.clone()),
)
.unwrap_err();
assert_eq!(
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
}
);
}
}
#[cfg(test)]
mod mixnode_unbonding {
use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
use super::*;
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::transactions::{try_decrease_pledge, try_increase_pledge};
use crate::support::tests::test_helpers::get_bank_send_msg;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Uint128;
use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::transactions::{_try_decrease_pledge, _try_increase_pledge};
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use super::*;
#[test]
fn returns_hard_error_if_mixnode_doesnt_exist() {
// this should have never happened so hard error MUST be thrown here
@@ -1150,12 +886,10 @@ mod tests {
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
_try_increase_pledge(
try_increase_pledge(
test.deps_mut(),
env.clone(),
change.clone(),
Addr::unchecked(owner),
None,
mock_info(owner, &change.clone()),
)
.unwrap();
@@ -1170,12 +904,11 @@ mod tests {
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
_try_decrease_pledge(
try_decrease_pledge(
test.deps_mut(),
env.clone(),
mock_info(owner, &[]),
change[0].clone(),
Addr::unchecked(owner),
None,
)
.unwrap();
@@ -1263,79 +996,6 @@ mod tests {
0
)
}
#[test]
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
let mut test = TestSetup::new();
let vesting_contract = test.vesting_contract();
let pledge = Uint128::new(250_000_000);
let pledge_coin = coin(250_000_000, TEST_COIN_DENOM);
let owner = "mix-owner1";
let mix_id_vesting = test.add_dummy_mixnode_with_legal_proxy(owner, Some(pledge));
let env = test.env();
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id_vesting).unwrap();
assert!(mixnodes_storage::mixnode_bonds()
.may_load(test.deps().storage, mix_id_vesting)
.unwrap()
.is_none());
// and all tokens are returned back to the proxy
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
assert_eq!(receiver, vesting_contract.as_str());
assert_eq!(sent_amount[0], pledge_coin);
// and we get appropriate track message
let mut found_track = true;
for msg in &res.messages {
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msg.msg
{
found_track = true;
assert_eq!(contract_addr, vesting_contract.as_str());
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUnbondMixnode {
owner: owner.to_string(),
amount: pledge_coin.clone(),
})
.unwrap();
assert_eq!(&expected_msg, msg);
assert!(funds.is_empty())
}
}
assert!(found_track);
}
#[test]
fn returns_error_for_illegal_proxy() {
let mut test = TestSetup::new();
let dummy_proxy = Addr::unchecked("not-vesting-contract");
let env = test.env();
let vesting_contract = test.vesting_contract();
let owner = "mix-owner";
let pledge = Uint128::new(250_000_000);
let mix_id_illegal_proxy =
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(pledge), dummy_proxy.clone());
// this is the halting issue that should have never occurred
let res_other_proxy =
unbond_mixnode(test.deps_mut(), &env, 123, mix_id_illegal_proxy).unwrap_err();
assert_eq!(
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
}
);
}
}
#[cfg(test)]
@@ -1615,11 +1275,9 @@ mod tests {
#[cfg(test)]
mod decreasing_pledge {
use cosmwasm_std::{to_binary, BankMsg, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
use cosmwasm_std::{BankMsg, CosmosMsg, Uint128};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn returns_hard_error_if_mixnode_doesnt_exist() {
@@ -1699,64 +1357,6 @@ mod tests {
)
}
#[test]
fn returns_tokens_back_to_the_proxy_if_bonded_with_vesting() {
let mut test = TestSetup::new();
let owner = "mix-owner";
let mix_id = test.add_dummy_mixnode_with_legal_proxy(owner, None);
test.set_pending_pledge_change(mix_id, None);
let vesting_contract = test.vesting_contract();
let amount = test.coin(12345);
let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
assert_eq!(res.messages.len(), 2);
assert_eq!(
res.messages[0].msg,
CosmosMsg::Bank(BankMsg::Send {
to_address: vesting_contract.to_string(),
amount: vec![amount],
})
)
}
#[test]
fn attaches_vesting_track_message() {
let mut test = TestSetup::new();
let mix_id_no_proxy = test.add_dummy_mixnode("mix-owner1", None);
test.set_pending_pledge_change(mix_id_no_proxy, None);
let mix_id_proxy = test.add_dummy_mixnode_with_legal_proxy("mix-owner2", None);
test.set_pending_pledge_change(mix_id_proxy, None);
let vesting_contract = test.vesting_contract();
let amount = test.coin(12345);
let res_no_proxy =
decrease_pledge(test.deps_mut(), 123, mix_id_no_proxy, amount.clone()).unwrap();
// nothing was attached (apart from bank message tested in `returns_tokens_back_to_the_owner`)
// because it wasn't done with proxy!
assert_eq!(res_no_proxy.messages.len(), 1);
let res_proxy =
decrease_pledge(test.deps_mut(), 123, mix_id_proxy, amount.clone()).unwrap();
assert_eq!(res_proxy.messages.len(), 2);
assert_eq!(
res_proxy.messages[1].msg,
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: vesting_contract.to_string(),
msg: to_binary(&VestingContractExecuteMsg::TrackDecreasePledge {
owner: "mix-owner2".to_string(),
amount,
})
.unwrap(),
funds: vec![],
})
);
}
#[test]
fn without_any_events_in_between_is_equivalent_to_pledging_the_same_amount_immediately() {
let mut test = TestSetup::new();
+4 -10
View File
@@ -159,11 +159,8 @@ mod tests {
}
fn push_dummy_epoch_action(test: &mut TestSetup) {
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let dummy_action =
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
let env = test.env();
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
}
@@ -571,11 +568,8 @@ mod tests {
);
// it exists
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let dummy_action =
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
let env = test.env();
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action.clone()).unwrap();
let expected = PendingEpochEventResponse {
+8 -10
View File
@@ -222,11 +222,10 @@ mod tests {
let env = test.env();
for _ in 0..500 {
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let dummy_action = PendingEpochEventKind::new_undelegate(
Addr::unchecked("foomp"),
test.rng.next_u32(),
);
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
@@ -235,11 +234,10 @@ mod tests {
test.execute_all_pending_events();
for _ in 0..10 {
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let dummy_action = PendingEpochEventKind::new_undelegate(
Addr::unchecked("foomp"),
test.rng.next_u32(),
);
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
+17 -57
View File
@@ -373,18 +373,14 @@ mod tests {
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::Addr;
use mixnet_contract_common::pending_events::PendingEpochEventKind;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) {
// if you attempt to undelegate non-existent delegation,
// it will return an empty response, but will not fail
let env = test.env();
for i in 0..n {
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: i as MixId,
proxy: None,
};
let dummy_action =
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), i as MixId);
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
}
}
@@ -406,7 +402,7 @@ mod tests {
mod performing_pending_epoch_actions {
use super::*;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use cosmwasm_std::{coin, coins, wasm_execute, BankMsg, Empty, SubMsg};
use cosmwasm_std::{coin, coins, BankMsg, Empty, SubMsg};
use mixnet_contract_common::events::{
new_active_set_update_event, new_delegation_on_unbonded_node_event,
new_undelegation_event,
@@ -495,7 +491,6 @@ mod tests {
#[test]
fn catches_all_events_and_messages_from_executed_actions() {
let mut test = TestSetup::new();
let vesting_contract = test.vesting_contract();
let env = test.env();
let legit_mix = test.add_dummy_mixnode("mix-owner", None);
@@ -509,17 +504,15 @@ mod tests {
// delegate to node that doesn't exist,
// we expect to receive BankMsg with tokens being returned,
// and event regarding delegation
let non_existent_delegation = PendingEpochEventKind::Delegate {
owner: Addr::unchecked("foomp"),
mix_id: 123,
amount: coin(123, TEST_COIN_DENOM),
proxy: None,
};
let non_existent_delegation = PendingEpochEventKind::new_delegate(
Addr::unchecked("foomp"),
123,
coin(123, TEST_COIN_DENOM),
);
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
.unwrap();
expected_events.push(new_delegation_on_unbonded_node_event(
&Addr::unchecked("foomp"),
&None,
123,
));
expected_messages.push(SubMsg::new(BankMsg::Send {
@@ -527,33 +520,6 @@ mod tests {
amount: coins(123, TEST_COIN_DENOM),
}));
// delegation to node that doesn't exist with vesting contract
// we expect the same as above PLUS TrackUndelegation message
let non_existent_delegation = PendingEpochEventKind::Delegate {
owner: Addr::unchecked("foomp2"),
mix_id: 123,
amount: coin(123, TEST_COIN_DENOM),
proxy: Some(vesting_contract.clone()),
};
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
.unwrap();
expected_events.push(new_delegation_on_unbonded_node_event(
&Addr::unchecked("foomp2"),
&Some(vesting_contract.clone()),
123,
));
expected_messages.push(SubMsg::new(BankMsg::Send {
to_address: vesting_contract.clone().into_string(),
amount: coins(123, TEST_COIN_DENOM),
}));
let msg = VestingContractExecuteMsg::TrackUndelegation {
owner: "foomp2".to_string(),
mix_id: 123,
amount: coin(123, TEST_COIN_DENOM),
};
let track_undelegate_message = wasm_execute(vesting_contract, &msg, vec![]).unwrap();
expected_messages.push(SubMsg::new(track_undelegate_message));
// updating active set should only emit events and no cosmos messages
let action_with_event = PendingEpochEventKind::UpdateActiveSetSize { new_size: 50 };
storage::push_new_epoch_event(test.deps_mut().storage, &env, action_with_event)
@@ -561,16 +527,12 @@ mod tests {
expected_events.push(new_active_set_update_event(env.block.height, 50));
// undelegation just returns tokens and emits event
let legit_undelegate = PendingEpochEventKind::Undelegate {
owner: delegator.clone(),
mix_id: legit_mix,
proxy: None,
};
let legit_undelegate =
PendingEpochEventKind::new_undelegate(delegator.clone(), legit_mix);
storage::push_new_epoch_event(test.deps_mut().storage, &env, legit_undelegate).unwrap();
expected_events.push(new_undelegation_event(
env.block.height,
&delegator,
&None,
legit_mix,
));
expected_messages.push(SubMsg::new(BankMsg::Send {
@@ -583,9 +545,9 @@ mod tests {
let mut expected = Response::new().add_events(expected_events);
expected.messages = expected_messages;
assert_eq!(res, expected);
assert_eq!(executed, 4);
assert_eq!(executed, 3);
assert_eq!(
4,
3,
storage::LAST_PROCESSED_EPOCH_EVENT
.load(test.deps().storage)
.unwrap()
@@ -1330,17 +1292,15 @@ mod tests {
let mut expected_messages: Vec<SubMsg<Empty>> = Vec::new();
// epoch event
let non_existent_delegation = PendingEpochEventKind::Delegate {
owner: Addr::unchecked("foomp"),
mix_id: 123,
amount: coin(123, TEST_COIN_DENOM),
proxy: None,
};
let non_existent_delegation = PendingEpochEventKind::new_delegate(
Addr::unchecked("foomp"),
123,
coin(123, TEST_COIN_DENOM),
);
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
.unwrap();
expected_events.push(new_delegation_on_unbonded_node_event(
&Addr::unchecked("foomp"),
&None,
123,
));
expected_messages.push(SubMsg::new(BankMsg::Send {
+1
View File
@@ -19,3 +19,4 @@ mod support;
#[cfg(feature = "contract-testing")]
mod testing;
mod vesting_migration;
@@ -45,6 +45,8 @@ pub(crate) mod tests {
minimum_mixnode_delegation: None,
minimum_mixnode_pledge: coin(123u128, "unym"),
minimum_gateway_pledge: coin(456u128, "unym"),
profit_margin: Default::default(),
interval_operating_cost: Default::default(),
},
};
@@ -6,7 +6,7 @@ use cosmwasm_std::{Addr, Storage};
use cosmwasm_std::{Coin, StdResult};
use cw_storage_plus::Item;
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::ContractState;
use mixnet_contract_common::{ContractState, OperatingCostRange, ProfitMarginRange};
pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new(CONTRACT_STATE_KEY);
@@ -28,6 +28,22 @@ pub(crate) fn minimum_gateway_pledge(storage: &dyn Storage) -> Result<Coin, Mixn
.map(|state| state.params.minimum_gateway_pledge)?)
}
pub(crate) fn profit_margin_range(
storage: &dyn Storage,
) -> Result<ProfitMarginRange, MixnetContractError> {
Ok(CONTRACT_STATE
.load(storage)
.map(|state| state.params.profit_margin)?)
}
pub(crate) fn interval_oprating_cost_range(
storage: &dyn Storage,
) -> Result<OperatingCostRange, MixnetContractError> {
Ok(CONTRACT_STATE
.load(storage)
.map(|state| state.params.interval_operating_cost)?)
}
#[allow(unused)]
pub(crate) fn minimum_delegation_stake(
storage: &dyn Storage,
@@ -121,6 +121,8 @@ pub mod tests {
denom,
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT + Uint128::new(1234),
},
profit_margin: Default::default(),
interval_operating_cost: Default::default(),
};
let initial_params = storage::CONTRACT_STATE
+1 -14
View File
@@ -97,7 +97,6 @@ pub(crate) fn save_new_mixnode(
mixnode: MixNode,
cost_params: MixNodeCostParams,
owner: Addr,
proxy: Option<Addr>,
pledge: Coin,
) -> Result<(MixId, Layer), MixnetContractError> {
let layer = assign_layer(storage)?;
@@ -105,15 +104,7 @@ pub(crate) fn save_new_mixnode(
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?;
let mixnode_bond = MixNodeBond::new(
mix_id,
owner,
pledge,
layer,
mixnode,
proxy,
env.block.height,
);
let mixnode_bond = MixNodeBond::new(mix_id, owner, pledge, layer, mixnode, env.block.height);
// save mixnode bond data
// note that this implicitly checks for uniqueness on identity key, sphinx key and owner
@@ -411,7 +402,6 @@ pub(crate) mod tests {
mixnode,
cost_params.clone(),
owner.clone(),
None,
pledge.clone(),
)
.unwrap();
@@ -444,7 +434,6 @@ pub(crate) mod tests {
mixnode,
cost_params.clone(),
Addr::unchecked("different-owner"),
None,
pledge.clone(),
);
assert!(res.is_err());
@@ -457,7 +446,6 @@ pub(crate) mod tests {
mixnode,
cost_params.clone(),
owner,
None,
pledge.clone(),
);
assert!(res.is_err());
@@ -471,7 +459,6 @@ pub(crate) mod tests {
mixnode,
cost_params,
Addr::unchecked("different-owner"),
None,
pledge,
);
assert!(res.is_err());
@@ -12,7 +12,6 @@ use nym_contracts_common::signing::Verifier;
pub(crate) fn verify_mixnode_bonding_signature(
deps: Deps<'_>,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
mixnode: MixNode,
cost_params: MixNodeCostParams,
@@ -23,8 +22,7 @@ pub(crate) fn verify_mixnode_bonding_signature(
// reconstruct the payload
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
let msg =
construct_mixnode_bonding_sign_payload(nonce, sender, proxy, pledge, mixnode, cost_params);
let msg = construct_mixnode_bonding_sign_payload(nonce, sender, pledge, mixnode, cost_params);
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
+36 -576
View File
@@ -1,8 +1,7 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use cosmwasm_std::{coin, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_mixnode_bonding_event, new_mixnode_config_update_event,
@@ -25,8 +24,8 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
validate_pledge,
ensure_no_pending_pledge_changes, ensure_operating_cost_within_range,
ensure_profit_margin_within_range, validate_pledge,
};
use super::storage;
@@ -61,74 +60,30 @@ pub fn assign_mixnode_layer(
Ok(Response::default())
}
pub fn try_add_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
_try_add_mixnode(
deps,
env,
mix_node,
cost_params,
info.funds,
info.sender,
owner_signature,
None,
)
}
pub fn try_add_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner: String,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_add_mixnode(
deps,
env,
mix_node,
cost_params,
info.funds,
owner,
owner_signature,
Some(proxy),
)
}
// I'm not entirely sure how to deal with this warning at the current moment
//
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
// so that we could return a better error message if it doesn't match?
#[allow(clippy::too_many_arguments)]
fn _try_add_mixnode(
pub(crate) fn try_add_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mixnode: MixNode,
cost_params: MixNodeCostParams,
pledge: Vec<Coin>,
owner: Addr,
owner_signature: MessageSignature,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// ensure the profit margin is within the defined range
ensure_profit_margin_within_range(deps.storage, cost_params.profit_margin_percent)?;
// ensure the operating cost is within the defined range
ensure_operating_cost_within_range(deps.storage, &cost_params.interval_operating_cost)?;
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
let pledge = validate_pledge(pledge, minimum_pledge)?;
let pledge = validate_pledge(info.funds, minimum_pledge)?;
// if the client has an active bonded mixnode or gateway, don't allow bonding
// note that this has to be done explicitly as `UniqueIndex` constraint would not protect us
// against attempting to use different node types (i.e. gateways and mixnodes)
ensure_no_existing_bond(&owner, deps.storage)?;
ensure_no_existing_bond(&info.sender, deps.storage)?;
// there's no need to explicitly check whether there already exists mixnode with the same
// identity or sphinx keys as this is going to be done implicitly when attempting to save
@@ -137,8 +92,7 @@ fn _try_add_mixnode(
// check if this sender actually owns the mixnode by checking the signature
verify_mixnode_bonding_signature(
deps.as_ref(),
owner.clone(),
proxy.clone(),
info.sender.clone(),
pledge.clone(),
mixnode.clone(),
cost_params.clone(),
@@ -146,7 +100,7 @@ fn _try_add_mixnode(
)?;
// update the signing nonce associated with this sender so that the future signature would be made on the new value
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?;
let node_identity = mixnode.identity_key.clone();
let (node_id, layer) = save_new_mixnode(
@@ -154,14 +108,12 @@ fn _try_add_mixnode(
env,
mixnode,
cost_params,
owner.clone(),
proxy.clone(),
info.sender.clone(),
pledge.clone(),
)?;
Ok(Response::new().add_event(new_mixnode_bonding_event(
&owner,
&proxy,
&info.sender,
&pledge,
&node_identity,
node_id,
@@ -174,43 +126,19 @@ pub fn try_increase_pledge(
env: Env,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
_try_increase_pledge(deps, env, info.funds, info.sender, None)
}
pub fn try_increase_pledge_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_increase_pledge(deps, env, info.funds, owner, Some(proxy))
}
pub fn _try_increase_pledge(
deps: DepsMut<'_>,
env: Env,
increase: Vec<Coin>,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
let mut pending_changes = mix_details.pending_changes;
let mix_id = mix_details.mix_id();
// increasing pledge is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
ensure_no_pending_pledge_changes(&pending_changes)?;
let rewarding_denom = rewarding_denom(deps.storage)?;
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
let pledge_increase = validate_pledge(info.funds, coin(1, rewarding_denom))?;
let cosmos_event = new_pending_pledge_increase_event(mix_id, &pledge_increase);
@@ -232,39 +160,14 @@ pub fn try_decrease_pledge(
info: MessageInfo,
decrease_by: Coin,
) -> Result<Response, MixnetContractError> {
_try_decrease_pledge(deps, env, decrease_by, info.sender, None)
}
pub fn try_decrease_pledge_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
decrease_by: Coin,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_decrease_pledge(deps, env, decrease_by, owner, Some(proxy))
}
pub fn _try_decrease_pledge(
deps: DepsMut<'_>,
env: Env,
decrease_by: Coin,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
let mut pending_changes = mix_details.pending_changes;
let mix_id = mix_details.mix_id();
// decreasing pledge is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
ensure_no_pending_pledge_changes(&pending_changes)?;
@@ -312,34 +215,12 @@ pub fn _try_decrease_pledge(
Ok(Response::new().add_event(cosmos_event))
}
pub fn try_remove_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_remove_mixnode(deps, env, owner, Some(proxy))
}
pub fn try_remove_mixnode(
pub(crate) fn try_remove_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
_try_remove_mixnode(deps, env, info.sender, None)
}
pub(crate) fn _try_remove_mixnode(
deps: DepsMut<'_>,
env: Env,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
let pending_changes = storage::PENDING_MIXNODE_CHANGES
.may_load(deps.storage, existing_bond.mix_id)?
.unwrap_or_default();
@@ -348,15 +229,12 @@ pub(crate) fn _try_remove_mixnode(
ensure_epoch_in_progress_state(deps.storage)?;
// see if the proxy matches
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
ensure_bonded(&existing_bond)?;
// if there are any pending requests to change the pledge, wait for them to resolve before allowing the unbonding
ensure_no_pending_pledge_changes(&pending_changes)?;
// set `is_unbonding` field
// clippy beta 1.70.0-beta.1 false positive
#[allow(clippy::redundant_clone)]
let mut updated_bond = existing_bond.clone();
updated_bond.is_unbonding = true;
storage::mixnode_bonds().replace(
@@ -375,7 +253,6 @@ pub(crate) fn _try_remove_mixnode(
Ok(
Response::new().add_event(new_pending_mixnode_unbonding_event(
&existing_bond.owner,
&existing_bond.proxy,
existing_bond.identity(),
existing_bond.mix_id,
)),
@@ -387,39 +264,13 @@ pub(crate) fn try_update_mixnode_config(
info: MessageInfo,
new_config: MixNodeConfigUpdate,
) -> Result<Response, MixnetContractError> {
let owner = info.sender;
_try_update_mixnode_config(deps, new_config, owner, None)
}
pub(crate) fn try_update_mixnode_config_on_behalf(
deps: DepsMut<'_>,
info: MessageInfo,
new_config: MixNodeConfigUpdate,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner = deps.api.addr_validate(&owner)?;
let proxy = info.sender;
_try_update_mixnode_config(deps, new_config, owner, Some(proxy))
}
pub(crate) fn _try_update_mixnode_config(
deps: DepsMut<'_>,
new_config: MixNodeConfigUpdate,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
ensure_bonded(&existing_bond)?;
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
let cfg_update_event =
new_mixnode_config_update_event(existing_bond.mix_id, &owner, &proxy, &new_config);
new_mixnode_config_update_event(existing_bond.mix_id, &info.sender, &new_config);
// clippy beta 1.70.0-beta.1 false positive
#[allow(clippy::redundant_clone)]
let mut updated_bond = existing_bond.clone();
updated_bond.mix_node.host = new_config.host;
updated_bond.mix_node.mix_port = new_config.mix_port;
@@ -442,45 +293,24 @@ pub(crate) fn try_update_mixnode_cost_params(
env: Env,
info: MessageInfo,
new_costs: MixNodeCostParams,
) -> Result<Response, MixnetContractError> {
let owner = info.sender;
_try_update_mixnode_cost_params(deps, env, new_costs, owner, None)
}
pub(crate) fn try_update_mixnode_cost_params_on_behalf(
deps: DepsMut,
env: Env,
info: MessageInfo,
new_costs: MixNodeCostParams,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner = deps.api.addr_validate(&owner)?;
let proxy = info.sender;
_try_update_mixnode_cost_params(deps, env, new_costs, owner, Some(proxy))
}
pub(crate) fn _try_update_mixnode_cost_params(
deps: DepsMut,
env: Env,
new_costs: MixNodeCostParams,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// see if the node still exists
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
// changing cost params is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
ensure_bonded(&existing_bond)?;
// ensure the profit margin is within the defined range
ensure_profit_margin_within_range(deps.storage, new_costs.profit_margin_percent)?;
// ensure the operating cost is within the defined range
ensure_operating_cost_within_range(deps.storage, &new_costs.interval_operating_cost)?;
let cosmos_event = new_mixnode_pending_cost_params_update_event(
existing_bond.mix_id,
&owner,
&proxy,
&info.sender,
&new_costs,
);
@@ -497,7 +327,7 @@ pub(crate) fn _try_update_mixnode_cost_params(
#[cfg(test)]
pub mod tests {
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Order, StdResult, Uint128};
use cosmwasm_std::{Addr, Order, StdResult, Uint128};
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{EpochState, EpochStatus, ExecuteMsg, LayerDistribution, Percent};
@@ -680,38 +510,6 @@ pub mod tests {
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
}
#[test]
fn mixnode_add_with_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
let (mixnode, sig, _) = test.mixnode_with_signature(owner, None);
let cost_params = fixtures::mix_node_cost_params_fixture();
let res = try_add_mixnode_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &good_mixnode_pledge()),
mixnode,
cost_params,
owner.to_string(),
sig,
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
#[test]
fn removing_mixnode_cant_be_performed_if_epoch_transition_is_in_progress() {
let bad_states = vec![
@@ -761,23 +559,6 @@ pub mod tests {
);
let mix_id = test.add_dummy_mixnode(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_remove_mixnode_on_behalf(
test.deps_mut(),
env.clone(),
mock_info(vesting_contract.as_ref(), &[]),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
})
);
// "normal" unbonding succeeds and unbonding event is pushed to the pending epoch events
let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone());
@@ -799,35 +580,6 @@ pub mod tests {
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
fn mixnode_remove_with_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let res = try_remove_mixnode_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[]),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
#[test]
fn mixnode_remove_is_not_allowed_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
@@ -908,22 +660,7 @@ pub mod tests {
);
let mix_id = test.add_dummy_mixnode(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_update_mixnode_config_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
update.clone(),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
})
);
// "normal" update succeeds
let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone());
assert!(res.is_ok());
@@ -943,41 +680,6 @@ pub mod tests {
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
fn updating_mixnode_config_with_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let update = MixNodeConfigUpdate {
host: "1.1.1.1:1234".to_string(),
mix_port: 1234,
verloc_port: 1235,
http_api_port: 1236,
version: "v1.2.3".to_string(),
};
let res = try_update_mixnode_config_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
update,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
#[test]
fn mixnode_cost_params_cant_be_updated_when_epoch_transition_is_in_progress() {
let bad_states = vec![
@@ -1042,23 +744,7 @@ pub mod tests {
);
let mix_id = test.add_dummy_mixnode(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_update_mixnode_cost_params_on_behalf(
test.deps_mut(),
env.clone(),
mock_info(vesting_contract.as_ref(), &[]),
update.clone(),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
})
);
// "normal" update succeeds
let res = try_update_mixnode_cost_params(
test.deps_mut(),
@@ -1099,40 +785,6 @@ pub mod tests {
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
fn updating_mixnode_cost_params_with_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let update = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(42).unwrap(),
interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM),
};
let res = try_update_mixnode_cost_params_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[]),
update,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
#[test]
fn adding_mixnode_with_duplicate_sphinx_key_errors_out() {
let mut test = TestSetup::new();
@@ -1240,69 +892,6 @@ pub mod tests {
)
}
#[test]
fn is_not_allowed_if_theres_proxy_mismatch() {
let mut test = TestSetup::new();
let env = test.env();
let owner_without_proxy = Addr::unchecked("no-proxy");
let owner_with_proxy = Addr::unchecked("with-proxy");
let proxy = Addr::unchecked("proxy");
let wrong_proxy = Addr::unchecked("unrelated-proxy");
test.add_dummy_mixnode(owner_without_proxy.as_str(), None);
test.add_dummy_mixnode_with_illegal_proxy(
owner_with_proxy.as_str(),
None,
proxy.clone(),
);
let res = _try_increase_pledge(
test.deps_mut(),
env.clone(),
Vec::new(),
owner_without_proxy.clone(),
Some(proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: "proxy".to_string(),
})
);
let res = _try_increase_pledge(
test.deps_mut(),
env.clone(),
Vec::new(),
owner_with_proxy.clone(),
None,
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "None".to_string(),
})
);
let res = _try_increase_pledge(
test.deps_mut(),
env,
Vec::new(),
owner_with_proxy.clone(),
Some(wrong_proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "unrelated-proxy".to_string(),
})
)
}
#[test]
fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
let mut test = TestSetup::new();
@@ -1457,35 +1046,6 @@ pub mod tests {
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let res = try_increase_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
#[cfg(test)]
@@ -1546,73 +1106,6 @@ pub mod tests {
)
}
#[test]
fn is_not_allowed_if_theres_proxy_mismatch() {
let mut test = TestSetup::new();
let env = test.env();
let owner_without_proxy = Addr::unchecked("no-proxy");
let owner_with_proxy = Addr::unchecked("with-proxy");
let proxy = Addr::unchecked("proxy");
let wrong_proxy = Addr::unchecked("unrelated-proxy");
// just to make sure that after decrease the value would still be above the minimum
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
test.add_dummy_mixnode(owner_without_proxy.as_str(), Some(stake));
test.add_dummy_mixnode_with_illegal_proxy(
owner_with_proxy.as_str(),
Some(stake),
proxy.clone(),
);
let res = _try_decrease_pledge(
test.deps_mut(),
env.clone(),
decrease.clone(),
owner_without_proxy.clone(),
Some(proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: "proxy".to_string(),
})
);
let res = _try_decrease_pledge(
test.deps_mut(),
env.clone(),
decrease.clone(),
owner_with_proxy.clone(),
None,
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "None".to_string(),
})
);
let res = _try_decrease_pledge(
test.deps_mut(),
env,
decrease,
owner_with_proxy.clone(),
Some(wrong_proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "unrelated-proxy".to_string(),
})
)
}
#[test]
fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
let mut test = TestSetup::new();
@@ -1809,38 +1302,5 @@ pub mod tests {
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(stake), illegal_proxy.clone());
let res = try_decrease_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
decrease,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
}
+49
View File
@@ -1,2 +1,51 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::interval::storage as interval_storage;
use cosmwasm_std::{DepsMut, Order, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::PendingEpochEventKind;
fn ensure_no_pending_proxy_events(storage: &dyn Storage) -> Result<(), MixnetContractError> {
let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(storage)?;
let last_inserted = interval_storage::EPOCH_EVENT_ID_COUNTER.load(storage)?;
// no pending events
if last_executed == last_inserted {
return Ok(());
}
for maybe_event in
interval_storage::PENDING_EPOCH_EVENTS.range(storage, None, None, Order::Ascending)
{
let (id, event_data) = maybe_event?;
match event_data.kind {
PendingEpochEventKind::Delegate { proxy, .. } => {
if proxy.is_some() {
return Err(MixnetContractError::FailedMigration {
comment: format!(
"there is a pending vesting contract delegation with id {id}"
),
});
}
}
PendingEpochEventKind::Undelegate { proxy, .. } => {
if proxy.is_some() {
return Err(MixnetContractError::FailedMigration {
comment: format!(
"there is a pending vesting contract undelegation with id {id}"
),
});
}
}
_ => continue,
}
}
Ok(())
}
pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> {
ensure_no_pending_proxy_events(deps.storage)?;
Ok(())
}
+42 -194
View File
@@ -1,8 +1,20 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response};
use super::storage;
use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_storage;
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::helpers;
use crate::rewards::helpers::update_and_save_last_rewarded;
use crate::support::helpers::{
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
AttachSendTokens,
};
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_active_set_update_event, new_mix_rewarding_event,
@@ -16,22 +28,6 @@ use mixnet_contract_common::reward_params::{
IntervalRewardingParamsUpdate, NodeRewardParams, Performance,
};
use mixnet_contract_common::{Delegation, EpochState, MixId};
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_storage;
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::helpers;
use crate::rewards::helpers::update_and_save_last_rewarded;
use crate::support::helpers::{
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
ensure_proxy_match, ensure_sent_by_vesting_contract, send_to_proxy_or_owner,
};
use super::storage;
pub(crate) fn try_reward_mixnode(
deps: DepsMut<'_>,
@@ -111,6 +107,14 @@ pub(crate) fn try_reward_mixnode(
);
}
// make sure node's profit margin is within the allowed range,
// if not adjust it accordingly
let params = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params;
mix_rewarding.normalise_profit_margin(params.profit_margin);
mix_rewarding.normalise_operating_cost(params.interval_operating_cost);
let rewarding_params = storage::REWARDING_PARAMS.load(deps.storage)?;
let node_reward_params = NodeRewardParams::new(node_performance, node_status.is_active());
@@ -140,37 +144,16 @@ pub(crate) fn try_withdraw_operator_reward(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
_try_withdraw_operator_reward(deps, info.sender, None)
}
pub(crate) fn try_withdraw_operator_reward_on_behalf(
deps: DepsMut<'_>,
info: MessageInfo,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_withdraw_operator_reward(deps, owner, Some(proxy))
}
pub(crate) fn _try_withdraw_operator_reward(
deps: DepsMut<'_>,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// we need to grab all of the node's details so we'd known original pledge alongside
// we need to grab all of the node's details, so we'd known original pledge alongside
// all the earned rewards (and obviously to know if this node even exists and is still
// in the bonded state)
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?.ok_or(
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or(
MixnetContractError::NoAssociatedMixNodeBond {
owner: owner.clone(),
owner: info.sender.clone(),
},
)?;
let mix_id = mix_details.mix_id();
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
let reward = helpers::withdraw_operator_reward(deps.storage, mix_details)?;
@@ -178,26 +161,13 @@ pub(crate) fn _try_withdraw_operator_reward(
// if the reward is zero, don't track or send anything - there's no point
if !reward.amount.is_zero() {
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]);
response = response.add_message(return_tokens);
if let Some(proxy) = &proxy {
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
// otherwise, we don't care
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
if proxy == vesting_contract {
let msg = VestingContractExecuteMsg::TrackReward {
amount: reward.clone(),
address: owner.clone().into_string(),
};
let track_reward_message = wasm_execute(proxy, &msg, vec![])?;
response = response.add_message(track_reward_message);
}
}
response = response.send_tokens(&info.sender, reward.clone())
}
Ok(response.add_event(new_withdraw_operator_reward_event(
&owner, &proxy, reward, mix_id,
&info.sender,
reward,
mix_id,
)))
}
@@ -205,37 +175,15 @@ pub(crate) fn try_withdraw_delegator_reward(
deps: DepsMut<'_>,
info: MessageInfo,
mix_id: MixId,
) -> Result<Response, MixnetContractError> {
_try_withdraw_delegator_reward(deps, mix_id, info.sender, None)
}
pub(crate) fn try_withdraw_delegator_reward_on_behalf(
deps: DepsMut<'_>,
info: MessageInfo,
mix_id: MixId,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_withdraw_delegator_reward(deps, mix_id, owner, Some(proxy))
}
pub(crate) fn _try_withdraw_delegator_reward(
deps: DepsMut<'_>,
mix_id: MixId,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// see if the delegation even exists
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
None => {
return Err(MixnetContractError::NoMixnodeDelegationFound {
mix_id,
address: owner.into_string(),
proxy: proxy.map(Addr::into_string),
address: info.sender.into_string(),
proxy: None,
});
}
Some(delegation) => delegation,
@@ -257,33 +205,18 @@ pub(crate) fn _try_withdraw_delegator_reward(
_ => (),
};
ensure_proxy_match(&proxy, &delegation.proxy)?;
let reward = helpers::withdraw_delegator_reward(deps.storage, delegation, mix_rewarding)?;
let mut response = Response::new();
// if the reward is zero, don't track or send anything - there's no point
if !reward.amount.is_zero() {
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]);
response = response.add_message(return_tokens);
if let Some(proxy) = &proxy {
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
// otherwise, we don't care
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
if proxy == vesting_contract {
let msg = VestingContractExecuteMsg::TrackReward {
amount: reward.clone(),
address: owner.clone().into_string(),
};
let track_reward_message = wasm_execute(proxy, &msg, vec![])?;
response = response.add_message(track_reward_message);
}
}
response = response.send_tokens(&info.sender, reward.clone())
}
Ok(response.add_event(new_withdraw_delegator_reward_event(
&owner, &proxy, reward, mix_id,
&info.sender,
reward,
mix_id,
)))
}
@@ -1405,13 +1338,10 @@ pub mod tests {
#[cfg(test)]
mod withdrawing_delegator_reward {
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::interval::pending_events;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
use cosmwasm_std::{BankMsg, CosmosMsg, Decimal, Uint128};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
@@ -1742,60 +1672,14 @@ pub mod tests {
let accumulated_actual = truncate_reward_amount(accumulated_quad);
assert_eq_with_leeway(total_claimed, accumulated_actual, Uint128::new(6));
}
#[test]
fn fails_for_illegal_proxy() {
let test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let mut test = TestSetup::new();
let mix_id =
test.add_dummy_mixnode("mix-owner1", Some(Uint128::new(1_000_000_000_000)));
let delegator = "delegator";
test.add_immediate_delegation_with_illegal_proxy(
delegator,
100_000_000u128,
mix_id,
illegal_proxy.clone(),
);
// reward the node
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id]);
test.start_epoch_transition();
test.reward_with_distribution(mix_id, test_helpers::performance(100.0));
let res = try_withdraw_delegator_reward_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
mix_id,
delegator.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
#[cfg(test)]
mod withdrawing_operator_reward {
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128};
use crate::interval::pending_events;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::TestSetup;
use super::*;
use crate::interval::pending_events;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::{Addr, BankMsg, CosmosMsg, Uint128};
#[test]
fn can_only_be_done_if_bond_exists() {
@@ -1908,42 +1792,6 @@ pub mod tests {
})
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "mix-owner1";
let mix_id = test.add_dummy_mixnode_with_illegal_proxy(
owner,
Some(Uint128::new(1_000_000_000_000)),
illegal_proxy.clone(),
);
// reward the node
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id]);
test.start_epoch_transition();
test.reward_with_distribution(mix_id, test_helpers::performance(100.0));
let res = try_withdraw_operator_reward_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
#[cfg(test)]
+42 -173
View File
@@ -4,11 +4,11 @@
use crate::gateways::storage as gateways_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixNodeBond};
use nym_contracts_common::Percent;
// helper trait to attach `Msg` to a response if it's provided
#[allow(dead_code)]
@@ -26,131 +26,16 @@ impl<T> AttachOptionalMessage<T> for Response<T> {
}
}
// another helper trait to remove some duplicate code and consolidate comments regarding
// possible epoch progression halting behaviour
pub(crate) trait VestingTracking
where
Self: Sized,
{
fn maybe_add_track_vesting_undelegation_message(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
mix_id: MixId,
amount: Coin,
) -> Result<Self, MixnetContractError>;
fn maybe_add_track_vesting_unbond_mixnode_message(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError>;
fn maybe_add_track_vesting_decrease_mixnode_pledge(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError>;
pub(crate) trait AttachSendTokens {
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self;
}
impl VestingTracking for Response {
fn maybe_add_track_vesting_undelegation_message(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
mix_id: MixId,
amount: Coin,
) -> Result<Self, MixnetContractError> {
// if there's a proxy set (i.e. the vesting contract), send the track message
if let Some(proxy) = proxy {
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
// Note: this can INTENTIONALLY cause epoch progression halt if the proxy is not the vesting contract
// But this is fine, since this situation should have NEVER occurred in the first place
// (as all 'on_behalf' methods, including 'DelegateToMixnodeOnBehalf' that got us here,
// explicitly require the proxy to be the vesting contract)
// 'fixing' it would require manually inspecting the problematic event, investigating
// it's cause and manually (presumably via migration) clearing it.
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy,
vesting_contract,
});
}
let msg = VestingContractExecuteMsg::TrackUndelegation {
owner,
mix_id,
amount,
};
let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?;
Ok(self.add_message(track_undelegate_message))
} else {
// there's no proxy so nothing to do
Ok(self)
}
}
fn maybe_add_track_vesting_unbond_mixnode_message(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError> {
// if there's a proxy set (i.e. the vesting contract), send the track message
if let Some(proxy) = proxy {
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
// exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`.
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy,
vesting_contract,
});
}
let msg = VestingContractExecuteMsg::TrackUnbondMixnode { owner, amount };
let track_unbond_message = wasm_execute(proxy, &msg, vec![])?;
Ok(self.add_message(track_unbond_message))
} else {
// there's no proxy so nothing to do
Ok(self)
}
}
fn maybe_add_track_vesting_decrease_mixnode_pledge(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError> {
if let Some(proxy) = proxy {
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
// exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`.
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy,
vesting_contract,
});
}
let msg = VestingContractExecuteMsg::TrackDecreasePledge { owner, amount };
let track_decrease_pledge_message = wasm_execute(proxy, &msg, vec![])?;
Ok(self.add_message(track_decrease_pledge_message))
} else {
// there's no proxy so nothing to do
Ok(self)
}
impl<T> AttachSendTokens for Response<T> {
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self {
self.add_message(BankMsg::Send {
to_address: to.as_ref().to_string(),
amount: vec![amount],
})
}
}
@@ -158,20 +43,6 @@ impl VestingTracking for Response {
// api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
// }
/// Attempts to construct a `BankMsg` to send specified tokens to the provided
/// proxy address. If that's unavailable, the `BankMsg` will use the "owner" as the
/// "to_address".
pub(crate) fn send_to_proxy_or_owner(
proxy: &Option<Addr>,
owner: &Addr,
amount: Vec<Coin>,
) -> BankMsg {
BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(owner).to_string(),
amount,
}
}
pub(crate) fn validate_pledge(
mut pledge: Vec<Coin>,
minimum_pledge: Coin,
@@ -337,39 +208,6 @@ pub(crate) fn ensure_is_owner(
Ok(())
}
pub(crate) fn ensure_proxy_match(
actual: &Option<Addr>,
expected: &Option<Addr>,
) -> Result<(), MixnetContractError> {
if actual != expected {
return Err(MixnetContractError::ProxyMismatch {
existing: expected
.as_ref()
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
incoming: actual
.as_ref()
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
});
}
Ok(())
}
pub(crate) fn ensure_sent_by_vesting_contract(
info: &MessageInfo,
storage: &dyn Storage,
) -> Result<(), MixnetContractError> {
let vesting_contract_address =
crate::mixnet_contract_settings::storage::vesting_contract_address(storage)?;
if info.sender != vesting_contract_address {
Err(MixnetContractError::SenderIsNotVestingContract {
received: info.sender.clone(),
vesting_contract: vesting_contract_address,
})
} else {
Ok(())
}
}
pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> {
if bond.is_unbonding {
return Err(MixnetContractError::MixnodeIsUnbonding {
@@ -431,3 +269,34 @@ pub(crate) fn decode_ed25519_identity_key(
Ok(public_key)
}
pub(crate) fn ensure_profit_margin_within_range(
storage: &dyn Storage,
profit_margin: Percent,
) -> Result<(), MixnetContractError> {
let range = mixnet_params_storage::profit_margin_range(storage)?;
if !range.within_range(profit_margin) {
return Err(MixnetContractError::ProfitMarginOutsideRange {
provided: profit_margin,
range,
});
}
Ok(())
}
pub fn ensure_operating_cost_within_range(
storage: &dyn Storage,
operating_cost: &Coin,
) -> Result<(), MixnetContractError> {
let range = mixnet_params_storage::interval_oprating_cost_range(storage)?;
if !range.within_range(operating_cost.amount) {
return Err(MixnetContractError::OperatingCostOutsideRange {
denom: operating_cost.denom.clone(),
provided: operating_cost.amount,
range,
});
}
Ok(())
}
@@ -22,7 +22,7 @@ pub(crate) fn valid_bond_gateway_msg(
..tests::fixtures::gateway_fixture()
};
let msg = gateway_bonding_sign_payload(deps, sender, None, gateway.clone(), stake);
let msg = gateway_bonding_sign_payload(deps, sender, gateway.clone(), stake);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let identity_key = keypair.public_key().to_base58_string();

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