Compare commits

...

105 Commits

Author SHA1 Message Date
Gala 47cae50e68 profit margin only for mixnode 2022-10-04 11:51:34 +02:00
Gala c644956576 wip 2022-09-29 17:08:31 +03:00
Gala c329724f8c Merge branch 'develop' into node-settings-copy 2022-09-29 15:44:52 +03:00
Jędrzej Stuczyński 49b3a5aa90 Made config.toml values in validator-api take precedence over mainnet defaults (#1645)
* Made config.toml values in validator-api take precedence over mainnet defaults

* Updated mixnode and gateway configs with similar priority adjustments

* Changelog
2022-09-23 16:59:42 +01:00
Jędrzej Stuczyński 879ce3f2d5 Feature/field renaming (#1654)
* Replaced serde renames to aliases

Ideally I would have removed all serde macros, but then it would have broken existing QA deployments - perhaps we should do it later

* Renamed 'node_id' in Delegation to 'mix_id'

* Further renamings of 'node_id' to 'mix_id' in various places
2022-09-23 15:01:39 +01:00
Pierre Dommerc 996f0bf732 feature(explorer-api): rewrite geoip2 location service (#1651) 2022-09-23 14:53:56 +02:00
Jędrzej Stuczyński b289a3570a Prefixing all mixnet contract events with a v2_ prefix (#1652) 2022-09-23 12:33:29 +01:00
Gala 1b689edb43 Merge pull request #1653 from nymtech/ne-stak-sat-filter
NE: fix upper saturation value used on filters
2022-09-23 11:37:47 +02:00
Jon Häggblad 95c5b70eb7 Remove the old deprecated parts of network-defaults (#1646)
* network-defaults: remove all the old crap and simplity

* validator-client: remove commented out code
2022-09-23 11:32:23 +02:00
Jędrzej Stuczyński a5b4504b0a Emitting information about prior delegation data before rewarding (#1650)
* Emitting information about prior delegation data before rewarding

* Cargo fmt
2022-09-23 09:32:37 +01:00
Gala 995a61b7ea fix upper saturation value use on filters 2022-09-23 10:20:51 +02:00
Drazen Urch 0adf4df094 Fig and shell completions (#1638)
* Fig and shell completions lib

* Complete all the things
2022-09-22 17:26:37 +02:00
Gala 8c877d64d6 Merge pull request #1649 from nymtech/fix-validator-url-access
NE: Fix validators url
2022-09-22 15:32:34 +02:00
Gala 9ae1f046c4 avoid destructuring 2022-09-22 15:13:33 +02:00
Gala 7dc776f98a wip fixing conflicts 2022-09-22 13:16:39 +02:00
Gala 9717bcbb17 WIP: Merge branch 'develop' into 348-bonding-settings 2022-09-22 12:22:24 +02:00
Gala f401266d1b Merge pull request #1644 from nymtech/ne-transition-v1-v2
NE: Adding a workaround for v1 to v2 transition
2022-09-22 10:47:32 +02:00
Gala 1878b50752 Merge branch 'develop' into ne-transition-v1-v2 2022-09-22 10:34:16 +02:00
Gala 8bd7b69bf9 Merge pull request #1597 from nymtech/305-ui-fixes-inputs
Wallet: address Figma differences on modals, dialogs and inputs
2022-09-22 10:33:28 +02:00
Gala cf2ede1040 adding a temporaly solution for transition v1 to v2 2022-09-21 17:57:19 +02:00
Gala af1bf57f24 anothe conflict fix 2022-09-21 17:41:31 +02:00
Gala 0de6a0f1ca Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-21 17:41:07 +02:00
Gala 978cbc4f00 fix conflicts 2022-09-21 17:03:56 +02:00
Gala ebb06d4beb Merge branch 'develop' - wip fixing clonflicts 2022-09-21 17:03:47 +02:00
Gala 03974f9cb3 Merge pull request #1628 from nymtech/409-ne-filters-info
NE & Wallet: NE TableToolbar ui and Wallet new display when no delegations
2022-09-21 16:52:21 +02:00
Jędrzej Stuczyński d322f5e91b Removed the concept of exiting validator API after X consecutive failures (#1643) 2022-09-21 15:20:33 +01:00
Gala 0dee6d9db7 spacing between title and content on pages 2022-09-21 16:05:03 +02:00
Gala 05bd6d6a9a signup mnemonic change 2022-09-21 16:04:06 +02:00
Jędrzej Stuczyński c43dbf6f4d Feature/remove deprecated routes (#1642)
* Removed deprecated routes in validator-api

* Removed deprecated routes in explorer-api
2022-09-21 14:54:51 +01:00
Dave Hrycyszyn 848ace1e0b Using the pre-built package in the chat demo 2022-09-21 13:28:41 +01:00
Mark Sinclair f98d9d89bc GitHub Actions - add input to manual dispatch
Adds `RUST_FLAGS="--cfg tokio_unstable"` when the input is true to add the tokio console to the validator api
2022-09-21 12:24:09 +01:00
Jon Häggblad b9015c1321 Update to latest set of selection chance buckets (#1626)
* Update to latest set of selection chance buckets

* Fixup after rebase

* Lock file update

* storybook update

* update storybook

Co-authored-by: Gala <calero.vg@gmail.com>
2022-09-21 12:38:42 +02:00
Fouad 59b0fe2f94 change input placeholders to labels (#1575)
* change input placeholders to labels

* use back button on 'show mnemonic' modal
2022-09-21 11:28:30 +01:00
Gala bf98c1b369 Merge branch 'develop' into 409-ne-filters-info 2022-09-21 12:01:01 +02:00
Gala d7e3b2c6f2 update branch 2022-09-21 11:57:54 +02:00
Gala 7302b64be7 Merge pull request #1617 from nymtech/309-figma-diff-mnemonic
Wallet: Figma diff, change mnemonic textfield
2022-09-21 11:55:15 +02:00
Gala d5365a7602 Merge pull request #1524 from nymtech/317-forms-menus-titles-ui
317 forms menus titles UI
2022-09-21 11:54:53 +02:00
Jędrzej Stuczyński 524d563077 Added additional log statement to show config file save location 2022-09-21 10:12:53 +01:00
Jon Häggblad e44ddc419c clippy: fix warnings in beta toolchain (#1639) 2022-09-21 09:25:22 +02:00
Jon Häggblad be20e17ebb ci: add libudev-dev to nightly linux build 2022-09-21 08:52:41 +02:00
Dave Hrycyszyn 7b76beab76 Switch from deprecated substr() to slice() 2022-09-20 16:00:18 +01:00
Bogdan-Ștefan Neacşu 9ed013b418 Add library to communicate with ledger (#1640)
* Add library to communicate with ledger

* Update changelog

* Install libudev-dev on linux
2022-09-20 17:57:14 +03:00
Dave Hrycyszyn 33ae43b86d Removing commented code 2022-09-20 15:44:56 +01:00
Dave Hrycyszyn 2c1ad1388d removing surb noise display 2022-09-20 15:35:53 +01:00
Jędrzej Stuczyński 136666d759 Feature/rewarding revamp (#1472)
* Query for node stake saturation

* Queries for currently pending events

* Rewarded set query

* Moved ContractState to common types

since it's being returned as a result of one of the queries on the mixnet contract and thus it needs to be accessible outside the contract itself

* Cleaend up storage initialisation

* started restoring unit tests

* Removed attached 1ucoin for cross-contract execute msgs

* wip

* query for rewarding details of a mix node

* Changes for mixnodes and gateways

* Furher progress on v2 changelog(-ish) description

* wip

* first version of the description

* mixnode bonding queries tests and fixes

* ibid for storage

* MixnodeEventType enum + created events for missing mixnode txs

* tests for adding new mixnode

* Additional mixnode-related tests + bug fixes

* Display for Percent

* Bunch of tests for try_reward_mixnode

* More tests and fixes

* ibid

* tests for updating rewarding params + important bug fix

* Started removing unused imports

* rewarding queries tests + undelegation bugfix

* A lot of todo()-ing and commenting out unimplemented code

* implements https://github.com/nymtech/team-core/issues/113

* Delegation tests + fixes

* Emiting events by top level interval txs + incorporating limit

* question

* Missing events emissions

* removed some code duplication

* wip

* pending delegation tests

* Vesting contract update

* More tests (and fixes) for pending events txs

* Restored gateway tx tests

* Another cleanup iteration

* removed redundant comment

* Unit tests, fixes and simplifcations for interval-related txs

* Unit tests for helper functions

* Interval queries unit tests

* Test for correct contract initialisation

* Another round of cleanup

* Work on mixnet_query_client trait

* mixnet_signing_client trait

* Removed redundant methods

* Slowly restoring validator client functionality

* Added deprecated query for mix details by identity

* wip restoration of validator-api

* Work on deprecating validator API routes

* Further validator-api routes

* Restored rest of status api routes

* Resolved all todos in ValidatorApiStorage

There's still bunch left in StorageManager though

* Changed NodeId from u64 to u32

* Updating sql code

* Network monitor internals

* Changed behaviour of full_epoch_id and updated epoch operations

* Fixed sql queries

* [most likely] finished updating rest of the validator API

* Post rebasing fixes

* Feature/rewarding revamp explorer api changes (#1511)

* Changed cache to allow for non-string keys

* Helper method for best-effort conversion of pubkey to nodeid

* Updated validator-api client routes

* Updated routes to use mix-id indexing

* Introduction of deprecated routes callable by identity key

* Fixed mixnode compatibility by changing read node details fields (#1512)

* Fixed bond to topology conversion for client compatibility (#1513)

* Updated 'verify_gateway_owner' to use correct nymd_client method for obtaining gateway details (#1515)

* Updated constructor for ValidatorCacheInner

* Fixed wasm client topology construction

* Run cargo fmt on the entire codebase

* Feature/rewarding revamp wallet backend changes (#1529)

* Updated mixnode-related ts types

* Updated nym-wallet-types

* Updated 'get_contract_settings' and commented out code of other tauri commands

* 'update_contract_settings'

* 'bond_gateway'

* unbond_gateway'

* Utility commands for the transition period

* 'bond_mixnode'

* 'unbond_mixnode'

* Ability to update mixnode cost paramaters

* Mixnode config update

* Updated mixnode_bond_details

It also returns a different underlying type now

* Updated 'gateway_bond_details'

* Obtaining pending operator rewards

* Improved way of obtaining number of mixnode delegators

* simplified error handling in 'fetch_mix_node_description'

* mixnode and gateway ownership queries

* updated get_number_of_mixnode_delegators to use mix_id since we have the conversion utils helper

* mixnode delegation

* undelegating

* Obtaining pending delegator rewards

* Command for obtaining current interval details

* Queries to handle paging for pending events

* Additional level of indirection to pending events to incorporate event id into response

* Wallet compatible pending event types

* Commands fo obtaining pending events

* Re-implemented pending delegation events

* Further work on delegation

* Removed unused imports

* Commands for withdrawing rewards

* Admin-related simulations

* mixnet-related simulation commands

* Validator-api related routes

* Bond-related vesting operations

* Vesting simulations

* Vesting handler for UpdateMixnodeCostParams

* Vesting reward claiming

* Vesting queries

* claim_locked_and_unlocked_delegator_reward

* The massive delegation query

* cleanup

* updated typescript requests

* sorted the new type exports in ts-rs-cli

* Regenerated typescript types

* temporarily ignoring unreachable code in vesting migration

* Updated missed test fixture

* Fixed missing coconut-specific import

* cargo fmt

* Exporting reward-related types

* utility to convert stringified decimal to cosmjs Decimal

* deriving Eq alongside PartialEq

* wip - typescript fixes

* using default operating cost when bonding mixnode

* Using default operating cost when updating mixnode cost params

* most delegation fixes

* Wrapping delegation with node identity

* Added MultiIndex on owner and identity key to unbonded mixnodes

* Support for queries for unbonded nodes by owner or by identity key

* Cargo fmt + ts types update

* feature locking unused imports

* fix(nym-wallet): typing and error (#1548)

* post-rebase fixes

* Changed storage key for new delegations map in vesting contract

* fix(wallet): typing issues (#1562)

* fix(wallet): error UI feedback (#1565)

* clean(wallet): remove useless files (with flamethrowers 🔥) (#1567)

* Changed default_mixnode_cost_params to allow accepting f32 instead

* Revert "Changed default_mixnode_cost_params to allow accepting f32 instead"

This reverts commit fb62a0014f.

* Fixed APY calculation for 0 pledge value

* Don't send rewarding transactions for empty rewarded set

* Fixed mixnode rewarding in validator API

* fix(nym-wallet): profit margin (#1574)

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Removed todo!() from vesting contract migration since its going to be dealt with differently

* fix(nym-wallet): stake saturation and delegations (#1578)

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* fix(nym-wallet): get rid of delegation history

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): get rid of delegation history

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): fix clippy

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Updated vesting contract migration to update the mixnet contract address

* feat(wallet): add confirmation/warning modal for unbonding

* Post rebasing fixes

* commented out all code

* Bunch of work in progres, but working simulator

* Removing redundant fields + increased precision to 9decimal places

* deserialization of Percent with value validation

* wip

* Further moving things around + mixnode bonding

* Mixnode unbonding

* Starting restoration on contract state

* Revamping interval

* More work on epoch/interval

* progress on mixnode rewarding

* Moved MixNodeRewarding to rewards storage

* wip on delegations

* Removed concept of periods and historical records and moved cummulative reward ratio directly to delegation

* more wip delegation

* Full delegation flow

* mixnode config updates

* Mixnode cost function updates

* Work on moving mixnode unbonding to post-epoch actions

* Unbonding

* Processing undelegation

* changing cost params

* Uncommented existing gateways features

without much changes so far, however, things like unbonding should probably also go to epoch queue

* ExecuteMsg cleanup

* unit tests for withdrawing rewards against known values

* Transactions for withdrawing rewards

* Transactions for updating various parameters

* First round of post-tx cleanup

* Moved all storage keys to constants.rs

* Using correct initial gateway pledge amount

* Renamed sybil_resistance_percent to sybil_resistance with percent being implicit from the typ

* Starting with contract queries

* Keeping minimal details of unbonded mixnoces

* Checking for owner address rather than rewarding validator when updating rewarding params

* Mixnode-related queries

* Gateway-related queries

* Query for paged unbonded mixnodes

* Delegations queries

* Query for current interval details

* Removed 'fixed' dependency from the mixnet common

* wip on implementing rewards-related queries

* Pending rewards queries

* Query for node stake saturation

* Queries for currently pending events

* Rewarded set query

* Moved ContractState to common types

since it's being returned as a result of one of the queries on the mixnet contract and thus it needs to be accessible outside the contract itself

* Cleaend up storage initialisation

* started restoring unit tests

* Removed attached 1ucoin for cross-contract execute msgs

* wip

* query for rewarding details of a mix node

* Changes for mixnodes and gateways

* Furher progress on v2 changelog(-ish) description

* wip

* first version of the description

* mixnode bonding queries tests and fixes

* ibid for storage

* MixnodeEventType enum + created events for missing mixnode txs

* tests for adding new mixnode

* Additional mixnode-related tests + bug fixes

* Display for Percent

* Bunch of tests for try_reward_mixnode

* More tests and fixes

* ibid

* tests for updating rewarding params + important bug fix

* Started removing unused imports

* rewarding queries tests + undelegation bugfix

* A lot of todo()-ing and commenting out unimplemented code

* implements https://github.com/nymtech/team-core/issues/113

* Delegation tests + fixes

* Emiting events by top level interval txs + incorporating limit

* question

* Missing events emissions

* removed some code duplication

* wip

* pending delegation tests

* Vesting contract update

* More tests (and fixes) for pending events txs

* Restored gateway tx tests

* Another cleanup iteration

* removed redundant comment

* Unit tests, fixes and simplifcations for interval-related txs

* Unit tests for helper functions

* Interval queries unit tests

* Test for correct contract initialisation

* Another round of cleanup

* Work on mixnet_query_client trait

* mixnet_signing_client trait

* Removed redundant methods

* Slowly restoring validator client functionality

* Added deprecated query for mix details by identity

* wip restoration of validator-api

* Work on deprecating validator API routes

* Further validator-api routes

* Restored rest of status api routes

* Resolved all todos in ValidatorApiStorage

There's still bunch left in StorageManager though

* Changed NodeId from u64 to u32

* Updating sql code

* Network monitor internals

* Changed behaviour of full_epoch_id and updated epoch operations

* Fixed sql queries

* [most likely] finished updating rest of the validator API

* Post rebasing fixes

* Feature/rewarding revamp explorer api changes (#1511)

* Changed cache to allow for non-string keys

* Helper method for best-effort conversion of pubkey to nodeid

* Updated validator-api client routes

* Updated routes to use mix-id indexing

* Introduction of deprecated routes callable by identity key

* Fixed mixnode compatibility by changing read node details fields (#1512)

* Fixed bond to topology conversion for client compatibility (#1513)

* Updated 'verify_gateway_owner' to use correct nymd_client method for obtaining gateway details (#1515)

* Updated constructor for ValidatorCacheInner

* Fixed wasm client topology construction

* Run cargo fmt on the entire codebase

* Feature/rewarding revamp wallet backend changes (#1529)

* Updated mixnode-related ts types

* Updated nym-wallet-types

* Updated 'get_contract_settings' and commented out code of other tauri commands

* 'update_contract_settings'

* 'bond_gateway'

* unbond_gateway'

* Utility commands for the transition period

* 'bond_mixnode'

* 'unbond_mixnode'

* Ability to update mixnode cost paramaters

* Mixnode config update

* Updated mixnode_bond_details

It also returns a different underlying type now

* Updated 'gateway_bond_details'

* Obtaining pending operator rewards

* Improved way of obtaining number of mixnode delegators

* simplified error handling in 'fetch_mix_node_description'

* mixnode and gateway ownership queries

* updated get_number_of_mixnode_delegators to use mix_id since we have the conversion utils helper

* mixnode delegation

* undelegating

* Obtaining pending delegator rewards

* Command for obtaining current interval details

* Queries to handle paging for pending events

* Additional level of indirection to pending events to incorporate event id into response

* Wallet compatible pending event types

* Commands fo obtaining pending events

* Re-implemented pending delegation events

* Further work on delegation

* Removed unused imports

* Commands for withdrawing rewards

* Admin-related simulations

* mixnet-related simulation commands

* Validator-api related routes

* Bond-related vesting operations

* Vesting simulations

* Vesting handler for UpdateMixnodeCostParams

* Vesting reward claiming

* Vesting queries

* claim_locked_and_unlocked_delegator_reward

* The massive delegation query

* cleanup

* updated typescript requests

* sorted the new type exports in ts-rs-cli

* Regenerated typescript types

* temporarily ignoring unreachable code in vesting migration

* Updated missed test fixture

* Fixed missing coconut-specific import

* cargo fmt

* Exporting reward-related types

* utility to convert stringified decimal to cosmjs Decimal

* deriving Eq alongside PartialEq

* wip - typescript fixes

* using default operating cost when bonding mixnode

* Using default operating cost when updating mixnode cost params

* most delegation fixes

* Wrapping delegation with node identity

* Added MultiIndex on owner and identity key to unbonded mixnodes

* Support for queries for unbonded nodes by owner or by identity key

* Cargo fmt + ts types update

* feature locking unused imports

* fix(nym-wallet): typing and error (#1548)

* post-rebase fixes

* Changed storage key for new delegations map in vesting contract

* fix(wallet): typing issues (#1562)

* fix(wallet): error UI feedback (#1565)

* clean(wallet): remove useless files (with flamethrowers 🔥) (#1567)

* Changed default_mixnode_cost_params to allow accepting f32 instead

* Revert "Changed default_mixnode_cost_params to allow accepting f32 instead"

This reverts commit fb62a0014f.

* Fixed APY calculation for 0 pledge value

* Don't send rewarding transactions for empty rewarded set

* Fixed mixnode rewarding in validator API

* fix(nym-wallet): profit margin (#1574)

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Removed todo!() from vesting contract migration since its going to be dealt with differently

* fix(nym-wallet): stake saturation and delegations (#1578)

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* fix(nym-wallet): get rid of delegation history

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): get rid of delegation history

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): fix clippy

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Updated vesting contract migration to update the mixnet contract address

* feat(wallet): add confirmation/warning modal for unbonding

* Post rebasing fixes

* Removed deprecation on GetMixnodeDetailsByIdentity

* Fixed nym-cli

* Removed needless borrow

* Updated colorMap and textMap

Co-authored-by: durch <durch@users.noreply.github.com>
Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
Co-authored-by: Pierre Dommerc <dommerc.pierre@gmail.com>
2022-09-20 14:44:57 +01:00
Gala 4648967e93 fix build and refactor from CR 2022-09-20 12:43:51 +02:00
Gala 824e980647 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-20 11:49:33 +02:00
Gala b4fe8af890 refactor from CR 2022-09-19 11:04:26 +02:00
Gala 1c8ab2395d Merge branch 'develop' into 409-ne-filters-info 2022-09-19 10:24:18 +02:00
Gala 02e75ea5cd display different content when user doesn't have delegations 2022-09-16 12:24:32 +02:00
Gala ebb3f6eebb fixing the build 2022-09-15 16:06:14 +02:00
Gala 2c15d22e1e Merge branch 'develop' into 409-ne-filters-info 2022-09-15 15:55:58 +02:00
Gala f1dca2c9a8 styling the mobile view 2022-09-15 14:50:33 +02:00
Gala dee2c50b50 ne toolbar changes 2022-09-14 16:04:39 +02:00
Gala a394c9b59a PR requested changes 2022-09-14 14:48:30 +02:00
Gala 17768bab0b Merge branch 'develop' into 317-forms-menus-titles-ui 2022-09-14 14:11:10 +02:00
Gala 27d566dd47 some styling 2022-09-14 11:46:06 +02:00
Gala bfa0144594 text change 2022-09-14 11:38:10 +02:00
Gala 8924f9642f wip 2022-09-14 11:35:22 +02:00
Gala 58541defad Merge branch 'develop' into 309-figma-diff-mnemonic 2022-09-14 11:29:55 +02:00
Gala c513014913 taking advantage to deal with some other differences 2022-09-14 11:27:41 +02:00
Gala 5985ba5182 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-14 10:08:17 +02:00
Gala ade15d3c60 change mnemonic textfield 2022-09-13 14:53:04 +02:00
Gala 1241a81514 changing alert behaviour 2022-09-13 13:49:42 +02:00
Gala 08a190c1cb Merge branch 'develop' into 348-bonding-settings 2022-09-13 12:44:11 +02:00
Gala 81f36e8da7 some refactor 2022-09-08 13:36:44 +02:00
Gala f230229ce9 change save button label 2022-09-08 12:18:09 +02:00
Gala 912fb4ab38 Merge branch 'develop' into 348-bonding-settings 2022-09-08 12:05:34 +02:00
Gala 1ab6bce821 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-07 11:27:57 +02:00
Gala 70624e9062 modals and dialogs border in dark mode 2022-09-06 16:40:50 +02:00
Gala 2407285121 fixing paddings on modals 2022-09-06 14:33:41 +02:00
Gala db3171ea09 ModalListItem fix font size 2022-09-06 13:58:54 +02:00
Gala 2aaaa0deb7 globally no colon at the end of ModalListItem 2022-09-06 13:55:20 +02:00
Gala d410277d14 change all placeholders on textfields into labels 2022-09-06 13:41:35 +02:00
Gala 99ceabb0b0 using the wallet Tab component 2022-09-06 13:18:43 +02:00
Gala 25df7bcd4d Merge branch 'develop' into 348-bonding-settings 2022-09-02 09:39:21 +02:00
Gala 1cdca7bec3 unbond modal verification step 2022-09-01 16:57:48 +02:00
Gala c809c7733d logic refactor 2022-09-01 15:06:23 +02:00
Gala 7b53003edb wip verification modal 2022-08-31 18:49:47 +02:00
Gala 831d9d2bf8 update alert text 2022-08-31 18:20:40 +02:00
Gala cb7c51ba12 remove node settings modal trigger 2022-08-31 17:37:00 +02:00
Gala 0310f0a8a9 some refactor 2022-08-31 17:23:53 +02:00
Gala bb79d08f6d dynamic values and remove hard coded code 2022-08-31 16:58:53 +02:00
Gala 414c86b500 fix button width 2022-08-31 12:13:40 +02:00
Gala 4304ffcf3c adding notification span 2022-08-31 11:44:23 +02:00
Gala 309b23e18a adding confirmation modals 2022-08-31 10:55:13 +02:00
Gala 52703583f0 adding validation to parameters settings 2022-08-31 10:10:37 +02:00
Gala 6473ef13c6 validate info fields 2022-08-30 18:51:39 +02:00
Gala 6de7d060e3 keep same margin on between pages 2022-08-30 17:00:48 +02:00
Gala 9a45f15ba4 wip 2022-08-30 16:49:07 +02:00
Gala 66b5eb13b0 fixing nymcard title size and margin in delegations page 2022-08-30 14:32:15 +02:00
Gala 4b37d4f050 Merge branch 'develop' into 317-forms-menus-titles-ui 2022-08-30 12:50:11 +02:00
Gala 746795b7ce mook bonded node 2022-08-30 12:49:50 +02:00
Gala 8b81247044 Merge branch 'develop' into 348-bonding-settings 2022-08-30 11:08:19 +02:00
Gala c6cd787950 adding unbonding modal 2022-08-19 18:06:04 +02:00
Gala f9ab20b10f more styling in node
settings page
2022-08-18 17:27:28 +02:00
Gala acffd496ed nav styles 2022-08-18 17:07:59 +02:00
Gala 466ac1a1e0 settings general page 2022-08-18 16:39:05 +02:00
Gala d53adcd17e nodesettings page and logic to browse 2022-08-17 18:55:58 +02:00
Gala 36e82e831f Merge branch 'develop' into 348-bonding-settings 2022-08-17 13:55:06 +02:00
Gala cbe0115f01 wip 2022-08-17 11:10:10 +02:00
Gala 37d501f16d fix delegate more focus state 2022-08-11 16:20:05 +02:00
Gala 1e76169178 fixing spacing in modals 2022-08-11 14:57:37 +02:00
Gala 7406eeff14 ui top bar 2022-08-11 14:11:57 +02:00
Gala bdabe31fc9 side navigation 2022-08-11 14:03:02 +02:00
401 changed files with 25184 additions and 18464 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
+11
View File
@@ -2,6 +2,12 @@ name: Publish Nym binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
release:
types: [created]
@@ -25,6 +31,11 @@ jobs:
with:
script: |
core.setFailed('Release tag did not start with nym-binaries-...')
- name: Sets env vars for tokio if set in manual dispatch inputs
run: |
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
+7
View File
@@ -9,6 +9,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
- common/ledger: new library for communicating with a Ledger device ([#1640])
### Fixed
- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
### Changed
@@ -21,6 +26,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1577]: https://github.com/nymtech/nym/pull/1577
[#1585]: https://github.com/nymtech/nym/pull/1585
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
Generated
+720 -700
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -40,6 +40,7 @@ members = [
"common/crypto/dkg",
"common/execute",
"common/inclusion-probability",
"common/ledger",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
@@ -62,6 +63,7 @@ members = [
"common/topology",
"common/types",
"common/wasm-utils",
"common/completions",
"explorer-api",
"gateway",
"gateway/gateway-requests",
@@ -136,7 +136,7 @@ impl LoopCoverTrafficStream<OsRng> {
let cover_message = generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
&self.ack_key,
&self.our_full_destination,
self.average_ack_delay,
self.average_packet_delay,
@@ -228,7 +228,7 @@ where
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
&self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
@@ -13,7 +13,7 @@ use std::time::Duration;
use task::ShutdownListener;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use topology::{nym_topology_from_bonds, NymTopology};
use topology::{nym_topology_from_detailed, NymTopology};
use url::Url;
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
@@ -266,8 +266,8 @@ impl TopologyRefresher {
};
let mixnodes_count = mixnodes.len();
let topology =
nym_topology_from_bonds(mixnodes, gateways).filter_system_version(&self.client_version);
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if !self.check_layer_distribution(&topology, mixnodes_count) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
+2 -1
View File
@@ -9,7 +9,7 @@ edition = "2021"
async-trait = "0.1.52"
bip39 = "1.0.1"
cfg-if = "0.1"
clap = { version = "3.0.10", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
pickledb = "0.4.1"
rand = "0.7.3"
serde = { version = "1.0", features = ["derive"] }
@@ -19,6 +19,7 @@ tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "m
coconut-interface = { path = "../../common/coconut-interface" }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
+7
View File
@@ -3,6 +3,7 @@
use async_trait::async_trait;
use clap::{Args, Subcommand};
use completions::ArgShell;
use pickledb::PickleDb;
use rand::rngs::OsRng;
use std::str::FromStr;
@@ -28,6 +29,12 @@ pub(crate) enum Commands {
ListDeposits(ListDeposits),
/// Get a credential for a given deposit
GetCredential(GetCredential),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
#[async_trait]
+6
View File
@@ -12,6 +12,8 @@ cfg_if::cfg_if! {
use commands::{Commands, Execute};
use error::Result;
use network_defaults::setup_env;
use clap::CommandFactory;
use completions::fig_generate;
use clap::Parser;
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
@@ -52,10 +54,14 @@ cfg_if::cfg_if! {
),
};
let bin_name = "nym-credential-client";
match &args.command {
Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?,
Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?,
Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name)
}
Ok(())
+2 -1
View File
@@ -20,7 +20,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
# and the single instance of abortable we have should really be refactored anyway
url = "2.2"
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
dirs = "4.0"
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
@@ -34,6 +34,7 @@ tokio-tungstenite = "0.14" # websocket
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
+12
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
pub(crate) mod init;
pub(crate) mod run;
@@ -62,6 +64,12 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -76,10 +84,14 @@ pub(crate) struct OverrideConfig {
}
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-native-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
+2 -2
View File
@@ -103,7 +103,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(&config_version, &to_version);
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
@@ -111,7 +111,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&config_version, &to_version);
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
+2 -1
View File
@@ -11,7 +11,7 @@ name = "nym_socks5"
path = "src/lib.rs"
[dependencies]
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
dirs = "4.0"
futures = "0.3"
log = "0.4"
@@ -27,6 +27,7 @@ url = "2.2"
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
+12
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
use config::parse_validators;
pub mod init;
@@ -63,6 +65,12 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -76,10 +84,14 @@ pub(crate) struct OverrideConfig {
}
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-socks5-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
+2 -2
View File
@@ -102,7 +102,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(&config_version, &to_version);
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
@@ -110,7 +110,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&config_version, &to_version);
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
+4 -4
View File
@@ -25,7 +25,7 @@ async function main() {
set_panic_hook();
// validator server we will use to get topology from
const validator = "https://validator.nymtech.net/api"; //"http://localhost:8081";
const validator = "https://validator.nymtech.net/api";
client = new NymClient(validator);
@@ -69,7 +69,7 @@ async function sendMessageTo() {
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().substr(11, 12);
let timestamp = new Date().toISOString().slice(11, 21);
let sendDiv = document.createElement("div")
let paragraph = document.createElement("p")
@@ -90,11 +90,11 @@ function displayReceived(message) {
const content = message.message
const replySurb = message.replySurb
let timestamp = new Date().toISOString().substr(11, 12);
let timestamp = new Date().toISOString().slice(11, 21);
let receivedDiv = document.createElement("div")
let paragraph = document.createElement("p")
paragraph.setAttribute('style', 'color: green')
let paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
let paragraphContent = document.createTextNode(timestamp + " received >>> " + content)
paragraph.appendChild(paragraphContent)
receivedDiv.appendChild(paragraph)
document.getElementById("output").appendChild(receivedDiv)
+2 -2
View File
@@ -34,6 +34,6 @@
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-client-wasm": "file:../pkg"
"@nymproject/nym-client-wasm": "1.0.0"
}
}
}
+2 -2
View File
@@ -11,7 +11,7 @@ use rand::rngs::OsRng;
use received_processor::ReceivedMessagesProcessor;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, nym_topology_from_bonds, NymTopology};
use topology::{gateway, nym_topology_from_detailed, NymTopology};
use url::Url;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
@@ -265,7 +265,7 @@ impl NymClient {
Ok(gateways) => gateways,
};
let topology = nym_topology_from_bonds(mixnodes, gateways);
let topology = nym_topology_from_detailed(mixnodes, gateways);
let version = env!("CARGO_PKG_VERSION");
topology.filter_system_version(version)
}
@@ -186,7 +186,7 @@ impl Client {
address.into(),
receiver,
initial_connection_timeout,
&*current_reconnection_attempt,
&current_reconnection_attempt,
)
.await
});
+266 -295
View File
@@ -2,35 +2,33 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{validator_api, ValidatorClientError};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::NodeId;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef};
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
#[cfg(feature = "nymd-client")]
use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse};
use crate::nymd::traits::MixnetQueryClient;
#[cfg(feature = "nymd-client")]
use crate::nymd::{
self, error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient};
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval,
MixnetContractVersion, MixnodeRewardingStatusResponse, RewardedSetNodeStatus,
RewardedSetUpdateDetails,
mixnode::MixNodeBond,
pending_events::{PendingEpochEvent, PendingIntervalEvent},
Delegation, RewardedSetNodeStatus, UnbondedMixnode,
};
#[cfg(feature = "nymd-client")]
use network_defaults::NymNetworkDetails;
#[cfg(feature = "nymd-client")]
use std::collections::{HashMap, HashSet};
use validator_api_requests::models::MixNodeBondAnnotated;
#[cfg(feature = "nymd-client")]
#[must_use]
@@ -191,12 +189,9 @@ impl Client<QueryNymdClient> {
}
}
// nymd wrappers
#[cfg(feature = "nymd-client")]
impl<C> Client<C> {
pub fn change_validator_api(&mut self, new_endpoint: Url) {
self.validator_api.change_url(new_endpoint)
}
// use case: somebody initialised client without a contract in order to upload and initialise one
// and now they want to actually use it without making new client
pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) {
@@ -208,203 +203,22 @@ impl<C> Client<C> {
self.nymd.mixnet_contract_address().clone()
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?)
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes_detailed().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_operator_rewards(&self, address: String) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_operator_rewards(address).await?.u128())
}
pub async fn get_delegator_rewards(
&self,
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_delegator_rewards(address, mix_identity, proxy)
.await?
.u128())
}
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
proxy_address: Option<String>,
) -> Result<Vec<DelegationEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_pending_delegation_events(owner_address, proxy_address)
.await?)
}
pub async fn get_current_epoch(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_epoch().await?)
}
pub async fn get_current_operator_cost(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_operator_cost().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
self.nymd.get_mixnet_contract_version().await
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract_common::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
.await?)
}
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_circulating_supply().await?.u128())
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_sybil_resistance_percent().await?)
}
pub async fn get_active_set_work_factor(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_active_set_work_factor().await?)
}
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epochs_in_interval().await?)
}
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_interval_reward_percent().await?)
}
pub async fn get_current_rewarded_set_update_details(
&self,
) -> Result<RewardedSetUpdateDetails, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.query_current_rewarded_set_update_details()
.await?)
}
// basically handles paging for us
pub async fn get_all_nymd_rewarded_set_mixnode_identities(
pub async fn get_all_nymd_rewarded_set_mixnodes(
&self,
) -> Result<Vec<(IdentityKey, RewardedSetNodeStatus)>, ValidatorClientError>
) -> Result<Vec<(NodeId, RewardedSetNodeStatus)>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut identities = Vec::new();
let mut start_after = None;
let mut height = None;
loop {
let mut paged_response = self
.nymd
.get_rewarded_set_identities_paged(
start_after.take(),
self.rewarded_set_page_limit,
height,
)
.get_rewarded_set_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
identities.append(&mut paged_response.identities);
if height.is_none() {
// keep using the same height (the first query happened at the most recent height)
height = Some(paged_response.at_height)
}
identities.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
@@ -416,83 +230,122 @@ impl<C> Client<C> {
Ok(identities)
}
pub async fn get_nymd_rewarded_and_active_sets(
&self,
) -> Result<Vec<(MixNodeBond, RewardedSetNodeStatus)>, ValidatorClientError>
pub async fn get_all_nymd_mixnode_bonds(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.collect::<HashMap<_, _>>();
Ok(all_mixnodes
.into_iter()
.filter_map(|node| {
rewarded_set_identities
.get(node.identity())
.map(|status| (node, *status))
})
.collect())
}
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_rewarded_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.map(|(identity, _status)| identity)
.collect::<HashSet<_>>();
Ok(all_mixnodes
.into_iter()
.filter(|node| rewarded_set_identities.contains(node.identity()))
.collect())
}
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_active_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let active_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.filter_map(|(identity, status)| {
if status.is_active() {
Some(identity)
} else {
None
}
})
.collect::<HashSet<_>>();
Ok(all_mixnodes
.into_iter()
.filter(|node| active_set_identities.contains(node.identity()))
.collect())
}
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnodes_paged(start_after.take(), self.mixnode_page_limit)
.get_mixnode_bonds_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnodes_detailed_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes(
&self,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes_by_owner(
&self,
owner: &cosmrs::AccountId,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_by_owner_paged(owner, self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes_by_identity(
&self,
identity_key: String,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_by_identity_paged(
identity_key.clone(),
self.mixnode_page_limit,
start_after.take(),
)
.await?;
mixnodes.append(&mut paged_response.nodes);
@@ -508,7 +361,7 @@ impl<C> Client<C> {
pub async fn get_all_nymd_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut gateways = Vec::new();
let mut start_after = None;
@@ -531,18 +384,18 @@ impl<C> Client<C> {
pub async fn get_all_nymd_single_mixnode_delegations(
&self,
identity: IdentityKey,
mix_id: NodeId,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mix_delegations_paged(
identity.clone(),
.get_mixnode_delegations_paged(
mix_id,
start_after.take(),
self.mixnode_delegations_page_limit,
)
@@ -564,7 +417,7 @@ impl<C> Client<C> {
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
@@ -589,10 +442,128 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_mixnode_avg_uptimes(
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_all_network_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_pending_epoch_events(
&self,
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_avg_uptimes().await?)
) -> Result<Vec<PendingEpochEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut events = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_pending_epoch_events_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
events.append(&mut paged_response.events);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(events)
}
pub async fn get_all_nymd_pending_interval_events(
&self,
) -> Result<Vec<PendingIntervalEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut events = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_pending_interval_events_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
events.append(&mut paged_response.events);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(events)
}
}
// validator-api wrappers
#[cfg(feature = "nymd-client")]
impl<C> Client<C> {
pub fn change_validator_api(&mut self, new_endpoint: Url) {
self.validator_api.change_url(new_endpoint)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?)
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes_detailed().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn blind_sign(
@@ -630,17 +601,17 @@ impl ApiClient {
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -652,7 +623,7 @@ impl ApiClient {
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
) -> Result<GatewayCoreStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_gateway_core_status_count(identity, since)
@@ -661,39 +632,39 @@ impl ApiClient {
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
) -> Result<MixnodeCoreStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_core_status_count(identity, since)
.get_mixnode_core_status_count(mix_id, since)
.await?)
}
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_status(identity).await?)
Ok(self.validator_api.get_mixnode_status(mix_id).await?)
}
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<RewardEstimationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_reward_estimation(identity)
.get_mixnode_reward_estimation(mix_id)
.await?)
}
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_stake_saturation(identity)
.get_mixnode_stake_saturation(mix_id)
.await?)
}
@@ -1,11 +1,12 @@
use crate::nymd::error::NymdError;
use crate::nymd::{Config as ClientConfig, NymdClient, QueryNymdClient};
use crate::ApiClient;
use network_defaults::all::Network;
use crate::nymd::traits::MixnetQueryClient;
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
use network_defaults::NymNetworkDetails;
use std::collections::HashMap;
use std::hash::BuildHasher;
use std::time::Duration;
@@ -17,12 +18,12 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
// Run connection tests for all specified nymd and api urls. These are all run concurrently.
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
) -> (
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
) {
// Setup all the clients for the connection tests
let connection_test_clients =
@@ -45,16 +46,16 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
}
fn setup_connection_tests<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
) -> impl Iterator<Item = ClientForConnectionTest> {
let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| {
let address = mixnet_contract_address
.get(&network)
.expect("No configured contract address")
.clone();
let config = ClientConfig::try_from_nym_network_details(&network.details())
let config = ClientConfig::try_from_nym_network_details(&network)
.expect("failed to create valid nymd client config");
if let Ok(mut client) = NymdClient::<QueryNymdClient>::connect(config, url.as_str()) {
@@ -80,7 +81,7 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
fn extract_and_collect_results_into_map(
connection_results: &[ConnectionResult],
url_type: &UrlType,
) -> HashMap<Network, Vec<(Url, bool)>> {
) -> HashMap<NymNetworkDetails, Vec<(Url, bool)>> {
connection_results
.iter()
.filter(|c| &c.url_type() == url_type)
@@ -92,7 +93,7 @@ fn extract_and_collect_results_into_map(
}
async fn test_nymd_connection(
network: Network,
network: NymNetworkDetails,
url: &Url,
client: &NymdClient<QueryNymdClient>,
) -> ConnectionResult {
@@ -104,56 +105,47 @@ async fn test_nymd_connection(
{
Ok(Err(NymdError::TendermintError(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {}",
"failed".red(),
e
);
log::debug!("Checking: nymd_url: {url}: {}: {}", "failed".red(), e);
false
}
Ok(Err(NymdError::AbciError(code, log))) => {
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
// for example on a pre-launch network.
log::debug!(
"Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}",
"Checking: nymd_url: {url}: {}, but with abci error: {code}: {log}",
"success".green()
);
code == 18
}
Ok(Err(error @ NymdError::NoContractAddressAvailable)) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {error}",
"failed".red()
);
log::debug!("Checking: nymd_url: {url}: {}: {error}", "failed".red());
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::debug!(
"Checking: nymd_url: {network}: {url}: {}, but with error: {e}",
"Checking: nymd_url: {url}: {}, but with error: {e}",
"success".green()
);
true
}
Ok(Ok(_)) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}",
"success".green()
);
log::debug!("Checking: nymd_url: {url}: {}", "success".green());
true
}
Err(e) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: nymd_url: {url}: {}: {e}", "failed".red());
false
}
};
ConnectionResult::Nymd(network, url.clone(), result)
}
async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult {
async fn test_api_connection(
network: NymNetworkDetails,
url: &Url,
client: &ApiClient,
) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
client.get_cached_mixnodes(),
@@ -161,21 +153,15 @@ async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) ->
.await
{
Ok(Ok(_)) => {
log::debug!("Checking: api_url: {network}: {url}: {}", "success".green());
log::debug!("Checking: api_url: {url}: {}", "success".green());
true
}
Ok(Err(e)) => {
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
false
}
Err(e) => {
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
false
}
};
@@ -183,8 +169,8 @@ async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) ->
}
enum ClientForConnectionTest {
Nymd(Network, Url, Box<NymdClient<QueryNymdClient>>),
Api(Network, Url, ApiClient),
Nymd(NymNetworkDetails, Url, Box<NymdClient<QueryNymdClient>>),
Api(NymNetworkDetails, Url, ApiClient),
}
impl ClientForConnectionTest {
@@ -217,12 +203,12 @@ impl fmt::Display for UrlType {
#[derive(Debug)]
enum ConnectionResult {
Nymd(Network, Url, bool),
Api(Network, Url, bool),
Nymd(NymNetworkDetails, Url, bool),
Api(NymNetworkDetails, Url, bool),
}
impl ConnectionResult {
fn result(&self) -> (&Network, &Url, &bool) {
fn result(&self) -> (&NymNetworkDetails, &Url, &bool) {
match self {
ConnectionResult::Nymd(network, url, result)
| ConnectionResult::Api(network, url, result) => (network, url, result),
@@ -239,11 +225,8 @@ impl ConnectionResult {
impl fmt::Display for ConnectionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (network, url, result) = self.result();
let (_network, url, result) = self.result();
let url_type = self.url_type();
write!(
f,
"{network}: {url}: {url_type}: connection is successful: {result}"
)
write!(f, "{url}: {url_type}: connection is successful: {result}")
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,387 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey};
use mixnet_contract_common::mixnode::{
MixNodeDetails, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse,
PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse,
};
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
use mixnet_contract_common::rewarding::{
EstimatedCurrentEpochRewardResponse, PendingRewardResponse,
};
use mixnet_contract_common::{
delegation, ContractBuildInformation, ContractState, ContractStateParams,
CurrentIntervalResponse, EpochEventId, GatewayBondResponse, GatewayOwnershipResponse,
IdentityKey, IntervalEventId, LayerDistribution, MixOwnershipResponse, MixnodeDetailsResponse,
NodeId, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse,
PendingEpochEventsResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
};
use serde::Deserialize;
#[async_trait]
pub trait MixnetQueryClient {
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>;
// state/sys-params-related
async fn get_mixnet_contract_version(&self) -> Result<ContractBuildInformation, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetContractVersion {})
.await
}
async fn get_rewarding_validator_address(&self) -> Result<AccountId, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardingValidatorAddress {})
.await
}
async fn get_mixnet_contract_settings(&self) -> Result<ContractStateParams, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {})
.await
}
async fn get_mixnet_contract_state(&self) -> Result<ContractState, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetState {})
.await
}
async fn get_rewarding_parameters(&self) -> Result<RewardingParams, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardingParams {})
.await
}
async fn get_current_interval_details(&self) -> Result<CurrentIntervalResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetCurrentIntervalDetails {})
.await
}
async fn get_rewarded_set_paged(
&self,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedRewardedSetResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardedSet { limit, start_after })
.await
}
// mixnode-related:
async fn get_mixnode_bonds_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedMixnodeBondsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodeBonds { limit, start_after })
.await
}
async fn get_mixnodes_detailed_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedMixnodesDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodesDetailed { limit, start_after })
.await
}
async fn get_unbonded_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after })
.await
}
async fn get_unbonded_by_owner_paged(
&self,
owner: &AccountId,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByOwner {
owner: owner.to_string(),
limit,
start_after,
})
.await
}
async fn get_unbonded_by_identity_paged(
&self,
identity_key: String,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByIdentityKey {
identity_key,
limit,
start_after,
})
.await
}
async fn get_owned_mixnode(
&self,
address: &AccountId,
) -> Result<MixOwnershipResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedMixnode {
address: address.to_string(),
})
.await
}
async fn get_mixnode_details(
&self,
mix_id: NodeId,
) -> Result<MixnodeDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDetails { mix_id })
.await
}
async fn get_mixnode_rewarding_details(
&self,
mix_id: NodeId,
) -> Result<MixnodeRewardingDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeRewardingDetails { mix_id })
.await
}
async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStakeSaturation { mix_id })
.await
}
async fn get_unbonded_mixnode_information(
&self,
mix_id: NodeId,
) -> Result<UnbondedMixnodeResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodeInformation { mix_id })
.await
}
async fn get_layer_distribution(&self) -> Result<LayerDistribution, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetLayerDistribution {})
.await
}
// gateway-related:
async fn get_gateways_paged(
&self,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> Result<PagedGatewayResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetGateways { start_after, limit })
.await
}
/// Checks whether there is a bonded gateway associated with the provided identity key
async fn get_gateway_bond(
&self,
identity: IdentityKey,
) -> Result<GatewayBondResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetGatewayBond { identity })
.await
}
/// Checks whether there is a bonded gateway associated with the provided client's address
async fn get_owned_gateway(
&self,
address: &AccountId,
) -> Result<GatewayOwnershipResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedGateway {
address: address.to_string(),
})
.await
}
// delegation-related:
/// Gets list of all delegations towards particular mixnode on particular page.
async fn get_mixnode_delegations_paged(
&self,
mix_id: NodeId,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedMixNodeDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDelegations {
mix_id,
start_after,
limit,
})
.await
}
/// Gets list of all the mixnodes to which a particular address delegated.
async fn get_delegator_delegations_paged(
&self,
delegator: String,
start_after: Option<(NodeId, OwnerProxySubKey)>,
limit: Option<u32>,
) -> Result<PagedDelegatorDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegatorDelegations {
delegator,
start_after,
limit,
})
.await
}
/// Checks value of delegation of given client towards particular mixnode.
async fn get_delegation_details(
&self,
mix_id: NodeId,
delegator: &AccountId,
proxy: Option<String>,
) -> Result<MixNodeDelegationResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegationDetails {
mix_id,
delegator: delegator.to_string(),
proxy,
})
.await
}
/// Gets all the delegations on the entire network
async fn get_all_network_delegations_paged(
&self,
start_after: Option<delegation::StorageKey>,
limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetAllDelegations { start_after, limit })
.await
}
// rewards related
async fn get_pending_operator_reward(
&self,
operator: &AccountId,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingOperatorReward {
address: operator.to_string(),
})
.await
}
async fn get_pending_mixnode_operator_reward(
&self,
mix_id: NodeId,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingMixNodeOperatorReward { mix_id })
.await
}
async fn get_pending_delegator_reward(
&self,
delegator: &AccountId,
mix_id: NodeId,
proxy: Option<String>,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
})
.await
}
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_operator_reward(
&self,
mix_id: NodeId,
estimated_performance: Performance,
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochOperatorReward {
mix_id,
estimated_performance,
})
.await
}
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_delegator_reward(
&self,
delegator: &AccountId,
mix_id: NodeId,
proxy: Option<String>,
estimated_performance: Performance,
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
estimated_performance,
})
.await
}
// interval-related
async fn get_pending_epoch_events_paged(
&self,
start_after: Option<EpochEventId>,
limit: Option<u32>,
) -> Result<PendingEpochEventsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvents { start_after, limit })
.await
}
async fn get_pending_interval_events_paged(
&self,
start_after: Option<IntervalEventId>,
limit: Option<u32>,
) -> Result<PendingIntervalEventsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvents { start_after, limit })
.await
}
async fn get_mixnode_details_by_identity(
&self,
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
}
#[async_trait]
impl<C> MixnetQueryClient for NymdClient<C>
where
C: CosmWasmClient + Sync + Send,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(self.mixnet_contract_address(), &query)
.await
}
}
#[async_trait]
impl<C> MixnetQueryClient for crate::Client<C>
where
C: CosmWasmClient + Sync + Send,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.nymd.query_mixnet_contract(query).await
}
}
@@ -0,0 +1,496 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::coin::Coin;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use cosmrs::AccountId;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
use mixnet_contract_common::{
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId,
};
#[async_trait]
pub trait MixnetSigningClient {
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>;
// state/sys-params-related
async fn update_rewarding_validator_address(
&self,
address: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateRewardingValidatorAddress {
address: address.to_string(),
},
vec![],
)
.await
}
async fn update_contract_state_params(
&self,
updated_parameters: ContractStateParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters },
vec![],
)
.await
}
async fn update_active_set_size(
&self,
active_set_size: u32,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
},
vec![],
)
.await
}
async fn update_rewarding_parameters(
&self,
updated_params: IntervalRewardingParamsUpdate,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateRewardingParams {
updated_params,
force_immediately,
},
vec![],
)
.await
}
async fn update_interval_config(
&self,
epochs_in_interval: u32,
epoch_duration_secs: u64,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateIntervalConfig {
epochs_in_interval,
epoch_duration_secs,
force_immediately,
},
vec![],
)
.await
}
async fn advance_current_epoch(
&self,
new_rewarded_set: Vec<NodeId>,
expected_active_set_size: u32,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size,
},
vec![],
)
.await
}
async fn reconcile_epoch_events(
&self,
limit: Option<u32>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::ReconcileEpochEvents { limit },
vec![],
)
.await
}
// mixnode-related:
async fn bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
},
vec![pledge],
)
.await
}
async fn bond_mixnode_on_behalf(
&self,
owner: AccountId,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondMixnodeOnBehalf {
mix_node,
cost_params,
owner_signature,
owner: owner.to_string(),
},
vec![pledge],
)
.await
}
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await
}
async fn unbond_mixnode_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UnbondMixnodeOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParams { new_costs },
vec![],
)
.await
}
async fn update_mixnode_cost_params_on_behalf(
&self,
owner: AccountId,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParamsOnBehalf {
new_costs,
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn update_mixnode_config(
&self,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeConfig { new_config },
vec![],
)
.await
}
async fn update_mixnode_config_on_behalf(
&self,
owner: AccountId,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeConfigOnBehalf {
new_config,
owner: owner.to_string(),
},
vec![],
)
.await
}
// gateway-related:
async fn bond_gateway(
&self,
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondGateway {
gateway,
owner_signature,
},
vec![pledge],
)
.await
}
async fn bond_gateway_on_behalf(
&self,
owner: AccountId,
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondGatewayOnBehalf {
gateway,
owner_signature,
owner: owner.to_string(),
},
vec![pledge],
)
.await
}
async fn unbond_gateway(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondGateway {}, vec![])
.await
}
async fn unbond_gateway_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UnbondGatewayOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
// delegation-related:
async fn delegate_to_mixnode(
&self,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnode { mix_id },
vec![amount],
)
.await
}
async fn delegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnodeOnBehalf {
mix_id,
delegate: delegate.to_string(),
},
vec![amount],
)
.await
}
async fn undelegate_from_mixnode(
&self,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
}
async fn undelegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf {
mix_id,
delegate: delegate.to_string(),
},
vec![],
)
.await
}
// reward-related
async fn reward_mixnode(
&self,
mix_id: NodeId,
performance: Performance,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::RewardMixnode {
mix_id,
performance,
},
vec![],
)
.await
}
async fn withdraw_operator_reward(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::WithdrawOperatorReward {}, vec![])
.await
}
async fn withdraw_operator_reward_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawOperatorRewardOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn withdraw_delegator_reward(
&self,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorReward { mix_id },
vec![],
)
.await
}
async fn withdraw_delegator_reward_on_behalf(
&self,
owner: AccountId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf {
mix_id,
owner: owner.to_string(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> MixnetSigningClient for NymdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.mixnet_contract_address(),
&msg,
fee,
memo,
funds,
)
.await
}
}
#[async_trait]
impl<C> MixnetSigningClient for crate::Client<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
self.nymd.execute_mixnet_contract(fee, msg, funds).await
}
}
@@ -1,8 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client;
mod mixnet_query_client;
mod mixnet_signing_client;
mod multisig_query_client;
mod multisig_signing_client;
mod vesting_query_client;
@@ -10,6 +12,8 @@ mod vesting_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_query_client::MultisigQueryClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use vesting_query_client::VestingQueryClient;
@@ -7,7 +7,7 @@ use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
use mixnet_contract_common::IdentityKey;
use mixnet_contract_common::NodeId;
use vesting_contract::vesting::Account;
use vesting_contract_common::{
messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse,
@@ -76,12 +76,12 @@ pub trait VestingQueryClient {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_identity: String,
mix_id: NodeId,
) -> Result<DelegationTimesResponse, NymdError>;
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, IdentityKey, u64)>,
start_after: Option<(u32, NodeId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NymdError>;
@@ -269,11 +269,11 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_identity: String,
mix_id: NodeId,
) -> Result<DelegationTimesResponse, NymdError> {
let request = VestingQueryMsg::GetDelegationTimes {
address: address.to_string(),
mix_identity,
mix_id,
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
@@ -282,7 +282,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, IdentityKey, u64)>,
start_after: Option<(u32, NodeId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NymdError> {
let request = VestingQueryMsg::GetAllDelegations { start_after, limit };
@@ -6,14 +6,28 @@ use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Coin, Fee, NymdClient};
use async_trait::async_trait;
use mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::{Gateway, MixNode, NodeId};
use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification};
#[async_trait]
pub trait VestingSigningClient {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
msg: VestingExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_config(
&self,
profix_margin_percent: u8,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -43,10 +57,12 @@ pub trait VestingSigningClient {
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
@@ -65,21 +81,21 @@ pub trait VestingSigningClient {
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode<'a>(
async fn vesting_delegate_to_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode<'a>(
async fn vesting_undelegate_from_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -95,15 +111,46 @@ pub trait VestingSigningClient {
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
msg: VestingExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.name().to_string();
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&msg,
fee,
memo,
funds,
)
.await
}
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UpdateMixnodeCostParams { new_costs },
vec![],
)
.await
}
async fn vesting_update_mixnode_config(
&self,
profit_margin_percent: u8,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
};
let req = VestingExecuteMsg::UpdateMixnodeConfig { new_config };
self.client
.execute(
self.address(),
@@ -203,26 +250,22 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
amount: pledge.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::BondMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature: owner_signature.to_string(),
amount: pledge.into(),
},
vec![],
)
.await
}
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
@@ -262,6 +305,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn withdraw_vested_coins(
&self,
amount: Coin,
@@ -282,72 +326,54 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount: amount.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::TrackUndelegation",
vec![],
)
.await
}
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
amount: amount.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::DelegateToMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_id,
amount: amount.into(),
},
vec![],
)
.await
}
async fn vesting_undelegate_from_mixnode<'a>(
async fn vesting_delegate_to_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UndelegateFromMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::DelegateToMixnode {
mix_id,
amount: amount.into(),
},
vec![],
)
.await
}
async fn vesting_undelegate_from_mixnode(
&self,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
}
async fn create_periodic_vesting_account(
@@ -72,7 +72,7 @@ impl DirectSecp256k1HdWallet {
}
fn derive_keypair(&self, hd_path: &DerivationPath) -> Result<Secp256k1Keypair, NymdError> {
let extended_private_key = XPrv::derive_from_path(&self.seed, hd_path)?;
let extended_private_key = XPrv::derive_from_path(self.seed, hd_path)?;
let private_key: SigningKey = extended_private_key.into();
let public_key = private_key.public_key();
@@ -207,8 +207,9 @@ impl DirectSecp256k1HdWalletBuilder {
#[cfg(test)]
mod tests {
use network_defaults::NymNetworkDetails;
use super::*;
use network_defaults::all::Network::*;
#[test]
fn generating_account_addresses() {
@@ -218,7 +219,9 @@ mod tests {
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
let prefix = MAINNET.bech32_prefix();
let prefix = NymNetworkDetails::new_mainnet()
.chain_details
.bech32_account_prefix;
let addrs = vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
@@ -3,17 +3,18 @@
use crate::validator_api::error::ValidatorAPIError;
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
pub mod error;
@@ -84,7 +85,7 @@ impl Client {
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
@@ -104,7 +105,7 @@ impl Client {
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
NO_PARAMS,
@@ -127,7 +128,7 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
NO_PARAMS,
@@ -150,28 +151,11 @@ impl Client {
.await
}
pub async fn get_probs_mixnode_rewarded(
&self,
mixnode_id: &str,
) -> Result<HashMap<String, f32>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::MIXNODES,
routes::REWARDED,
routes::INCLUSION_CHANCE,
mixnode_id,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
) -> Result<GatewayCoreStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
@@ -200,16 +184,16 @@ impl Client {
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
) -> Result<MixnodeCoreStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
@@ -221,7 +205,8 @@ impl Client {
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
CORE_STATUS_COUNT,
],
NO_PARAMS,
)
@@ -231,14 +216,14 @@ impl Client {
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<MixnodeStatusResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::STATUS,
],
NO_PARAMS,
@@ -248,14 +233,14 @@ impl Client {
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::REWARD_ESTIMATION,
],
NO_PARAMS,
@@ -265,14 +250,14 @@ impl Client {
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::STAKE_SATURATION,
],
NO_PARAMS,
@@ -282,14 +267,14 @@ impl Client {
pub async fn get_mixnode_inclusion_probability(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<InclusionProbabilityResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::INCLUSION_CHANCE,
],
NO_PARAMS,
@@ -299,27 +284,14 @@ impl Client {
pub async fn get_mixnode_avg_uptime(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<UptimeResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_avg_uptimes(&self) -> Result<Vec<UptimeResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
&mix_id.to_string(),
routes::AVG_UPTIME,
],
NO_PARAMS,
+5
View File
@@ -28,6 +28,11 @@ pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String {
format!("{} {}", amount, denom)
}
pub fn pretty_decimal_with_denom(value: Decimal, denom: &str) -> String {
// TODO: we might have to truncate the value here (that's why I moved it to separate function)
format!("{} {}", value, denom)
}
pub fn show_error<E>(e: E)
where
E: Display,
@@ -51,7 +51,7 @@ pub async fn query_balance(
return;
}
let denom = args.denom.unwrap_or_else(|| "".to_string());
let denom = args.denom.unwrap_or_default();
for coin in coins {
if denom.is_empty() || denom.eq_ignore_ascii_case(&coin.denom) {
@@ -4,12 +4,16 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, NodeId};
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
#[clap(long)]
pub amount: u128,
@@ -20,10 +24,25 @@ pub async fn delegate_to_mixnode(args: Args, client: SigningClient) {
info!("Starting delegation to mixnode");
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")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let coin = Coin::new(args.amount, denom);
let res = client
.delegate_to_mixnode(&*args.identity_key, coin.into(), None)
.delegate_to_mixnode(mix_id, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -8,8 +8,8 @@ use crate::context::SigningClientWithValidatorAPI;
use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough};
use comfy_table::Table;
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::Delegation;
use cosmwasm_std::Addr;
use mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventData};
#[derive(Debug, Parser)]
pub struct Args {}
@@ -26,19 +26,7 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
.map_err(show_error_passthrough);
let mixnet_contract_events = client
.nymd
.get_pending_delegation_events(client.nymd.address().to_string(), None)
.await
.map_err(show_error_passthrough);
let vesting_contract = client.nymd.vesting_contract_address();
let vesting_contract_events = client
.nymd
.get_pending_delegation_events(
client.nymd.address().to_string(),
Some(vesting_contract.to_string()),
)
.get_all_nymd_pending_epoch_events()
.await
.map_err(show_error_passthrough);
@@ -58,13 +46,6 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
print_delegation_events(res, &client).await;
}
}
if let Ok(res) = vesting_contract_events {
if !res.is_empty() {
println!();
println!("Pending delegations (locked tokens):");
print_delegation_events(res, &client).await;
}
}
}
async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidatorAPI) -> String {
@@ -77,14 +58,17 @@ async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidator
async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientWithValidatorAPI) {
let mut table = Table::new();
table.set_header(vec!["Timestamp", "Identity Key", "Delegation", "Proxy"]);
table.set_header(vec!["Timestamp", "Mix Id", "Delegation", "Proxy"]);
for delegation in delegations {
table.add_row(vec![
to_iso_timestamp(delegation.block_height as u32, client).await,
delegation.node_identity.to_string(),
to_iso_timestamp(delegation.height as u32, client).await,
delegation.mix_id.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
format!("{:?}", delegation.proxy),
delegation
.proxy
.map(Addr::into_string)
.unwrap_or_else(|| "-".into()),
]);
}
@@ -92,36 +76,53 @@ async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientW
}
async fn print_delegation_events(
events: Vec<DelegationEvent>,
events: Vec<PendingEpochEvent>,
client: &SigningClientWithValidatorAPI,
) {
let mut table = Table::new();
table.set_header(vec![
"Timestamp",
"Identity Key",
"Mix id",
"Delegation",
"Event Type",
"Proxy",
]);
for event in events {
match event {
DelegationEvent::Delegate(delegation) => {
table.add_row(vec![
to_iso_timestamp(delegation.block_height as u32, client).await,
delegation.node_identity.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
"Delegate".to_string(),
]);
match event.event {
PendingEpochEventData::Delegate {
owner,
mix_id,
amount,
proxy,
} => {
if owner.as_str() == client.nymd.address().as_ref() {
table.add_row(vec![
"not-sure-if-applicable".into(),
mix_id.to_string(),
pretty_cosmwasm_coin(&amount),
"Delegate".to_string(),
proxy.map(Addr::into_string).unwrap_or_else(|| "-".into()),
]);
}
}
DelegationEvent::Undelegate(undelegate) => {
table.add_row(vec![
to_iso_timestamp(undelegate.block_height() as u32, client).await,
undelegate.mix_identity().to_string(),
"-".to_string(),
"Undelegate".to_string(),
]);
PendingEpochEventData::Undelegate {
owner,
mix_id,
proxy,
} => {
if owner.as_str() == client.nymd.address().as_ref() {
table.add_row(vec![
"not-sure-if-applicable".into(),
mix_id.to_string(),
"-".to_string(),
"Undelegate".to_string(),
proxy.map(Addr::into_string).unwrap_or_else(|| "-".into()),
]);
}
}
_ => {}
}
}
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn claim_delegator_reward(args: Args, client: SigningClient) {
info!("Claim delegator reward");
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")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.execute_claim_delegator_reward(args.identity_key, None)
.withdraw_delegator_reward(mix_id, None)
.await
.expect("failed to claim delegator-reward");
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn vesting_claim_delegator_reward(args: Args, client: SigningClient) {
info!("Claim vesting delegator reward");
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")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.execute_vesting_claim_delegator_reward(args.identity, None)
.withdraw_delegator_reward_on_behalf(client.address().clone(), mix_id, None)
.await
.expect("failed to claim vesting delegator-reward");
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn undelegate_from_mixnode(args: Args, client: SigningClient) {
info!("removing stake from mix-node");
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")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.remove_mixnode_delegation(&*args.identity_key, None)
.undelegate_from_mixnode(mix_id, None)
.await
.expect("failed to remove stake from mixnode!");
@@ -4,7 +4,8 @@
use clap::Parser;
use log::info;
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, NodeId};
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
use crate::context::SigningClient;
@@ -12,7 +13,10 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
#[clap(long)]
pub amount: u128,
@@ -23,10 +27,25 @@ pub async fn vesting_delegate_to_mixnode(args: Args, client: SigningClient) {
info!("Starting vesting delegation to mixnode");
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")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_delegate_to_mixnode(&*args.identity_key, coin.into(), None)
.vesting_delegate_to_mixnode(mix_id, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -3,7 +3,8 @@
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
use crate::context::SigningClient;
@@ -11,14 +12,32 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) {
info!("removing stake from vesting mix-node");
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")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.vesting_undelegate_from_mixnode(&*args.identity_key, None)
.vesting_undelegate_from_mixnode(mix_id, None)
.await
.expect("failed to remove stake from vesting account on mixnode!");
@@ -6,6 +6,7 @@ use clap::Parser;
use log::{info, warn};
use mixnet_contract_common::Coin;
use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -68,7 +68,7 @@ pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_bond_gateway(gateway, &*args.signature, coin.into(), None)
.vesting_bond_gateway(gateway, &args.signature, coin.into(), None)
.await
.expect("failed to bond gateway!");
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -2,12 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, MixNodeCostParams, Percent};
use network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use validator_client::nymd::traits::MixnetSigningClient;
use validator_client::nymd::CosmWasmCoin;
use crate::context::SigningClient;
@@ -40,6 +43,12 @@ pub struct Args {
#[clap(long)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
@@ -71,13 +80,23 @@ pub async fn bond_mixnode(args: Args, client: SigningClient) {
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
};
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
let res = client
.bond_mixnode(mixnode, args.signature, coin.into(), None)
.bond_mixnode(mixnode, cost_params, args.signature, coin.into(), None)
.await
.expect("failed to bond mixnode!");
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -12,7 +13,7 @@ pub async fn claim_operator_reward(_args: Args, client: SigningClient) {
info!("Claim operator reward");
let res = client
.execute_claim_operator_reward(None)
.withdraw_operator_reward(None)
.await
.expect("failed to claim operator reward");
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -15,7 +16,7 @@ pub async fn vesting_claim_operator_reward(client: SigningClient) {
info!("Claim vesting operator reward");
let res = client
.execute_vesting_claim_operator_reward(None)
.withdraw_operator_reward_on_behalf(client.address().clone(), None)
.await
.expect("failed to claim vesting operator reward");
@@ -3,8 +3,8 @@
use clap::{Args, Subcommand};
pub mod update_profit_percent;
pub mod vesting_update_profit_percent;
pub mod update_config;
pub mod vesting_update_config;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -15,8 +15,12 @@ pub struct MixnetOperatorsMixnodeSettings {
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsMixnodeSettingsCommands {
/// Update profit percentage
UpdateProfitPercentage(update_profit_percent::Args),
/// Update profit percentage for a mixnode bonded with locked tokens
VestingUpdateProfitPercentage(vesting_update_profit_percent::Args),
/// Update mixnode configuration
UpdateConfig(update_config::Args),
/// Update mixnode configuration for a mixnode bonded with locked tokens
VestingUpdateConfig(vesting_update_config::Args),
/// Update mixnode cost parameters
UpdateCostParameters,
/// Update mixnode cost parameters for a mixnode bonded with locked tokens
VestingUpdateCostParameters,
}
@@ -0,0 +1,68 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixNodeConfigUpdate;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub verloc_port: Option<u16>,
#[clap(long)]
pub http_api_port: Option<u16>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn update_config(args: Args, client: SigningClient) {
info!("Update mix node config!");
let current_details = match client
.get_owned_mixnode(client.address())
.await
.expect("failed to query the chain for mixnode details")
.mixnode_details
{
Some(details) => details,
None => {
log::warn!("this operator does not own a mixnode to update");
return;
}
};
let update = MixNodeConfigUpdate {
host: args
.host
.unwrap_or(current_details.bond_information.mix_node.host),
mix_port: args
.mix_port
.unwrap_or(current_details.bond_information.mix_node.mix_port),
verloc_port: args
.verloc_port
.unwrap_or(current_details.bond_information.mix_node.verloc_port),
http_api_port: args
.http_api_port
.unwrap_or(current_details.bond_information.mix_node.http_api_port),
version: args
.version
.unwrap_or(current_details.bond_information.mix_node.version),
};
let res = client
.update_mixnode_config(update, None)
.await
.expect("updating mix-node config");
info!("mixnode config updated: {:?}", res)
}
@@ -1,24 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub profit_percent: u8,
}
pub async fn update_profit_percent(args: Args, client: SigningClient) {
info!("Update mix node profit percent - get those rewards!");
//profit percent between 1-100
let res = client
.update_mixnode_config(args.profit_percent, None)
.await
.expect("updating mix-node profit percent");
info!("profit percentage updated: {:?}", res)
}
@@ -0,0 +1,69 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixNodeConfigUpdate;
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub verloc_port: Option<u16>,
#[clap(long)]
pub http_api_port: Option<u16>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn vesting_update_config(client: SigningClient, args: Args) {
info!("Update vesting mix node config!");
let current_details = match client
.get_owned_mixnode(client.address())
.await
.expect("failed to query the chain for mixnode details")
.mixnode_details
{
Some(details) => details,
None => {
log::warn!("this operator does not own a mixnode to update");
return;
}
};
let update = MixNodeConfigUpdate {
host: args
.host
.unwrap_or(current_details.bond_information.mix_node.host),
mix_port: args
.mix_port
.unwrap_or(current_details.bond_information.mix_node.mix_port),
verloc_port: args
.verloc_port
.unwrap_or(current_details.bond_information.mix_node.verloc_port),
http_api_port: args
.http_api_port
.unwrap_or(current_details.bond_information.mix_node.http_api_port),
version: args
.version
.unwrap_or(current_details.bond_information.mix_node.version),
};
let res = client
.vesting_update_mixnode_config(update, None)
.await
.expect("updating vesting mix-node config");
info!("mixnode config updated: {:?}", res)
}
@@ -1,28 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub profit_percent: u8,
#[clap(long)]
pub gas: Option<u64>,
}
pub async fn vesting_update_profit_percent(client: SigningClient, args: Args) {
info!("Update vesting mix node profit percent - get those rewards!");
//profit percent between 1-100
let res = client
.vesting_update_mixnode_config(args.profit_percent, None)
.await
.expect("updating vesting mix-node profit percent");
info!("profit percentage updated: {:?}", res)
}
@@ -3,6 +3,7 @@
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
use crate::context::SigningClient;
@@ -3,13 +3,14 @@
use crate::context::SigningClient;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use mixnet_contract_common::Coin;
use mixnet_contract_common::MixNode;
use mixnet_contract_common::{Coin, MixNodeCostParams};
use mixnet_contract_common::{MixNode, Percent};
use network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use validator_client::nymd::VestingSigningClient;
use validator_client::nymd::{CosmWasmCoin, VestingSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
@@ -40,6 +41,12 @@ pub struct Args {
#[clap(long)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
@@ -72,13 +79,23 @@ pub async fn vesting_bond_mixnode(client: SigningClient, args: Args, denom: &str
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
};
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
let res = client
.vesting_bond_mixnode(mixnode, &*args.signature, coin.into(), None)
.vesting_bond_mixnode(mixnode, cost_params, &args.signature, coin.into(), None)
.await
.expect("failed to bond vesting mixnode!");
@@ -5,7 +5,7 @@ use clap::Parser;
use comfy_table::Table;
use crate::context::QueryClientWithValidatorAPI;
use crate::utils::{pretty_cosmwasm_coin, show_error};
use crate::utils::{pretty_decimal_with_denom, show_error};
#[derive(Debug, Parser)]
pub struct Args {
@@ -19,7 +19,8 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
Ok(res) => match args.identity_key {
Some(identity_key) => {
let node = res.iter().find(|node| {
node.mix_node
node.bond_information
.mix_node
.identity_key
.to_string()
.eq_ignore_ascii_case(&identity_key)
@@ -33,6 +34,7 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
let mut table = Table::new();
table.set_header(vec![
"Mix id",
"Identity Key",
"Owner",
"Host",
@@ -41,13 +43,15 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
"Version",
]);
for node in res {
let denom = &node.bond_information.original_pledge().denom;
table.add_row(vec![
node.mix_node.identity_key.to_string(),
node.owner.to_string(),
node.mix_node.host.to_string(),
pretty_cosmwasm_coin(&node.pledge_amount),
pretty_cosmwasm_coin(&node.total_delegation()),
node.mix_node.version,
node.mix_id().to_string(),
node.bond_information.mix_node.identity_key.clone(),
node.bond_information.owner.clone().into_string(),
node.bond_information.mix_node.host.clone(),
pretty_decimal_with_denom(node.rewarding_details.operator, denom),
pretty_decimal_with_denom(node.rewarding_details.delegates, denom),
node.bond_information.mix_node.version,
]);
}
@@ -51,7 +51,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
let res = client
.create_periodic_vesting_account(
&*args.address,
&args.address,
args.staking_address,
Some(vesting),
coin.into(),
@@ -70,7 +70,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
let send_coin_response = client
.send(
&AccountId::from_str(&*args.address).unwrap(),
&AccountId::from_str(&args.address).unwrap(),
vec![coin.into()],
"payment made :)",
None,
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "completions"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "3.2", features = ["derive"] }
clap_complete = "3.2"
clap_complete_fig = "3.2"
+58
View File
@@ -0,0 +1,58 @@
use clap::builder::Command;
use clap::clap_derive::ArgEnum;
use clap::Args;
use clap_complete::generator::generate;
use clap_complete::Shell as ClapShell;
use std::io;
pub fn fig_generate(command: &mut Command, name: &str) {
clap_complete::generate(
clap_complete_fig::Fig,
command,
name,
&mut std::io::stdout(),
)
}
#[derive(ArgEnum, Copy, Clone)]
pub enum Shell {
Bash,
Elvish,
Fish,
PowerShell,
Zsh,
}
#[derive(Args, Copy, Clone)]
pub struct ArgShell {
#[clap(arg_enum, value_name = "SHELL")]
shell: Shell,
}
impl ArgShell {
pub fn generate(&self, command: &mut Command, name: &str) {
self.shell.generate(command, name)
}
}
impl Shell {
pub fn generate(&self, command: &mut Command, name: &str) {
match &self {
Self::Bash => {
generate(ClapShell::Bash, command, name, &mut io::stdout());
}
Self::Elvish => {
generate(ClapShell::Elvish, command, name, &mut io::stdout());
}
Self::Fish => {
generate(ClapShell::Fish, command, name, &mut io::stdout());
}
Self::PowerShell => {
generate(ClapShell::PowerShell, command, name, &mut io::stdout());
}
Self::Zsh => {
generate(ClapShell::Zsh, command, name, &mut io::stdout());
}
}
}
}
+1
View File
@@ -88,6 +88,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
let location = custom_location
.unwrap_or_else(|| self.config_directory().join(Self::config_file_name()));
log::info!("Configuration file will be saved to {:?}", location);
cfg_if::cfg_if! {
if #[cfg(unix)] {
@@ -8,3 +8,4 @@ edition = "2021"
[dependencies]
cosmwasm-std = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
@@ -28,3 +28,26 @@ pub fn may_find_attribute(event: &Event, key: &str) -> Option<String> {
}
None
}
pub trait OptionallyAddAttribute {
fn add_optional_attribute(
self,
key: impl Into<String>,
value: Option<impl Into<String>>,
) -> Self;
}
impl OptionallyAddAttribute for Event {
fn add_optional_attribute(
self,
key: impl Into<String>,
value: Option<impl Into<String>>,
) -> Self {
if let Some(value) = value {
self.add_attribute(key, value)
} else {
// TODO: perhaps if value doesn't exist, we should emit explicit 'null'?
self
}
}
}
@@ -2,3 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
pub mod events;
pub mod types;
pub use types::*;
@@ -0,0 +1,33 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
// TODO: there's no reason this couldn't be used for proper binaries, but in that case
// perhaps the struct should get renamed and moved to a "more" common crate
#[derive(Debug, Serialize, Deserialize)]
pub struct ContractBuildInformation {
// VERGEN_BUILD_TIMESTAMP
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
pub build_timestamp: String,
// VERGEN_BUILD_SEMVER
/// Provides the build version, for example `0.1.0-9-g46f83e1`.
pub build_version: String,
// VERGEN_GIT_SHA
/// Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.
pub commit_sha: String,
// VERGEN_GIT_COMMIT_TIMESTAMP
/// Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.
pub commit_timestamp: String,
// VERGEN_GIT_BRANCH
/// Provides the name of the git branch that was used for the build, for example `master`.
pub commit_branch: String,
// VERGEN_RUSTC_SEMVER
/// Provides the rustc version that was used for the build, for example `1.52.0-nightly`.
pub rustc_version: String,
}
@@ -8,21 +8,22 @@ rust-version = "1.62"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
thiserror = "1.0"
fixed = { version = "1.1", features = ["serde"] }
az = "1.1"
contracts-common = { path = "../contracts-common" }
serde_json = "1.0.0"
# TO CHECK WHETHER STILL NEEDED:
log = "0.4.14"
time = { version = "0.3.6", features = ["parsing", "formatting"] }
ts-rs = {version = "6.1.2", optional = true}
contracts-common = { path = "../contracts-common" }
ts-rs = { version = "6.1.2", optional = true }
[dev-dependencies]
rand_chacha = "0.3"
time = { version = "0.3.5", features = ["serde", "macros"] }
[features]
@@ -0,0 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Decimal;
// I'm still not 100% sure how to feel about existence of this file
// This is equivalent of representing our display coin with 6 decimal places.
// I'm using this one as opposed to "Decimal::one()", as this provides us with higher accuracy
// whilst providing no noticable drawbacks.
pub const UNIT_DELEGATION_BASE: Decimal =
Decimal::raw(1_000_000_000 * 1_000_000_000_000_000_000u128);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unit_delegation_didnt_change() {
// a sanity check test to make sure Decimal's `DECIMAL_FRACTIONAL` internal implementation hasn't changed
assert_eq!(
UNIT_DELEGATION_BASE,
Decimal::one() * Decimal::from_atomics(1_000_000_000u32, 0).unwrap()
)
}
}
@@ -1,151 +1,135 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::{Addr, IdentityKey};
use cosmwasm_std::{Coin, Uint128};
use crate::{Addr, NodeId};
use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::hash::{Hash, Hasher};
type OwnerAddressBytes = Vec<u8>;
type BlockHeight = u64;
// just use a string representation of those so that we wouldn't need to bother with decoding bytes
// and trying to figure out whether they're valid, etc
pub type OwnerProxySubKey = String;
pub type StorageKey = (NodeId, OwnerProxySubKey);
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
pub fn generate_owner_storage_subkey(address: &Addr, proxy: Option<&Addr>) -> String {
if let Some(proxy) = &proxy {
address
let key_bytes = address
.as_bytes()
.iter()
.zip(proxy.as_bytes())
.map(|(x, y)| x ^ y)
.collect()
.collect::<Vec<_>>();
bs58::encode(key_bytes).into_string()
} else {
address.as_bytes().to_vec()
address.clone().into_string()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct Delegation {
/// Address of the owner of this delegation.
pub owner: Addr,
pub node_identity: IdentityKey,
/// Id of the MixNode that this delegation was performed against.
#[serde(alias = "node_id")]
pub mix_id: NodeId,
// Note to UI/UX devs: there's absolutely no point in displaying this value to the users,
// it would serve them no purpose. It's only used for calculating rewards
/// Value of the "unit delegation" associated with the mixnode at the time of delegation.
#[serde(alias = "crr")]
pub cumulative_reward_ratio: Decimal,
/// Original delegation amount. Note that it is never mutated as delegation accumulates rewards.
pub amount: Coin,
pub block_height: u64,
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of another address
}
impl Eq for Delegation {}
/// Block height where this delegation occurred.
pub height: u64,
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Delegation {
fn hash<H: Hasher>(&self, state: &mut H) {
self.owner.hash(state);
self.node_identity.hash(state);
self.block_height.hash(state);
self.proxy.hash(state);
}
/// Proxy address used to delegate the funds on behalf of another address
pub proxy: Option<Addr>,
}
impl Delegation {
pub fn new(
owner: Addr,
node_identity: IdentityKey,
mix_id: NodeId,
cumulative_reward_ratio: Decimal,
amount: Coin,
block_height: BlockHeight,
height: u64,
proxy: Option<Addr>,
) -> Self {
Delegation {
owner,
node_identity,
mix_id,
cumulative_reward_ratio,
amount,
block_height,
height,
proxy,
}
}
pub fn storage_key(&self) -> (IdentityKey, OwnerAddressBytes, BlockHeight) {
(
self.node_identity(),
self.proxy_storage_key(),
self.block_height(),
)
pub fn generate_storage_key(
mix_id: NodeId,
owner_address: &Addr,
proxy: Option<&Addr>,
) -> StorageKey {
(mix_id, generate_owner_storage_subkey(owner_address, proxy))
}
pub fn event_storage_key(&self) -> (OwnerAddressBytes, BlockHeight, IdentityKey) {
(
self.proxy_storage_key(),
self.block_height(),
self.node_identity(),
)
// this function might seem a bit redundant, but I'd rather explicitly keep it around in case
// some types change in the future
pub fn generate_storage_key_with_subkey(
mix_id: NodeId,
owner_proxy_subkey: OwnerProxySubKey,
) -> StorageKey {
(mix_id, owner_proxy_subkey)
}
pub fn proxy_storage_key(&self) -> OwnerAddressBytes {
generate_storage_key(&self.owner, self.proxy.as_ref())
pub fn dec_amount(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.amount.amount, 0).unwrap()
}
pub fn proxy(&self) -> Option<&Addr> {
self.proxy.as_ref()
pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
generate_owner_storage_subkey(&self.owner, self.proxy.as_ref())
}
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
self.amount.amount += amount;
if let Some(at_height) = at_height {
self.block_height = at_height;
}
}
pub fn amount(&self) -> &Coin {
&self.amount
}
pub fn node_identity(&self) -> IdentityKey {
self.node_identity.clone()
}
pub fn owner(&self) -> Addr {
self.owner.clone()
}
pub fn block_height(&self) -> u64 {
self.block_height
pub fn storage_key(&self) -> StorageKey {
Self::generate_storage_key(self.mix_id, &self.owner, self.proxy.as_ref())
}
}
impl Display for Delegation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} delegated towards {} by {} at block {}",
self.amount, self.node_identity, self.owner, self.block_height
)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixDelegationsResponse {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedMixNodeDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(String, u64)>,
pub start_next_after: Option<OwnerProxySubKey>,
}
impl PagedMixDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(String, u64)>) -> Self {
PagedMixDelegationsResponse {
impl PagedMixNodeDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<OwnerProxySubKey>) -> Self {
PagedMixNodeDelegationsResponse {
delegations,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<IdentityKey>,
pub start_next_after: Option<(NodeId, OwnerProxySubKey)>,
}
impl PagedDelegatorDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<IdentityKey>) -> Self {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(NodeId, OwnerProxySubKey)>,
) -> Self {
PagedDelegatorDelegationsResponse {
delegations,
start_next_after,
@@ -153,20 +137,32 @@ impl PagedDelegatorDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeDelegationResponse {
pub delegation: Option<Delegation>,
pub mixnode_still_bonded: bool,
}
impl PagedAllDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
impl MixNodeDelegationResponse {
pub fn new(delegation: Option<Delegation>, mixnode_still_bonded: bool) -> Self {
MixNodeDelegationResponse {
delegation,
mixnode_still_bonded,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<StorageKey>,
}
impl PagedAllDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<StorageKey>) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after,
}
}
}
@@ -1,23 +1,139 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::NodeId;
use cosmwasm_std::{Addr, Coin, Decimal};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError {
#[error("Overflow Error")]
OverflowError(#[from] cosmwasm_std::OverflowError),
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
BlockstampNotSet,
#[error("{source}")]
TryFromIntError {
#[from]
source: std::num::TryFromIntError,
},
#[error("Error casting from U128")]
CastError,
#[error("{source}")]
StdErr {
#[from]
source: cosmwasm_std::StdError,
},
#[error("Division by zero at {}", line!())]
DivisionByZero,
#[error("Provided percent value is greater than 100%")]
InvalidPercent,
#[error("Attempted to subtract decimals with overflow ({minuend}.sub({subtrahend}))")]
OverflowDecimalSubtraction {
minuend: Decimal,
subtrahend: Decimal,
},
#[error("Attempted to subtract with overflow ({minuend}.sub({subtrahend}))")]
OverflowSubtraction { minuend: u64, subtrahend: u64 },
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
InsufficientPledge { received: Coin, minimum: Coin },
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin },
#[error("Mixnode ({mix_id}) does not exist")]
MixNodeBondNotFound { mix_id: NodeId },
#[error("{owner} does not seem to own any mixnodes")]
NoAssociatedMixNodeBond { owner: Addr },
#[error("{owner} does not seem to own any gateways")]
NoAssociatedGatewayBond { owner: Addr },
#[error("This address has already bonded a mixnode")]
AlreadyOwnsMixnode,
#[error("This address has already bonded a gateway")]
AlreadyOwnsGateway,
#[error("Gateway with this identity already exists. Its owner is {owner}")]
DuplicateGateway { owner: Addr },
#[error("Unauthorized")]
Unauthorized,
#[error("No tokens were sent for the bonding")]
NoBondFound,
#[error("No funds were provided for the delegation")]
EmptyDelegation,
#[error("Wrong coin denomination. Received: {received}, expected: {expected}")]
WrongDenom { received: String, expected: String },
#[error("Received multiple coin types during staking")]
MultipleDenoms,
#[error("Proxy address mismatch, expected {existing}, got {incoming}")]
ProxyMismatch { existing: String, incoming: String },
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
#[error("Failed to recover ed25519 signature from its base58 representation - {0}")]
MalformedEd25519Signature(String),
#[error("Provided ed25519 signature did not verify correctly")]
InvalidEd25519Signature,
#[error("Can't perform the specified action as the current epoch is still progress. It started at {epoch_start} and finishes at {epoch_end}, while the current block time is {current_block_time}")]
EpochInProgress {
current_block_time: u64,
epoch_start: i64,
epoch_end: i64,
},
#[error("Mixnode {mix_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
MixnodeAlreadyRewarded {
mix_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {mix_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
MixnodeNotInRewardedSet {
mix_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {mix_id} is currently in the process of unbonding")]
MixnodeIsUnbonding { mix_id: NodeId },
#[error("Mixnode {mix_id} has already unbonded")]
MixnodeHasUnbonded { mix_id: NodeId },
#[error("The contract has ended up in a state that was deemed impossible: {comment}")]
InconsistentState { comment: String },
#[error(
"Could not find any delegation information associated with mixnode {mix_id} for {address} (proxy: {proxy:?})"
)]
NoMixnodeDelegationFound {
mix_id: NodeId,
address: String,
proxy: Option<String>,
},
#[error("Provided message to update rewarding params did not contain any updates")]
EmptyParamsChangeMsg,
#[error("Provided active set size is bigger than the rewarded set")]
InvalidActiveSetSize,
#[error("Provided rewarded set size is smaller than the active set")]
InvalidRewardedSetSize,
#[error("Provided active set size is zero")]
ZeroActiveSet,
#[error("Provided rewarded set size is zero")]
ZeroRewardedSet,
#[error("Received unexpected value for the active set. Got: {received}, expected: {expected}")]
UnexpectedActiveSetSize { received: u32, expected: u32 },
#[error("Received unexpected value for the rewarded set. Got: {received}, expected at most: {expected}")]
UnexpectedRewardedSetSize { received: u32, expected: u32 },
#[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")]
DuplicateRewardedSetNode { mix_id: NodeId },
}
@@ -1,32 +1,90 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardResult;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
use cosmwasm_std::{Addr, Coin, Event, Uint128};
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer, NodeId};
pub use contracts_common::events::*;
// FIXME: This should becoma an Enum
// event types
pub const DELEGATION_EVENT_TYPE: &str = "delegation";
pub const PENDING_DELEGATION_EVENT_TYPE: &str = "pending_delegation";
pub const RECONCILE_DELEGATION_EVENT_TYPE: &str = "reconcile_delegation";
pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation";
pub const PENDING_UNDELEGATION_EVENT_TYPE: &str = "pending_undelegation";
pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding";
pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding";
pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding";
pub const MIXNODE_UNBONDING_EVENT_TYPE: &str = "mixnode_unbonding";
pub const SETTINGS_UPDATE_EVENT_TYPE: &str = "settings_update";
pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding";
pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding";
pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
pub const CLAIM_DELEGATOR_REWARD_EVENT_TYPE: &str = "claim_delegator_reward";
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
pub const CLAIM_OPERATOR_REWARD_EVENT_TYPE: &str = "claim_operator_reward";
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
use cosmwasm_std::{Addr, Coin, Decimal, Event};
pub const EVENT_VERSION_PREFIX: &str = "v2_";
pub enum MixnetEventType {
MixnodeBonding,
GatewayBonding,
GatewayUnbonding,
PendingMixnodeUnbonding,
MixnodeUnbonding,
MixnodeConfigUpdate,
PendingMixnodeCostParamsUpdate,
MixnodeCostParamsUpdate,
MixnodeRewarding,
WithdrawDelegatorReward,
WithdrawOperatorReward,
PendingActiveSetUpdate,
ActiveSetUpdate,
PendingIntervalRewardingParamsUpdate,
IntervalRewardingParamsUpdate,
PendingDelegation,
PendingUndelegation,
Delegation,
DelegationOnUnbonding,
Undelegation,
ContractSettingsUpdate,
RewardingValidatorUpdate,
AdvanceEpoch,
ExecutePendingEpochEvents,
ExecutePendingIntervalEvents,
ReconcilePendingEvents,
PendingIntervalConfigUpdate,
IntervalConfigUpdate,
}
impl From<MixnetEventType> for String {
fn from(typ: MixnetEventType) -> Self {
typ.to_string()
}
}
impl ToString for MixnetEventType {
fn to_string(&self) -> String {
let event_name = match self {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
MixnetEventType::MixnodeConfigUpdate => "mixnode_config_update",
MixnetEventType::MixnodeUnbonding => "mixnode_unbonding",
MixnetEventType::PendingMixnodeCostParamsUpdate => "pending_mixnode_cost_params_update",
MixnetEventType::MixnodeCostParamsUpdate => "mixnode_cost_params_update",
MixnetEventType::MixnodeRewarding => "mix_rewarding",
MixnetEventType::WithdrawDelegatorReward => "withdraw_delegator_reward",
MixnetEventType::WithdrawOperatorReward => "withdraw_operator_reward",
MixnetEventType::PendingActiveSetUpdate => "pending_active_set_update",
MixnetEventType::ActiveSetUpdate => "active_set_update",
MixnetEventType::PendingIntervalRewardingParamsUpdate => {
"pending_interval_rewarding_params_update"
}
MixnetEventType::IntervalRewardingParamsUpdate => "interval_rewarding_params_update",
MixnetEventType::PendingDelegation => "pending_delegation",
MixnetEventType::PendingUndelegation => "pending_undelegation",
MixnetEventType::Delegation => "delegation",
MixnetEventType::Undelegation => "undelegation",
MixnetEventType::ContractSettingsUpdate => "settings_update",
MixnetEventType::RewardingValidatorUpdate => "rewarding_validator_address_update",
MixnetEventType::AdvanceEpoch => "advance_epoch",
MixnetEventType::ExecutePendingEpochEvents => "execute_pending_epoch_events",
MixnetEventType::ExecutePendingIntervalEvents => "execute_pending_interval_events",
MixnetEventType::ReconcilePendingEvents => "reconcile_pending_events",
MixnetEventType::PendingIntervalConfigUpdate => "pending_interval_config_update",
MixnetEventType::IntervalConfigUpdate => "interval_config_update",
MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node",
};
format!("{}{}", EVENT_VERSION_PREFIX, event_name)
}
}
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
@@ -41,35 +99,44 @@ pub const DELEGATION_TARGET_KEY: &str = "delegation_target";
pub const DELEGATION_HEIGHT_KEY: &str = "delegation_latest_block_height";
// bonding/unbonding
pub const MIX_ID_KEY: &str = "mix_id";
pub const NODE_IDENTITY_KEY: &str = "identity";
pub const ASSIGNED_LAYER_KEY: &str = "assigned_layer";
// settings change
pub const OLD_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "old_minimum_mixnode_pledge";
pub const OLD_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "old_minimum_gateway_pledge";
pub const OLD_MIXNODE_REWARDED_SET_SIZE_KEY: &str = "old_mixnode_rewarded_set_size";
pub const OLD_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "old_mixnode_active_set_size";
pub const OLD_ACTIVE_SET_WORK_FACTOR_KEY: &str = "old_active_set_work_factor";
pub const OLD_MINIMUM_DELEGATION_KEY: &str = "old_minimum_delegation";
pub const NEW_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "new_minimum_mixnode_pledge";
pub const NEW_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "new_minimum_gateway_pledge";
pub const NEW_MIXNODE_REWARDED_SET_SIZE_KEY: &str = "new_mixnode_rewarded_set_size";
pub const NEW_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "new_mixnode_active_set_size";
pub const NEW_MINIMUM_DELEGATION_KEY: &str = "new_minimum_delegation";
pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_address";
pub const NEW_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "new_rewarding_validator_address";
pub const UPDATED_MIXNODE_CONFIG_KEY: &str = "updated_mixnode_config";
pub const UPDATED_MIXNODE_COST_PARAMS_KEY: &str = "updated_mixnode_cost_params";
// rewarding
pub const INTERVAL_ID_KEY: &str = "interval_id";
pub const INTERVAL_KEY: &str = "interval_details";
pub const TOTAL_MIXNODE_REWARD_KEY: &str = "total_node_reward";
pub const OPERATOR_REWARD_KEY: &str = "operator_reward";
pub const TOTAL_PLEDGE_KEY: &str = "pledge";
pub const TOTAL_DELEGATIONS_KEY: &str = "delegated";
pub const LAMBDA_KEY: &str = "lambda";
pub const SIGMA_KEY: &str = "sigma";
pub const OPERATOR_REWARD_KEY: &str = "operator_reward";
pub const DELEGATES_REWARD_KEY: &str = "delegates_reward";
pub const APPROXIMATE_TIME_LEFT_SECS_KEY: &str = "approximate_time_left_secs";
pub const INTERVAL_REWARDING_PARAMS_UPDATE_KEY: &str = "interval_rewarding_params_update";
pub const UPDATED_INTERVAL_REWARDING_PARAMS_KEY: &str = "updated_interval_rewarding_params";
pub const PRIOR_DELEGATES_KEY: &str = "prior_delegates";
pub const PRIOR_UNIT_DELEGATION_KEY: &str = "prior_unit_delegation";
pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards";
pub const FURTHER_DELEGATIONS_TO_REWARD_KEY: &str = "further_delegations";
pub const NO_REWARD_REASON_KEY: &str = "no_reward_reason";
pub const BOND_NOT_FOUND_VALUE: &str = "bond_not_found";
pub const BOND_TOO_FRESH_VALUE: &str = "bond_too_fresh";
pub const ZERO_UPTIME_VALUE: &str = "zero_uptime";
pub const ZERO_PERFORMANCE_VALUE: &str = "zero_performance";
// rewarded set update
pub const ACTIVE_SET_SIZE_KEY: &str = "active_set_size";
@@ -80,156 +147,140 @@ pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
pub const CHECKPOINT_MIXNODES_EVENT: &str = "checkpoint_mixnodes";
pub const RECONCILIATION_ERROR_EVENT: &str = "reconciliation_error";
pub fn new_checkpoint_mixnodes_event(block_height: u64) -> Event {
Event::new(CHECKPOINT_MIXNODES_EVENT)
.add_attribute(BLOCK_HEIGHT_KEY, format!("{}", block_height))
}
pub fn new_error_event(err: String) -> Event {
Event::new(RECONCILIATION_ERROR_EVENT).add_attribute("error", err)
}
// interval
pub const EVENTS_EXECUTED_KEY: &str = "number_of_events_executed";
pub const REWARDED_SET_NODES_KEY: &str = "rewarded_set_nodes";
pub const NEW_EPOCHS_DURATION_SECS_KEY: &str = "new_epoch_durations_secs";
pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval";
pub fn new_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Event {
let mut event = Event::new(DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
Event::new(MixnetEventType::Delegation)
.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_identity)
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_delegation_on_unbonded_node_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_id: NodeId,
) -> 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_identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Event {
let mut event =
Event::new(PENDING_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_reconcile_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(RECONCILE_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(COMPOUND_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_claim_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(CLAIM_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_compound_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(COMPOUND_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
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_claim_delegator_reward_event(
pub fn new_withdraw_operator_reward_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: Coin,
mix_id: NodeId,
) -> 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: Uint128,
mix_identity: IdentityKeyRef<'_>,
amount: Coin,
mix_id: NodeId,
) -> Event {
let mut event =
Event::new(CLAIM_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
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())
}
pub fn new_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_identity: IdentityKeyRef<'_>,
amount: Uint128,
pub fn new_active_set_update_event(new_size: u32) -> Event {
Event::new(MixnetEventType::ActiveSetUpdate)
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
}
pub fn new_pending_active_set_update_event(
new_size: u32,
approximate_time_remaining_secs: i64,
) -> Event {
let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
Event::new(MixnetEventType::PendingActiveSetUpdate)
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
}
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
pub fn new_rewarding_params_update_event(
update: IntervalRewardingParamsUpdate,
updated: IntervalRewardParams,
) -> Event {
Event::new(MixnetEventType::IntervalRewardingParamsUpdate)
.add_attribute(
INTERVAL_REWARDING_PARAMS_UPDATE_KEY,
update.to_inline_json(),
)
.add_attribute(
UPDATED_INTERVAL_REWARDING_PARAMS_KEY,
updated.to_inline_json(),
)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
pub fn new_pending_rewarding_params_update_event(
update: IntervalRewardingParamsUpdate,
approximate_time_remaining_secs: i64,
) -> Event {
Event::new(MixnetEventType::PendingIntervalRewardingParamsUpdate)
.add_attribute(
INTERVAL_REWARDING_PARAMS_UPDATE_KEY,
update.to_inline_json(),
)
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
}
pub fn new_undelegation_event(delegator: &Addr, proxy: &Option<Addr>, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::Undelegation)
.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_identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Event {
let mut event =
Event::new(PENDING_UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
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(
@@ -238,16 +289,11 @@ pub fn new_gateway_bonding_event(
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
let mut event = Event::new(GATEWAY_BONDING_EVENT_TYPE)
Event::new(MixnetEventType::GatewayBonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
.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(
@@ -256,16 +302,11 @@ pub fn new_gateway_unbonding_event(
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
let mut event = Event::new(GATEWAY_UNBONDING_EVENT_TYPE)
Event::new(MixnetEventType::GatewayUnbonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
.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(
@@ -273,55 +314,92 @@ pub fn new_mixnode_bonding_event(
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
assigned_layer: Layer,
) -> Event {
let mut event = Event::new(MIXNODE_BONDING_EVENT_TYPE)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
Event::new(MixnetEventType::MixnodeBonding)
.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())
}
pub fn new_mixnode_unbonding_event(
pub fn new_mixnode_unbonding_event(mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_pending_mixnode_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Event {
let mut event = Event::new(MIXNODE_UNBONDING_EVENT_TYPE)
Event::new(MixnetEventType::PendingMixnodeUnbonding)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity);
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
}
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
pub fn new_mixnode_config_update_event(
mix_id: NodeId,
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())
}
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
pub fn new_mixnode_pending_cost_params_update_event(
mix_id: NodeId,
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())
}
pub fn new_mixnode_cost_params_update_event(
mix_id: NodeId,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::MixnodeCostParamsUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
pub fn new_rewarding_validator_address_update_event(old: Addr, new: Addr) -> Event {
Event::new(MixnetEventType::RewardingValidatorUpdate)
.add_attribute(OLD_REWARDING_VALIDATOR_ADDRESS_KEY, old)
.add_attribute(NEW_REWARDING_VALIDATOR_ADDRESS_KEY, new)
}
pub fn new_settings_update_event(
old_params: &ContractStateParams,
new_params: &ContractStateParams,
) -> Event {
let mut event = Event::new(SETTINGS_UPDATE_EVENT_TYPE);
let mut event = Event::new(MixnetEventType::ContractSettingsUpdate);
if old_params.minimum_mixnode_pledge != new_params.minimum_mixnode_pledge {
event = event
.add_attribute(
OLD_MINIMUM_MIXNODE_PLEDGE_KEY,
old_params.minimum_mixnode_pledge,
old_params.minimum_mixnode_pledge.to_string(),
)
.add_attribute(
NEW_MINIMUM_MIXNODE_PLEDGE_KEY,
new_params.minimum_mixnode_pledge,
new_params.minimum_mixnode_pledge.to_string(),
)
}
@@ -329,128 +407,128 @@ pub fn new_settings_update_event(
event = event
.add_attribute(
OLD_MINIMUM_GATEWAY_PLEDGE_KEY,
old_params.minimum_gateway_pledge,
old_params.minimum_gateway_pledge.to_string(),
)
.add_attribute(
NEW_MINIMUM_GATEWAY_PLEDGE_KEY,
new_params.minimum_gateway_pledge,
new_params.minimum_gateway_pledge.to_string(),
)
}
if old_params.mixnode_rewarded_set_size != new_params.mixnode_rewarded_set_size {
event = event
.add_attribute(
OLD_MIXNODE_REWARDED_SET_SIZE_KEY,
old_params.mixnode_rewarded_set_size.to_string(),
)
.add_attribute(
NEW_MIXNODE_REWARDED_SET_SIZE_KEY,
new_params.mixnode_rewarded_set_size.to_string(),
)
}
if old_params.mixnode_active_set_size != new_params.mixnode_active_set_size {
event = event
.add_attribute(
OLD_MIXNODE_ACTIVE_SET_SIZE_KEY,
old_params.mixnode_active_set_size.to_string(),
)
.add_attribute(
NEW_MIXNODE_ACTIVE_SET_SIZE_KEY,
new_params.mixnode_active_set_size.to_string(),
)
if old_params.minimum_mixnode_delegation != new_params.minimum_mixnode_delegation {
if let Some(ref old) = old_params.minimum_mixnode_delegation {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, old.to_string())
} else {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, "None")
}
if let Some(ref new) = new_params.minimum_mixnode_delegation {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, new.to_string())
} else {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, "None")
}
}
event
}
pub fn new_not_found_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE)
}
pub fn new_too_fresh_bond_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(NO_REWARD_REASON_KEY, BOND_TOO_FRESH_VALUE)
}
pub fn new_zero_uptime_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(NO_REWARD_REASON_KEY, ZERO_UPTIME_VALUE)
}
#[allow(clippy::too_many_arguments)]
pub fn new_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
node_reward_result: NodeRewardResult,
node_pledge: Uint128,
node_delegation: Uint128,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(TOTAL_PLEDGE_KEY, node_pledge)
.add_attribute(TOTAL_DELEGATIONS_KEY, node_delegation)
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
TOTAL_MIXNODE_REWARD_KEY,
node_reward_result.reward().to_string(),
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string())
.add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, ZERO_PERFORMANCE_VALUE)
}
pub fn new_mix_delegators_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
delegation_rewards_distributed: Uint128,
further_delegations: bool,
pub fn new_mix_rewarding_event(
interval: Interval,
mix_id: NodeId,
reward_distribution: RewardDistribution,
prior_delegates: Decimal,
prior_unit_delegation: Decimal,
) -> Event {
Event::new(MIX_DELEGATORS_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
DISTRIBUTED_DELEGATION_REWARDS_KEY,
delegation_rewards_distributed,
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(PRIOR_DELEGATES_KEY, prior_delegates.to_string())
.add_attribute(PRIOR_UNIT_DELEGATION_KEY, prior_unit_delegation.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(
OPERATOR_REWARD_KEY,
reward_distribution.operator.to_string(),
)
.add_attribute(
FURTHER_DELEGATIONS_TO_REWARD_KEY,
further_delegations.to_string(),
DELEGATES_REWARD_KEY,
reward_distribution.delegates.to_string(),
)
}
// note that when this event is emitted, we'll know the current block height
pub fn new_change_rewarded_set_event(
active_set_size: u32,
rewarded_set_size: u32,
nodes_in_rewarded_set: u32,
pub fn new_advance_epoch_event(interval: Interval, rewarded_nodes: u32) -> Event {
Event::new(MixnetEventType::AdvanceEpoch)
.add_attribute(
NEW_CURRENT_EPOCH_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(REWARDED_SET_NODES_KEY, rewarded_nodes.to_string())
}
pub fn new_pending_epoch_events_execution_event(executed: u32) -> Event {
Event::new(MixnetEventType::ExecutePendingEpochEvents)
.add_attribute(EVENTS_EXECUTED_KEY, executed.to_string())
}
pub fn new_pending_interval_events_execution_event(executed: u32) -> Event {
Event::new(MixnetEventType::ExecutePendingIntervalEvents)
.add_attribute(EVENTS_EXECUTED_KEY, executed.to_string())
}
pub fn new_reconcile_pending_events() -> Event {
Event::new(MixnetEventType::ReconcilePendingEvents)
}
pub fn new_interval_config_update_event(
epochs_in_interval: u32,
epoch_duration_secs: u64,
updated_rewarding_params: IntervalRewardParams,
) -> Event {
Event::new(CHANGE_REWARDED_SET_EVENT_TYPE)
.add_attribute(ACTIVE_SET_SIZE_KEY, active_set_size.to_string())
.add_attribute(REWARDED_SET_SIZE_KEY, rewarded_set_size.to_string())
.add_attribute(NODES_IN_REWARDED_SET_KEY, nodes_in_rewarded_set.to_string())
Event::new(MixnetEventType::IntervalConfigUpdate)
.add_attribute(
NEW_EPOCHS_DURATION_SECS_KEY,
epoch_duration_secs.to_string(),
)
.add_attribute(NEW_EPOCHS_IN_INTERVAL, epochs_in_interval.to_string())
.add_attribute(
UPDATED_INTERVAL_REWARDING_PARAMS_KEY,
updated_rewarding_params.to_inline_json(),
)
}
pub fn new_advance_interval_event(interval: Interval) -> Event {
Event::new(ADVANCE_INTERVAL_EVENT_TYPE)
.add_attribute(NEW_CURRENT_INTERVAL_KEY, interval.to_string())
}
pub fn new_advance_epoch_event(interval: Interval) -> Event {
Event::new(ADVANCE_EPOCH_EVENT_TYPE).add_attribute(NEW_CURRENT_EPOCH_KEY, interval.to_string())
pub fn new_pending_interval_config_update_event(
epochs_in_interval: u32,
epoch_duration_secs: u64,
approximate_time_remaining_secs: i64,
) -> Event {
Event::new(MixnetEventType::PendingIntervalConfigUpdate)
.add_attribute(
NEW_EPOCHS_DURATION_SECS_KEY,
epoch_duration_secs.to_string(),
)
.add_attribute(NEW_EPOCHS_IN_INTERVAL, epochs_in_interval.to_string())
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
}
@@ -1,3 +1,6 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
@@ -1,12 +1,13 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use crate::{EpochId, IntervalId};
use cosmwasm_std::Env;
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fmt::{Display, Formatter};
use std::time::Duration;
use time::OffsetDateTime;
@@ -59,12 +60,16 @@ pub(crate) mod string_rfc3339_offset_date_time {
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct Interval {
id: u32,
id: IntervalId,
epochs_in_interval: u32,
#[serde(with = "string_rfc3339_offset_date_time")]
start: OffsetDateTime,
length: Duration,
current_epoch_start: OffsetDateTime,
current_epoch_id: EpochId,
epoch_length: Duration,
total_elapsed_epochs: EpochId,
}
impl JsonSchema for Interval {
@@ -81,20 +86,46 @@ impl JsonSchema for Interval {
let object_validation = schema_object.object();
object_validation
.properties
.insert("id".to_owned(), gen.subschema_for::<u32>());
.insert("id".to_owned(), gen.subschema_for::<IntervalId>());
object_validation.required.insert("id".to_owned());
object_validation
.properties
.insert("epochs_in_interval".to_owned(), gen.subschema_for::<u32>());
object_validation
.required
.insert("epochs_in_interval".to_owned());
// PrimitiveDateTime does not implement JsonSchema. However it has a custom
// serialization to string, so we just specify the schema to be String.
object_validation.properties.insert(
"current_epoch_start".to_owned(),
gen.subschema_for::<String>(),
);
object_validation
.properties
.insert("start".to_owned(), gen.subschema_for::<String>());
object_validation.required.insert("start".to_owned());
.required
.insert("current_epoch_start".to_owned());
object_validation.properties.insert(
"current_epoch_id".to_owned(),
gen.subschema_for::<EpochId>(),
);
object_validation
.required
.insert("current_epoch_id".to_owned());
object_validation
.properties
.insert("length".to_owned(), gen.subschema_for::<Duration>());
object_validation.required.insert("length".to_owned());
.insert("epoch_length".to_owned(), gen.subschema_for::<Duration>());
object_validation.required.insert("epoch_length".to_owned());
object_validation.properties.insert(
"total_elapsed_epochs".to_owned(),
gen.subschema_for::<EpochId>(),
);
object_validation
.required
.insert("total_elapsed_epochs".to_owned());
Schema::Object(schema_object)
}
@@ -102,328 +133,451 @@ impl JsonSchema for Interval {
impl Interval {
/// Initialize epoch in the contract with default values.
pub fn init_epoch(env: Env) -> Self {
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self {
Interval {
id: 0,
epochs_in_interval,
// I really don't see a way for this to fail, unless the blockchain is lying to us
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
.expect("Invalid timestamp from env.block.time"),
length: Duration::from_secs(3600),
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64
)
.expect("Invalid timestamp from env.block.time"),
current_epoch_id: 0,
epoch_length,
total_elapsed_epochs: 0,
}
}
pub fn is_over(&self, env: Env) -> bool {
self.end_unix_timestamp() <= env.block.time.seconds() as i64
pub const fn current_epoch_id(&self) -> EpochId {
self.current_epoch_id
}
pub fn in_progress(&self, env: Env) -> bool {
let block_time = env.block.time.seconds() as i64;
self.start_unix_timestamp() <= block_time && block_time < self.end_unix_timestamp()
}
pub fn update_duration(&mut self, secs: u64) {
self.length = Duration::from_secs(secs);
}
pub const fn length_secs(&self) -> u64 {
self.length.as_secs()
}
/// Returns the next interval.
#[must_use]
pub fn next(&self) -> Self {
Interval {
id: self.id + 1,
start: self.end(),
length: self.length,
}
}
pub fn next_on_chain(&self, env: Env) -> Self {
let start = self
.end()
.max(OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap());
Interval {
id: self.id + 1,
start,
length: self.length,
}
}
/// Returns the last interval.
pub fn previous(&self) -> Option<Self> {
if self.id > 0 {
Some(Interval {
id: self.id - 1,
start: self.start - self.length,
length: self.length,
})
} else {
None
}
}
/// Determines whether the provided datetime is contained within the interval
///
/// # Arguments
///
/// * `datetime`: specified datetime
pub fn contains(&self, datetime: OffsetDateTime) -> bool {
self.start <= datetime && datetime <= self.end()
}
/// Determines whether the provided unix timestamp is contained within the interval
///
/// # Arguments
///
/// * `timestamp`: specified timestamp
pub fn contains_timestamp(&self, timestamp: i64) -> bool {
self.start_unix_timestamp() <= timestamp && timestamp <= self.end_unix_timestamp()
}
/// Returns new instance of [Interval] such that the provided datetime would be within
/// its duration.
///
/// # Arguments
///
/// * `now`: current datetime
pub fn current(&self, now: OffsetDateTime) -> Option<Self> {
let mut candidate = *self;
if now > self.start {
loop {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.next();
}
} else {
loop {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.previous()?;
}
}
}
/// Returns new instance of [Interval] such that the provided unix timestamp would be within
/// its duration.
///
/// # Arguments
///
/// * `now_unix`: current unix time
pub fn current_with_timestamp(&self, now_unix: i64) -> Option<Self> {
let mut candidate = *self;
if now_unix > self.start_unix_timestamp() {
loop {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.next();
}
} else {
loop {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.previous()?;
}
}
}
/// Checks whether this interval has already finished
///
/// # Arguments
///
/// * `now`: current datetime
pub fn has_elapsed(&self, now: OffsetDateTime) -> bool {
self.end() < now
}
/// Returns id of this interval
pub const fn id(&self) -> u32 {
pub const fn current_interval_id(&self) -> IntervalId {
self.id
}
/// Determines amount of time left until this interval finishes.
///
/// # Arguments
///
/// * `now`: current datetime
pub fn until_end(&self, now: OffsetDateTime) -> Option<Duration> {
let remaining = self.end() - now;
if remaining.is_negative() {
None
pub const fn epochs_in_interval(&self) -> u32 {
self.epochs_in_interval
}
pub fn force_change_epochs_in_interval(&mut self, epochs_in_interval: u32) {
self.epochs_in_interval = epochs_in_interval;
if self.current_epoch_id >= epochs_in_interval {
// we have to go to the next interval as we can't
// have the same (interval, epoch) combo as we had in the past
self.id += self.current_epoch_id / epochs_in_interval;
self.current_epoch_id %= epochs_in_interval;
}
}
pub fn change_epoch_length(&mut self, epoch_length: Duration) {
self.epoch_length = epoch_length
}
pub const fn total_elapsed_epochs(&self) -> u32 {
self.total_elapsed_epochs
}
pub const fn current_epoch_absolute_id(&self) -> u32 {
// since we count epochs starting from 0, if n epochs have elapsed, the current one has absolute id of n
self.total_elapsed_epochs
}
#[inline]
pub fn is_current_epoch_over(&self, env: &Env) -> bool {
self.current_epoch_end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn secs_until_current_epoch_end(&self, env: &Env) -> i64 {
if self.is_current_epoch_over(env) {
0
} else {
remaining.try_into().ok()
self.current_epoch_end_unix_timestamp() - env.block.time.seconds() as i64
}
}
#[inline]
pub fn is_current_interval_over(&self, env: &Env) -> bool {
self.current_interval_end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn secs_until_current_interval_end(&self, env: &Env) -> i64 {
if self.is_current_interval_over(env) {
0
} else {
self.current_interval_end_unix_timestamp() - env.block.time.seconds() as i64
}
}
pub fn current_epoch_in_progress(&self, env: &Env) -> bool {
let block_time = env.block.time.seconds() as i64;
self.current_epoch_start_unix_timestamp() <= block_time
&& block_time < self.current_epoch_end_unix_timestamp()
}
pub fn update_epoch_duration(&mut self, secs: u64) {
self.epoch_length = Duration::from_secs(secs);
}
pub const fn epoch_length_secs(&self) -> u64 {
self.epoch_length.as_secs()
}
/// Returns the next epoch. If if would result in advancing the interval,
/// the relevant changes are applied.
#[must_use]
pub fn advance_epoch(&self) -> Self {
// remember we start from 0th epoch, so if we're supposed to have 100 epochs in interval,
// epoch 99 is going to be the last one
if self.current_epoch_id == self.epochs_in_interval - 1 {
Interval {
id: self.id + 1,
epochs_in_interval: self.epochs_in_interval,
current_epoch_start: self.current_epoch_end(),
current_epoch_id: 0,
epoch_length: self.epoch_length,
total_elapsed_epochs: self.total_elapsed_epochs + 1,
}
} else {
Interval {
id: self.id,
epochs_in_interval: self.epochs_in_interval,
current_epoch_start: self.current_epoch_end(),
current_epoch_id: self.current_epoch_id + 1,
epoch_length: self.epoch_length,
total_elapsed_epochs: self.total_elapsed_epochs + 1,
}
}
}
/// Returns the starting datetime of this interval.
pub const fn start(&self) -> OffsetDateTime {
self.start
pub const fn current_epoch_start(&self) -> OffsetDateTime {
self.current_epoch_start
}
/// Returns the length of this interval.
pub const fn length(&self) -> Duration {
self.length
pub const fn epoch_length(&self) -> Duration {
self.epoch_length
}
/// Returns the ending datetime of this interval.
pub fn end(&self) -> OffsetDateTime {
self.start + self.length
/// Returns the ending datetime of the current epoch.
pub fn current_epoch_end(&self) -> OffsetDateTime {
self.current_epoch_start + self.epoch_length
}
/// Returns the unix timestamp of the start of this interval.
pub const fn start_unix_timestamp(&self) -> i64 {
self.start().unix_timestamp()
pub fn epochs_until_interval_end(&self) -> u32 {
self.epochs_in_interval - self.current_epoch_id
}
/// Returns the unix timestamp of the end of this interval.
pub fn end_unix_timestamp(&self) -> i64 {
self.end().unix_timestamp()
/// Returns the ending datetime of the current interval.
pub fn current_interval_end(&self) -> OffsetDateTime {
self.current_epoch_start + self.epochs_until_interval_end() * self.epoch_length
}
/// Returns the unix timestamp of the start of the current epoch.
pub const fn current_epoch_start_unix_timestamp(&self) -> i64 {
self.current_epoch_start().unix_timestamp()
}
/// Returns the unix timestamp of the end of the current epoch.
#[inline]
pub fn current_epoch_end_unix_timestamp(&self) -> i64 {
self.current_epoch_end().unix_timestamp()
}
/// Returns the unix timestamp of the end of the current interval.
#[inline]
pub fn current_interval_end_unix_timestamp(&self) -> i64 {
self.current_interval_end().unix_timestamp()
}
}
impl Display for Interval {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let length = self.length().as_secs();
let length = self.epoch_length_secs();
let full_hours = length / 3600;
let rem = length % 3600;
write!(
f,
"Interval {}: {} - {} ({}h {}s)",
"Interval {}: epoch {}/{} (current epoch begun at: {}; epoch lengths: {}h {}s)",
self.id,
self.start(),
self.end(),
self.current_epoch_id + 1,
self.epochs_in_interval,
self.current_epoch_start,
full_hours,
rem
)
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct CurrentIntervalResponse {
pub interval: Interval,
pub current_blocktime: u64,
pub is_current_interval_over: bool,
pub is_current_epoch_over: bool,
}
impl CurrentIntervalResponse {
pub fn new(interval: Interval, env: Env) -> Self {
CurrentIntervalResponse {
interval,
current_blocktime: env.block.time.seconds(),
is_current_interval_over: interval.is_current_interval_over(&env),
is_current_epoch_over: interval.is_current_epoch_over(&env),
}
}
pub fn time_until_current_epoch_end(&self) -> Duration {
if self.is_current_epoch_over {
Duration::from_secs(0)
} else {
let remaining_secs =
self.interval.current_epoch_end_unix_timestamp() - self.current_blocktime as i64;
// this should never be negative, but better safe than sorry and guard ourselves against that case
if remaining_secs <= 0 {
Duration::from_secs(0)
} else {
Duration::from_secs(remaining_secs as u64)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingEpochEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingEpochEvent>,
pub start_next_after: Option<u32>,
}
impl PendingEpochEventsResponse {
pub fn new(
seconds_until_executable: i64,
events: Vec<PendingEpochEvent>,
start_next_after: Option<u32>,
) -> Self {
PendingEpochEventsResponse {
seconds_until_executable,
events,
start_next_after,
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingIntervalEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingIntervalEvent>,
pub start_next_after: Option<u32>,
}
impl PendingIntervalEventsResponse {
pub fn new(
seconds_until_executable: i64,
events: Vec<PendingIntervalEvent>,
start_next_after: Option<u32>,
) -> Self {
PendingIntervalEventsResponse {
seconds_until_executable,
events,
start_next_after,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::mock_env;
use rand_chacha::rand_core::{RngCore, SeedableRng};
#[test]
fn previous() {
fn advancing_epoch() {
// just advancing epoch
let interval = Interval {
id: 1,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
id: 0,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 23,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
};
let expected = Interval {
id: 0,
start: time::macros::datetime!(2021-08-22 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 13:00 UTC),
current_epoch_id: 24,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 1,
};
assert_eq!(expected, interval.previous().unwrap());
assert_eq!(expected, interval.advance_epoch());
let genesis_interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert!(genesis_interval.previous().is_none());
}
#[test]
fn next() {
// results in advancing interval
let interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 99,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 42,
};
let expected = Interval {
id: 1,
start: time::macros::datetime!(2021-08-24 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 13:00 UTC),
current_epoch_id: 0,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 43,
};
assert_eq!(expected, interval.next())
assert_eq!(expected, interval.advance_epoch())
}
#[test]
fn checking_for_datetime_inclusion() {
fn checking_for_epoch_ends() {
let env = mock_env();
// epoch just begun
let interval = Interval {
id: 100,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
id: 0,
epochs_in_interval: 100,
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64 - 100,
)
.unwrap(),
current_epoch_id: 23,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
};
assert!(!interval.is_current_epoch_over(&env));
// it must contain its own boundaries
assert!(interval.contains(interval.start));
assert!(interval.contains(interval.end()));
// current time == current epoch start
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap();
assert!(!interval.is_current_epoch_over(&env));
let in_the_midle = interval.start + Duration::from_secs(interval.length.as_secs() / 2);
assert!(interval.contains(in_the_midle));
// epoch HASN'T yet begun (weird edge case, but can happen if we decide to manually adjust things)
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64 + 100).unwrap();
assert!(!interval.is_current_epoch_over(&env));
assert!(!interval.contains(interval.next().end()));
assert!(!interval.contains(interval.previous().unwrap().start()));
// current_time = EXACTLY end of the epoch
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap()
- interval.epoch_length;
assert!(interval.is_current_epoch_over(&env));
// revert time a bit more
interval.current_epoch_start -= Duration::from_secs(42);
assert!(interval.is_current_epoch_over(&env));
// revert by A LOT -> epoch still should be in finished state
interval.current_epoch_start -= Duration::from_secs(5 * 31 * 60 * 60);
assert!(interval.is_current_epoch_over(&env));
}
#[test]
fn determining_current_interval() {
let first_interval = Interval {
id: 100,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
fn interval_end() {
let mut interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 99,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
};
// interval just before
let fake_now = first_interval.start - Duration::from_secs(123);
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
// this interval (start boundary)
assert_eq!(
first_interval,
first_interval.current(first_interval.start).unwrap()
interval.current_epoch_start + interval.epoch_length,
interval.current_interval_end()
);
// this interval (in the middle)
let fake_now = first_interval.start + Duration::from_secs(123);
assert_eq!(first_interval, first_interval.current(fake_now).unwrap());
// this interval (end boundary)
interval.current_epoch_id -= 1;
assert_eq!(
first_interval,
first_interval.current(first_interval.end()).unwrap()
interval.current_epoch_start + 2 * interval.epoch_length,
interval.current_interval_end()
);
// next interval
let fake_now = first_interval.end() + Duration::from_secs(123);
interval.current_epoch_id -= 10;
assert_eq!(
first_interval.next(),
first_interval.current(fake_now).unwrap()
interval.current_epoch_start + 12 * interval.epoch_length,
interval.current_interval_end()
);
// few intervals in the past
let fake_now = first_interval.start()
- first_interval.length
- first_interval.length
- first_interval.length;
interval.current_epoch_id = 0;
assert_eq!(
first_interval
.previous()
.unwrap()
.previous()
.unwrap()
.previous()
.unwrap(),
first_interval.current(fake_now).unwrap()
interval.current_epoch_start + interval.epochs_in_interval * interval.epoch_length,
interval.current_interval_end()
);
}
// few intervals in the future
let fake_now = first_interval.end()
+ first_interval.length
+ first_interval.length
+ first_interval.length;
assert_eq!(
first_interval.next().next().next(),
first_interval.current(fake_now).unwrap()
);
#[test]
fn checking_for_interval_ends() {
let env = mock_env();
let epoch_length = Duration::from_secs(60 * 60);
let mut interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64
)
.unwrap(),
current_epoch_id: 98,
epoch_length,
total_elapsed_epochs: 0,
};
// current epoch just started (we still have to finish 2 epochs)
assert!(!interval.is_current_interval_over(&env));
// still need to finish the 99th epoch
interval.current_epoch_start -= epoch_length;
assert!(!interval.is_current_interval_over(&env));
// it JUST finished
interval.current_epoch_start -= epoch_length;
assert!(interval.is_current_interval_over(&env));
// nobody updated the interval data, but the current one should still be in finished state
interval.current_epoch_start -= 10 * epoch_length;
assert!(interval.is_current_interval_over(&env));
}
#[test]
fn getting_current_full_epoch_id() {
let env = mock_env();
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let epoch_length = Duration::from_secs(60 * 60);
let mut interval = Interval::init_interval(100, epoch_length, &env);
// normal situation
for i in 0u32..2000 {
assert_eq!(interval.current_epoch_absolute_id(), i);
interval = interval.advance_epoch();
}
let mut interval = Interval::init_interval(100, epoch_length, &env);
for i in 0u32..2000 {
// every few epochs decide to change epochs in interval
if i % 7 == 0 {
let new_epochs_in_interval = (rng.next_u32() % 200) + 42;
interval.force_change_epochs_in_interval(new_epochs_in_interval)
}
// make sure full epoch id is always monotonically increasing
assert_eq!(interval.current_epoch_absolute_id(), i);
interval = interval.advance_epoch();
}
}
}
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod constants;
pub mod delegation;
pub mod error;
pub mod events;
@@ -8,29 +9,31 @@ pub mod gateway;
mod interval;
pub mod mixnode;
mod msg;
pub mod pending_events;
pub mod reward_params;
pub mod rewarding;
mod types;
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
pub use cosmwasm_std::{Addr, Coin};
pub use contracts_common::types::*;
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixDelegationsResponse,
PagedMixNodeDelegationsResponse,
};
pub use gateway::{
Gateway, GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse,
};
pub use interval::Interval;
pub use interval::{
CurrentIntervalResponse, Interval, PendingEpochEventsResponse, PendingIntervalEventsResponse,
};
pub use mixnode::{
Layer, MixNode, MixNodeBond, MixOwnershipResponse, MixnodeBondResponse, PagedMixnodeResponse,
RewardedSetNodeStatus,
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
MixNodeRewarding, MixOwnershipResponse, MixnodeDetailsResponse, PagedMixnodeBondsResponse,
RewardedSetNodeStatus, UnbondedMixnode,
};
pub use msg::*;
pub use pending_events::{
PendingEpochEvent, PendingEpochEventData, PendingIntervalEvent, PendingIntervalEventData,
};
pub use reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate, RewardingParams};
pub use types::*;
pub type U128 = fixed::types::U75F53;
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
File diff suppressed because it is too large Load Diff
@@ -1,160 +1,342 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::reward_params::NodeRewardParams;
use crate::ContractStateParams;
use crate::delegation::OwnerProxySubKey;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
};
use crate::{delegation, ContractStateParams, NodeId, Percent};
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
pub rewarding_validator_address: String,
pub mixnet_denom: String,
pub vesting_contract_address: String,
pub rewarding_denom: String,
pub epochs_in_interval: u32,
pub epoch_duration: Duration,
pub initial_rewarding_params: InitialRewardingParams,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InitialRewardingParams {
pub initial_reward_pool: Decimal,
pub initial_staking_supply: Decimal,
pub sybil_resistance: Percent,
pub active_set_work_factor: Decimal,
pub interval_pool_emission: Percent,
pub rewarded_set_size: u32,
pub active_set_size: u32,
}
impl InitialRewardingParams {
pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams {
let epoch_reward_budget = self.initial_reward_pool
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
* self.interval_pool_emission;
let stake_saturation_point =
self.initial_staking_supply / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
RewardingParams {
interval: IntervalRewardParams {
reward_pool: self.initial_reward_pool,
staking_supply: self.initial_staking_supply,
epoch_reward_budget,
stake_saturation_point,
sybil_resistance: self.sybil_resistance,
active_set_work_factor: self.active_set_work_factor,
interval_pool_emission: self.interval_pool_emission,
},
rewarded_set_size: self.rewarded_set_size,
active_set_size: self.active_set_size,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// state/sys-params-related
UpdateRewardingValidatorAddress {
address: String,
},
InitEpoch {},
ReconcileDelegations {},
CheckpointMixnodes {},
CompoundOperatorRewardOnBehalf {
owner: String,
UpdateContractStateParams {
updated_parameters: ContractStateParams,
},
CompoundDelegatorRewardOnBehalf {
owner: String,
mix_identity: IdentityKey,
UpdateActiveSetSize {
active_set_size: u32,
force_immediately: bool,
},
CompoundOperatorReward {},
CompoundDelegatorReward {
mix_identity: IdentityKey,
UpdateRewardingParams {
updated_params: IntervalRewardingParamsUpdate,
force_immediately: bool,
},
CompoundReward {
operator: Option<String>,
delegator: Option<String>,
mix_identity: Option<IdentityKey>,
proxy: Option<String>,
UpdateIntervalConfig {
epochs_in_interval: u32,
epoch_duration_secs: u64,
force_immediately: bool,
},
AdvanceCurrentEpoch {
new_rewarded_set: Vec<NodeId>,
expected_active_set_size: u32,
},
ReconcileEpochEvents {
limit: Option<u32>,
},
// mixnode-related:
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
},
UnbondMixnode {},
UpdateMixnodeConfig {
profit_margin_percent: u8,
},
UpdateMixnodeConfigOnBehalf {
profit_margin_percent: u8,
owner: String,
},
BondGateway {
gateway: Gateway,
owner_signature: String,
},
UnbondGateway {},
UpdateContractStateParams(ContractStateParams),
DelegateToMixnode {
mix_identity: IdentityKey,
},
UndelegateFromMixnode {
mix_identity: IdentityKey,
},
RewardMixnode {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
},
// RewardNextMixDelegators {
// mix_identity: IdentityKey,
// // id of the current rewarding interval
// interval_id: u32,
// },
DelegateToMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
},
UndelegateFromMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
},
BondMixnodeOnBehalf {
mix_node: MixNode,
owner: String,
cost_params: MixNodeCostParams,
owner_signature: String,
owner: String,
},
UnbondMixnode {},
UnbondMixnodeOnBehalf {
owner: String,
},
UpdateMixnodeCostParams {
new_costs: MixNodeCostParams,
},
UpdateMixnodeCostParamsOnBehalf {
new_costs: MixNodeCostParams,
owner: String,
},
UpdateMixnodeConfig {
new_config: MixNodeConfigUpdate,
},
UpdateMixnodeConfigOnBehalf {
new_config: MixNodeConfigUpdate,
owner: String,
},
// gateway-related:
BondGateway {
gateway: Gateway,
owner_signature: String,
},
BondGatewayOnBehalf {
gateway: Gateway,
owner: String,
owner_signature: String,
},
UnbondGateway {},
UnbondGatewayOnBehalf {
owner: String,
},
WriteRewardedSet {
rewarded_set: Vec<IdentityKey>,
expected_active_set_size: u32,
// delegation-related:
DelegateToMixnode {
mix_id: NodeId,
},
// AdvanceCurrentInterval {},
AdvanceCurrentEpoch {},
ClaimOperatorReward {},
ClaimOperatorRewardOnBehalf {
DelegateToMixnodeOnBehalf {
mix_id: NodeId,
delegate: String,
},
UndelegateFromMixnode {
mix_id: NodeId,
},
UndelegateFromMixnodeOnBehalf {
mix_id: NodeId,
delegate: String,
},
// reward-related
RewardMixnode {
mix_id: NodeId,
performance: Performance,
},
WithdrawOperatorReward {},
WithdrawOperatorRewardOnBehalf {
owner: String,
},
ClaimDelegatorReward {
mix_identity: IdentityKey,
WithdrawDelegatorReward {
mix_id: NodeId,
},
ClaimDelegatorRewardOnBehalf {
mix_identity: IdentityKey,
WithdrawDelegatorRewardOnBehalf {
mix_id: NodeId,
owner: String,
},
}
impl ExecuteMsg {
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
format!("updating rewarding validator to {}", address)
}
ExecuteMsg::UpdateContractStateParams { .. } => {
"updating mixnet state parameters".into()
}
ExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
} => format!(
"updating active set size to {}. forced: {}",
active_set_size, force_immediately
),
ExecuteMsg::UpdateRewardingParams {
force_immediately, ..
} => format!(
"updating mixnet rewarding parameters. forced: {}",
force_immediately
),
ExecuteMsg::UpdateIntervalConfig {
force_immediately, ..
} => format!(
"updating mixnet interval configuration. forced: {}",
force_immediately
),
ExecuteMsg::AdvanceCurrentEpoch { .. } => "advancing current epoch".into(),
ExecuteMsg::ReconcileEpochEvents { .. } => "reconciling epoch events".into(),
ExecuteMsg::BondMixnode { mix_node, .. } => {
format!("bonding mixnode {}", mix_node.identity_key)
}
ExecuteMsg::BondMixnodeOnBehalf { mix_node, .. } => {
format!("bonding mixnode {} on behalf", mix_node.identity_key)
}
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. } => {
"updating mixnode cost parameters on behalf".into()
}
ExecuteMsg::UpdateMixnodeConfig { .. } => "updating mixnode configuration".into(),
ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. } => {
"updating mixnode configuration on behalf".into()
}
ExecuteMsg::BondGateway { gateway, .. } => {
format!("bonding gateway {}", gateway.identity_key)
}
ExecuteMsg::BondGatewayOnBehalf { gateway, .. } => {
format!("bonding gateway {} on behalf", gateway.identity_key)
}
ExecuteMsg::UnbondGateway { .. } => "unbonding gateway".into(),
ExecuteMsg::UnbondGatewayOnBehalf { .. } => "unbonding gateway on behalf".into(),
ExecuteMsg::DelegateToMixnode { mix_id } => format!("delegating to mixnode {}", mix_id),
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, .. } => {
format!("delegating to mixnode {} on behalf", mix_id)
}
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
format!("removing delegation from mixnode {}", mix_id)
}
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, .. } => {
format!("removing delegation from mixnode {} on behalf", mix_id)
}
ExecuteMsg::RewardMixnode {
mix_id,
performance,
} => format!(
"rewarding mixnode {} for performance {}",
mix_id, performance
),
ExecuteMsg::WithdrawOperatorReward { .. } => "withdrawing operator reward".into(),
ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. } => {
"withdrawing operator reward on behalf".into()
}
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
format!("withdrawing delegator reward from mixnode {}", mix_id)
}
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => format!(
"withdrawing delegator reward from mixnode {} on behalf",
mix_id
),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetBlacklistedNodes {},
GetCurrentOperatorCost {},
GetRewardingValidatorAddress {},
GetAllDelegationKeys {},
DebugGetAllDelegationValues {},
// state/sys-params-related
GetContractVersion {},
GetMixNodes {
GetRewardingValidatorAddress {},
GetStateParams {},
GetState {},
GetRewardingParams {},
GetCurrentIntervalDetails {},
GetRewardedSet {
limit: Option<u32>,
start_after: Option<IdentityKey>,
start_after: Option<NodeId>,
},
// mixnode-related:
GetMixNodeBonds {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetMixNodesDetailed {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodes {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodesByOwner {
owner: String,
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodesByIdentityKey {
identity_key: String,
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetOwnedMixnode {
address: String,
},
GetMixnodeDetails {
mix_id: NodeId,
},
GetMixnodeRewardingDetails {
mix_id: NodeId,
},
GetStakeSaturation {
mix_id: NodeId,
},
GetUnbondedMixNodeInformation {
mix_id: NodeId,
},
GetBondedMixnodeDetailsByIdentity {
mix_identity: IdentityKey,
},
GetLayerDistribution {},
// gateway-related:
GetGateways {
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
OwnsMixnode {
address: String,
},
OwnsGateway {
address: String,
},
GetMixnodeBond {
identity: IdentityKey,
},
GetGatewayBond {
identity: IdentityKey,
},
StateParams {},
GetOwnedGateway {
address: String,
},
// delegation-related:
// gets all [paged] delegations associated with particular mixnode
GetMixnodeDelegations {
mix_identity: IdentityKey,
mix_id: NodeId,
// since `start_after` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
start_after: Option<(String, u64)>,
start_after: Option<String>,
limit: Option<u32>,
},
// gets all [paged] delegations associated with particular delegator
@@ -162,81 +344,56 @@ pub enum QueryMsg {
// since `delegator` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
delegator: String,
start_after: Option<IdentityKey>,
start_after: Option<(NodeId, OwnerProxySubKey)>,
limit: Option<u32>,
},
// gets delegation associated with particular mixnode, delegator pair
GetDelegationDetails {
mix_identity: IdentityKey,
mix_id: NodeId,
delegator: String,
proxy: Option<String>,
},
LayerDistribution {},
GetRewardPool {},
GetCirculatingSupply {},
GetStakingSupply {},
GetIntervalRewardPercent {},
GetSybilResistancePercent {},
GetActiveSetWorkFactor {},
GetRewardingStatus {
mix_identity: IdentityKey,
interval_id: u32,
},
GetRewardedSet {
height: Option<u64>,
start_after: Option<IdentityKey>,
// gets all delegations in the system
GetAllDelegations {
start_after: Option<delegation::StorageKey>,
limit: Option<u32>,
},
GetRewardedSetUpdateDetails {},
GetCurrentRewardedSetHeight {},
GetRewardedSetRefreshBlocks {},
GetCurrentEpoch {},
GetEpochsInInterval {},
QueryOperatorReward {
// rewards related
GetPendingOperatorReward {
address: String,
},
QueryDelegatorReward {
GetPendingMixNodeOperatorReward {
mix_id: NodeId,
},
GetPendingDelegatorReward {
address: String,
mix_identity: IdentityKey,
mix_id: NodeId,
proxy: Option<String>,
},
GetPendingDelegationEvents {
owner_address: String,
proxy_address: Option<String>,
// given the provided performance, estimate the reward at the end of the current epoch
GetEstimatedCurrentEpochOperatorReward {
mix_id: NodeId,
estimated_performance: Performance,
},
GetCheckpointsForMixnode {
mix_identity: IdentityKey,
GetEstimatedCurrentEpochDelegatorReward {
address: String,
mix_id: NodeId,
proxy: Option<String>,
estimated_performance: Performance,
},
GetMixnodeAtHeight {
mix_identity: IdentityKey,
height: u64,
// interval-related
GetPendingEpochEvents {
limit: Option<u32>,
start_after: Option<u32>,
},
GetPendingIntervalEvents {
limit: Option<u32>,
start_after: Option<u32>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub nodes_to_remove: Option<Vec<NodeToRemove>>,
}
impl MigrateMsg {
pub fn nodes_to_remove(&self) -> Vec<NodeToRemove> {
self.nodes_to_remove.clone().unwrap_or_default()
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct NodeToRemove {
owner: String,
proxy: Option<String>,
}
impl NodeToRemove {
pub fn owner(&self) -> &str {
&self.owner
}
pub fn proxy(&self) -> Option<&String> {
self.proxy.as_ref()
}
}
pub struct MigrateMsg {}
@@ -0,0 +1,77 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::MixNodeCostParams;
use crate::reward_params::IntervalRewardingParamsUpdate;
use crate::{EpochEventId, IntervalEventId, NodeId};
use cosmwasm_std::{Addr, Coin};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingEpochEvent {
pub id: EpochEventId,
pub event: PendingEpochEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum PendingEpochEventData {
// can't just pass the `Delegation` struct here as it's impossible to determine
// `cumulative_reward_ratio` ahead of time
Delegate {
owner: Addr,
mix_id: NodeId,
amount: Coin,
proxy: Option<Addr>,
},
Undelegate {
owner: Addr,
mix_id: NodeId,
proxy: Option<Addr>,
},
UnbondMixnode {
mix_id: NodeId,
},
UpdateActiveSetSize {
new_size: u32,
},
}
impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
fn from(data: (EpochEventId, PendingEpochEventData)) -> Self {
PendingEpochEvent {
id: data.0,
event: data.1,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingIntervalEvent {
pub id: EpochEventId,
pub event: PendingIntervalEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum PendingIntervalEventData {
ChangeMixCostParams {
mix_id: NodeId,
new_costs: MixNodeCostParams,
},
UpdateRewardingParams {
update: IntervalRewardingParamsUpdate,
},
UpdateIntervalConfig {
epochs_in_interval: u32,
epoch_duration_secs: u64,
},
}
impl From<(IntervalEventId, PendingIntervalEventData)> for PendingIntervalEvent {
fn from(data: (IntervalEventId, PendingIntervalEventData)) -> Self {
PendingIntervalEvent {
id: data.0,
event: data.1,
}
}
}
@@ -1,288 +1,264 @@
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
use az::CheckedCast;
use cosmwasm_std::Uint128;
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{error::MixnetContractError, Percent};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct NodeEpochRewards {
params: NodeRewardParams,
result: StoredNodeRewardResult,
epoch_id: u32,
pub type Performance = Percent;
/// Parameters required by the mix-mining reward distribution that do not change during an interval.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/IntervalRewardParams.ts")
)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct IntervalRewardParams {
/// Current value of the rewarding pool.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub reward_pool: Decimal,
/// Current value of the staking supply.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub staking_supply: Decimal,
// computed values
/// Current value of the computed reward budget per epoch, per node.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub epoch_reward_budget: Decimal,
/// Current value of the stake saturation point.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub stake_saturation_point: Decimal,
// constants(-ish)
// default: 30%
/// Current value of the sybil resistance percent (`alpha`).
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub sybil_resistance: Percent,
// default: 10
/// Current active set work factor.
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub active_set_work_factor: Decimal,
// default: 2%
/// Current maximum interval pool emission.
/// Assuming all nodes in the rewarded set are fully saturated and have 100% performance,
/// this % of the reward pool would get distributed in rewards to all operators and its delegators.
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub interval_pool_emission: Percent,
}
impl NodeEpochRewards {
pub fn new(params: NodeRewardParams, result: StoredNodeRewardResult, epoch_id: u32) -> Self {
Self {
params,
result,
epoch_id,
}
}
pub fn epoch_id(&self) -> u32 {
self.epoch_id
}
pub fn sigma(&self) -> U128 {
self.result.sigma()
}
pub fn lambda(&self) -> U128 {
self.result.lambda()
}
pub fn params(&self) -> NodeRewardParams {
self.params
}
pub fn reward(&self) -> Uint128 {
self.result.reward()
}
pub fn operator_cost(&self, base_operator_cost: u64) -> U128 {
self.params.operator_cost(base_operator_cost)
}
pub fn node_profit(&self, base_operator_cost: u64) -> U128 {
let reward = U128::from_num(self.reward().u128());
// if operating cost is higher then the reward node profit is 0
reward.saturating_sub(self.operator_cost(base_operator_cost))
}
pub fn operator_reward(
&self,
profit_margin: U128,
base_operator_cost: u64,
) -> Result<Uint128, MixnetContractError> {
let reward = self.node_profit(base_operator_cost);
let operator_base_reward = reward.min(self.operator_cost(base_operator_cost));
let div_by_zero_check = if let Some(value) = self.lambda().checked_div(self.sigma()) {
value
} else {
return Err(MixnetContractError::DivisionByZero);
};
let operator_reward = (profit_margin + (ONE - profit_margin) * div_by_zero_check) * reward;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
}
pub fn delegation_reward(
&self,
delegation_amount: Uint128,
profit_margin: U128,
base_operator_cost: u64,
epoch_reward_params: EpochRewardParams,
) -> Result<Uint128, MixnetContractError> {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let staking_supply = U128::from_num(epoch_reward_params.staking_supply());
let scaled_delegation_amount = delegation_amount / staking_supply;
let check_div_by_zero =
if let Some(value) = scaled_delegation_amount.checked_div(self.sigma()) {
value
} else {
return Err(MixnetContractError::DivisionByZero);
};
let delegator_reward =
(ONE - profit_margin) * check_div_by_zero * self.node_profit(base_operator_cost);
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
impl IntervalRewardParams {
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct EpochRewardParams {
epoch_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
#[serde(alias = "circulating_supply")]
staking_supply: Uint128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/RewardingParams.ts")
)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct RewardingParams {
/// Parameters that should remain unchanged throughout an interval.
pub interval: IntervalRewardParams,
// while the active set size can change between epochs to accommodate for bandwidth demands,
// the active set size should be unchanged between epochs and should only be adjusted between
// intervals. However, it makes more sense to keep both of those values together as they're
// very strongly related to each other.
pub rewarded_set_size: u32,
pub active_set_size: u32,
}
impl EpochRewardParams {
pub fn new(
epoch_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
staking_supply: u128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
) -> EpochRewardParams {
EpochRewardParams {
epoch_reward_pool: Uint128::new(epoch_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
staking_supply: Uint128::new(staking_supply),
sybil_resistance_percent,
active_set_work_factor,
impl RewardingParams {
pub fn active_node_work(&self) -> Decimal {
self.interval.active_set_work_factor * self.standby_node_work()
}
pub fn standby_node_work(&self) -> Decimal {
let f = self.interval.active_set_work_factor;
let k = self.dec_rewarded_set_size();
let one = Decimal::one();
// nodes in reserve
let k_r = self.dec_standby_set_size();
one / (f * k - (f - one) * k_r)
}
pub fn dec_rewarded_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.rewarded_set_size, 0).unwrap()
}
pub fn dec_active_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.active_set_size, 0).unwrap()
}
fn dec_standby_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap()
}
pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(new_epochs_in_interval, 0).unwrap()
* self.interval.interval_pool_emission;
}
pub fn try_change_active_set_size(
&mut self,
new_active_set_size: u32,
) -> Result<(), MixnetContractError> {
if new_active_set_size == 0 {
return Err(MixnetContractError::ZeroActiveSet);
}
}
// technically it's identical to what would have been derived with a Default implementation,
// however, I prefer to be explicit about it, as a `Default::default` value makes no sense
// apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway
// (it's guarded behind an `initialised` flag)
pub fn new_empty() -> Self {
EpochRewardParams {
epoch_reward_pool: Uint128::new(0),
staking_supply: Uint128::new(0),
sybil_resistance_percent: 0,
rewarded_set_size: Uint128::new(0),
active_set_size: Uint128::new(0),
active_set_work_factor: 0,
if new_active_set_size > self.rewarded_set_size {
return Err(MixnetContractError::InvalidActiveSetSize);
}
self.active_set_size = new_active_set_size;
Ok(())
}
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
}
pub fn try_apply_updates(
&mut self,
updates: IntervalRewardingParamsUpdate,
epochs_in_interval: u32,
) -> Result<(), MixnetContractError> {
if !updates.contains_updates() {
return Err(MixnetContractError::EmptyParamsChangeMsg);
}
pub fn active_set_size(&self) -> u128 {
self.active_set_size.u128()
}
let mut recompute_epoch_budget = false;
let mut recompute_saturation_point = false;
pub fn staking_supply(&self) -> u128 {
self.staking_supply.u128()
}
if let Some(reward_pool) = updates.reward_pool {
recompute_epoch_budget = true;
self.interval.reward_pool = reward_pool;
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch_reward_pool.u128()
}
if let Some(staking_supply) = updates.staking_supply {
recompute_saturation_point = true;
self.interval.staking_supply = staking_supply;
}
pub fn sybil_resistance_percent(&self) -> u8 {
self.sybil_resistance_percent
}
if let Some(sybil_resistance_percent) = updates.sybil_resistance_percent {
self.interval.sybil_resistance = sybil_resistance_percent;
}
pub fn active_set_work_factor(&self) -> u8 {
self.active_set_work_factor
if let Some(active_set_work_factor) = updates.active_set_work_factor {
self.interval.active_set_work_factor = active_set_work_factor;
}
if let Some(interval_pool_emission) = updates.interval_pool_emission {
recompute_epoch_budget = true;
self.interval.interval_pool_emission = interval_pool_emission;
}
if let Some(rewarded_set_size) = updates.rewarded_set_size {
if rewarded_set_size == 0 {
return Err(MixnetContractError::ZeroRewardedSet);
}
if rewarded_set_size < self.active_set_size {
return Err(MixnetContractError::InvalidRewardedSetSize);
}
recompute_saturation_point = true;
self.rewarded_set_size = rewarded_set_size;
}
if recompute_epoch_budget {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
* self.interval.interval_pool_emission;
}
if recompute_saturation_point {
self.interval.stake_saturation_point = self.interval.staking_supply
/ Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
}
Ok(())
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
// TODO: possibly refactor this
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct NodeRewardParams {
reward_blockstamp: u64,
uptime: Uint128,
in_active_set: bool,
pub performance: Percent,
pub in_active_set: bool,
}
impl NodeRewardParams {
pub fn new(reward_blockstamp: u64, uptime: u128, in_active_set: bool) -> NodeRewardParams {
pub fn new(performance: Percent, in_active_set: bool) -> Self {
NodeRewardParams {
reward_blockstamp,
uptime: Uint128::new(uptime),
performance,
in_active_set,
}
}
pub fn operator_cost(&self, base_operator_cost: u64) -> U128 {
self.performance() * U128::from_num(base_operator_cost)
}
pub fn uptime(&self) -> Uint128 {
self.uptime
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct RewardParams {
pub epoch: EpochRewardParams,
pub node: NodeRewardParams,
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts")
)]
#[derive(
Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema,
)]
pub struct IntervalRewardingParamsUpdate {
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub reward_pool: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub staking_supply: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub sybil_resistance_percent: Option<Percent>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub active_set_work_factor: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub interval_pool_emission: Option<Percent>,
pub rewarded_set_size: Option<u32>,
}
impl RewardParams {
pub fn new(epoch: EpochRewardParams, node: NodeRewardParams) -> RewardParams {
RewardParams { epoch, node }
impl IntervalRewardingParamsUpdate {
pub fn contains_updates(&self) -> bool {
// essentially at least a single field has to be a `Some`
self.reward_pool.is_some()
|| self.staking_supply.is_some()
|| self.sybil_resistance_percent.is_some()
|| self.active_set_work_factor.is_some()
|| self.interval_pool_emission.is_some()
|| self.rewarded_set_size.is_some()
}
pub fn omega(&self) -> U128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if denom == 0 {
return U128::ZERO;
}
// Div by zero checked above
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
}
pub fn idle_nodes(&self) -> Uint128 {
self.epoch.rewarded_set_size - self.epoch.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.epoch.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.node.in_active_set
}
pub fn performance(&self) -> U128 {
self.node.performance()
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.node.reward_blockstamp = blockstamp;
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch.epoch_reward_pool.u128()
}
pub fn rewarded_set_size(&self) -> u128 {
self.epoch.rewarded_set_size.u128()
}
pub fn staking_supply(&self) -> u128 {
self.epoch.staking_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.node.reward_blockstamp
}
pub fn uptime(&self) -> Uint128 {
self.node.uptime()
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
@@ -0,0 +1,23 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Coin, Decimal, Uint128};
/// Truncates all decimal points so that the reward would fit in a `Coin` and so that we would
/// never attempt to reward more than the owner is due
/// for example it truncates "23.9" into "23"
pub fn truncate_reward(reward: Decimal, denom: impl Into<String>) -> Coin {
let amount = truncate_reward_amount(reward);
Coin {
denom: denom.into(),
amount,
}
}
pub fn truncate_reward_amount(reward: Decimal) -> Uint128 {
truncate_decimal(reward)
}
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
amount * Uint128::new(1)
}
@@ -0,0 +1,61 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub mod helpers;
pub mod simulator;
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct RewardEstimate {
pub total_node_reward: Decimal,
// note that operator reward includes the operating_cost,
// i.e. say total_node_reward was `1nym` and operating_cost was `2nym`
// in that case the operator reward would still be `1nym` as opposed to 0
pub operator: Decimal,
pub delegates: Decimal,
pub operating_cost: Decimal,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct RewardDistribution {
pub operator: Decimal,
pub delegates: Decimal,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
pub struct PendingRewardResponse {
pub amount_staked: Option<Coin>,
pub amount_earned: Option<Coin>,
pub amount_earned_detailed: Option<Decimal>,
/// The associated mixnode is still fully bonded, meaning it is neither unbonded
/// nor in the process of unbonding that would have finished at the epoch transition.
pub mixnode_still_fully_bonded: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
pub struct EstimatedCurrentEpochRewardResponse {
pub original_stake: Option<Coin>,
pub current_stake_value: Option<Coin>,
pub current_stake_value_detailed_amount: Option<Decimal>,
pub estimation: Option<Coin>,
pub detailed_estimation_amount: Option<Decimal>,
}
impl EstimatedCurrentEpochRewardResponse {
pub fn empty_response() -> Self {
EstimatedCurrentEpochRewardResponse {
original_stake: None,
current_stake_value: None,
current_stake_value_detailed_amount: None,
estimation: None,
detailed_estimation_amount: None,
}
}
}
@@ -0,0 +1,465 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError;
use crate::mixnode::{MixNodeCostParams, MixNodeRewarding};
use crate::reward_params::{IntervalRewardParams, NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{Delegation, Interval, Percent};
use cosmwasm_std::{Addr, Coin, Decimal};
pub struct Simulator {
pub node_rewarding_details: MixNodeRewarding,
pub node_delegations: Vec<Delegation>,
pub system_rewarding_params: RewardingParams,
pub interval: Interval,
}
impl Simulator {
pub fn new(
profit_margin_percent: Percent,
interval_operating_cost: Coin,
system_rewarding_params: RewardingParams,
initial_pledge: Coin,
interval: Interval,
) -> Self {
let cost_params = MixNodeCostParams {
profit_margin_percent,
interval_operating_cost,
};
Simulator {
node_rewarding_details: MixNodeRewarding::initialise_new(
cost_params,
&initial_pledge,
Default::default(),
),
node_delegations: vec![],
system_rewarding_params,
interval,
}
}
pub fn delegate(&mut self, amount: Coin) {
let cumulative_reward_ratio = self.node_rewarding_details.total_unit_reward;
// let record = self.node_rewarding_details.increment_period();
// self.node_historical_records.insert(period, record);
self.node_rewarding_details
.add_base_delegation(amount.amount);
// we don't care about the owner/node details here
self.node_delegations.push(Delegation::new(
Addr::unchecked("bob"),
42,
cumulative_reward_ratio,
amount,
123,
None,
))
}
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
self.node_rewarding_details
.determine_delegation_reward(delegation)
}
// since this is a simulator only, not something to be used in the production code, the unwraps are fine
// if user inputs are invalid
pub fn undelegate(
&mut self,
delegation_index: usize,
) -> Result<(Coin, Coin), MixnetContractError> {
let delegation = self.node_delegations.remove(delegation_index);
let reward = self.determine_delegation_reward(&delegation);
self.node_rewarding_details
.decrease_delegates_decimal(delegation.dec_amount() + reward)?;
self.node_rewarding_details.unique_delegations -= 1;
let reward_denom = &delegation.amount.denom;
let truncated_reward = truncate_reward(reward, reward_denom);
// if this was last delegation, move all leftover decimal tokens to the operator
// (this is literally in the order of a millionth of a micronym)
if self.node_delegations.is_empty() {
assert_eq!(self.node_rewarding_details.unique_delegations, 0);
self.node_rewarding_details.operator += self.node_rewarding_details.delegates;
self.node_rewarding_details.delegates = Decimal::zero();
}
Ok((delegation.amount, truncated_reward))
}
pub fn determine_total_delegation_reward(&self) -> Decimal {
let mut total = Decimal::zero();
for delegation in &self.node_delegations {
total += self.determine_delegation_reward(delegation)
}
total
}
pub fn simulate_epoch(&mut self, node_params: NodeRewardParams) -> RewardDistribution {
let reward_distribution = self.node_rewarding_details.calculate_epoch_reward(
&self.system_rewarding_params,
node_params,
self.interval.epochs_in_interval(),
);
self.node_rewarding_details.distribute_rewards(
reward_distribution,
self.interval.current_epoch_absolute_id(),
);
self.interval = self.interval.advance_epoch();
reward_distribution
}
// assume node state doesn't change in the interval (kinda unrealistic)
pub fn simulate_interval(&mut self, node_params: NodeRewardParams) {
assert_eq!(self.interval.current_epoch_id(), 0);
let id = self.interval.current_interval_id();
let mut distributed = Decimal::zero();
for _ in 0..self.interval.epochs_in_interval() {
let distr = self.simulate_epoch(node_params);
distributed += distr.operator;
distributed += distr.delegates;
}
assert_eq!(id + 1, self.interval.current_interval_id());
// update reward pool and all of that
let old = self.system_rewarding_params.interval;
let reward_pool = old.reward_pool - distributed;
let staking_supply = old.staking_supply + distributed;
let epoch_reward_budget = reward_pool
/ Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap()
* old.interval_pool_emission.value();
let stake_saturation_point = staking_supply
/ Decimal::from_atomics(self.system_rewarding_params.rewarded_set_size, 0).unwrap();
let updated_params = RewardingParams {
interval: IntervalRewardParams {
reward_pool,
staking_supply,
epoch_reward_budget,
stake_saturation_point,
sybil_resistance: old.sybil_resistance,
active_set_work_factor: old.active_set_work_factor,
interval_pool_emission: old.interval_pool_emission,
},
rewarded_set_size: self.system_rewarding_params.rewarded_set_size,
active_set_size: self.system_rewarding_params.active_set_size,
};
self.system_rewarding_params = updated_params;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reward_params::IntervalRewardParams;
use crate::rewarding::helpers::truncate_reward_amount;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{coin, Uint128};
use std::time::Duration;
fn base_simulator(initial_pledge: u128) -> Simulator {
let profit_margin = Percent::from_percentage_value(10).unwrap();
let interval_operating_cost = Coin::new(40_000_000, "unym");
let epochs_in_interval = 720u32;
let rewarded_set_size = 240;
let active_set_size = 100;
let interval_pool_emission = Percent::from_percentage_value(2).unwrap();
let reward_pool = 250_000_000_000_000u128;
let staking_supply = 100_000_000_000_000u128;
let epoch_reward_budget =
interval_pool_emission * Decimal::from_ratio(reward_pool, epochs_in_interval);
let stake_saturation_point = Decimal::from_ratio(staking_supply, rewarded_set_size);
let rewarding_params = RewardingParams {
interval: IntervalRewardParams {
reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(), // 250M * 1M (we're expressing it all in base tokens)
staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(), // 100M * 1M
epoch_reward_budget,
stake_saturation_point,
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
active_set_work_factor: Decimal::percent(1000), // value '10'
interval_pool_emission,
},
rewarded_set_size,
active_set_size,
};
let interval = Interval::init_interval(
epochs_in_interval,
Duration::from_secs(60 * 60),
&mock_env(),
);
let initial_pledge = Coin::new(initial_pledge, "unym");
Simulator::new(
profit_margin,
interval_operating_cost,
rewarding_params,
initial_pledge,
interval,
)
}
fn compare_decimals(a: Decimal, b: Decimal) {
let epsilon = Decimal::from_ratio(1u128, 100_000_000u128);
if a > b {
assert!(a - b < epsilon, "{} != {}", a, b)
} else {
assert!(b - a < epsilon, "{} != {}", a, b)
}
}
// essentially our delegations + estimated rewards HAVE TO equal to what we actually determined
fn check_rewarding_invariant(simulator: &Simulator) {
let delegation_sum: Decimal = simulator
.node_delegations
.iter()
.map(|d| d.dec_amount())
.sum();
let reward_sum = simulator.determine_total_delegation_reward();
compare_decimals(
delegation_sum + reward_sum,
simulator.node_rewarding_details.delegates,
)
}
#[test]
fn simulator_returns_expected_values_for_base_case() {
let mut simulator = base_simulator(10000_000000);
let epoch_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards = simulator.simulate_epoch(epoch_params);
assert_eq!(rewards.delegates, Decimal::zero());
compare_decimals(rewards.operator, "1128452.5416104363".parse().unwrap());
}
#[test]
fn single_delegation_at_genesis() {
let mut simulator = base_simulator(10000_000000);
simulator.delegate(Coin::new(18000_000000, "unym"));
let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards = simulator.simulate_epoch(node_params);
compare_decimals(rewards.delegates, "1795950.2602660495".parse().unwrap());
compare_decimals(rewards.operator, "1363716.856243172".parse().unwrap());
compare_decimals(
rewards.delegates,
simulator.determine_total_delegation_reward(),
);
assert_eq!(
simulator.node_rewarding_details.operator,
rewards.operator + Decimal::from_atomics(10000_000000u128, 0).unwrap()
);
assert_eq!(
simulator.node_rewarding_details.delegates,
rewards.delegates + Decimal::from_atomics(18000_000000u128, 0).unwrap()
);
}
#[test]
fn delegation_and_undelegation() {
let mut simulator = base_simulator(10000_000000);
let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards1 = simulator.simulate_epoch(node_params);
let expected_operator1 = "1128452.5416104363".parse().unwrap();
assert_eq!(rewards1.delegates, Decimal::zero());
compare_decimals(rewards1.operator, expected_operator1);
simulator.delegate(Coin::new(18000_000000, "unym"));
let rewards2 = simulator.simulate_epoch(node_params);
let expected_operator2 = "1363843.413584609".parse().unwrap();
let expected_delegator_reward1 = "1795952.25874404".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward1);
compare_decimals(rewards2.operator, expected_operator2);
let rewards3 = simulator.simulate_epoch(node_params);
let expected_operator3 = "1364017.7824440491".parse().unwrap();
let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap();
compare_decimals(rewards3.delegates, expected_delegator_reward2);
compare_decimals(rewards3.operator, expected_operator3);
let (delegation, reward) = simulator.undelegate(0).unwrap();
assert_eq!(delegation.amount.u128(), 18000_000000);
assert_eq!(
reward.amount,
(expected_delegator_reward1 + expected_delegator_reward2) * Uint128::new(1)
);
let base_op = Decimal::from_atomics(10000_000000u128, 0).unwrap();
compare_decimals(
simulator.node_rewarding_details.operator,
base_op + expected_operator1 + expected_operator2 + expected_operator3,
);
assert_eq!(Decimal::zero(), simulator.node_rewarding_details.delegates);
}
#[test]
fn withdrawing_operator_reward() {
// essentially all delegators' rewards (and the operator itself) are still correctly computed
let original_pledge = coin(10000_000000, "unym");
let mut simulator = base_simulator(original_pledge.amount.u128());
let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator.delegate(Coin::new(18000_000000, "unym"));
simulator.delegate(Coin::new(4000_000000, "unym"));
// "normal", sanity check rewarding
let rewards1 = simulator.simulate_epoch(node_params);
let expected_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1);
compare_decimals(rewards1.operator, expected_operator1);
check_rewarding_invariant(&simulator);
let reward = simulator
.node_rewarding_details
.withdraw_operator_reward(&original_pledge);
assert_eq!(reward.amount, truncate_reward_amount(expected_operator1));
assert_eq!(
simulator.node_rewarding_details.operator,
Decimal::from_atomics(original_pledge.amount, 0).unwrap()
);
let rewards2 = simulator.simulate_epoch(node_params);
let expected_operator2 = "1411113.0004067947".parse().unwrap();
let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2);
compare_decimals(rewards2.operator, expected_operator2);
check_rewarding_invariant(&simulator);
}
#[test]
fn withdrawing_delegator_reward() {
// essentially all delegators' rewards (and the operator itself) are still correctly computed
let mut simulator = base_simulator(10000_000000);
let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator.delegate(Coin::new(18000_000000, "unym"));
simulator.delegate(Coin::new(4000_000000, "unym"));
// "normal", sanity check rewarding
let rewards1 = simulator.simulate_epoch(node_params);
let expected_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1);
compare_decimals(rewards1.operator, expected_operator1);
check_rewarding_invariant(&simulator);
// reference to our `18000_000000` delegation
let delegation1 = &mut simulator.node_delegations[0];
let reward = simulator
.node_rewarding_details
.withdraw_delegator_reward(delegation1)
.unwrap();
let expected_del1_reward = "1799968.1174089068".parse().unwrap();
assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward));
// new reward after withdrawal
let rewards2 = simulator.simulate_epoch(node_params);
let expected_operator2 = "1411250.1907492676".parse().unwrap();
let expected_delegator_reward2 = "2200004.051009689".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2);
compare_decimals(rewards2.operator, expected_operator2);
check_rewarding_invariant(&simulator);
// check final values
let reward_del1 = simulator
.node_rewarding_details
.withdraw_delegator_reward(&mut simulator.node_delegations[0])
.unwrap();
let expected_del1_reward = "1799970.5883041779".parse().unwrap();
assert_eq!(
reward_del1.amount,
truncate_reward_amount(expected_del1_reward)
);
let reward_del2 = simulator
.node_rewarding_details
.withdraw_delegator_reward(&mut simulator.node_delegations[1])
.unwrap();
let first: Decimal = "399992.91497975704".parse().unwrap();
let second: Decimal = "400033.4627055114".parse().unwrap();
let expected_del2_reward = first + second;
assert_eq!(
reward_del2.amount,
truncate_reward_amount(expected_del2_reward)
);
}
#[test]
fn simulating_multiple_epochs() {
let mut simulator = base_simulator(10000_000000);
let mut is_active = true;
let mut performance = Percent::from_percentage_value(100).unwrap();
for epoch in 0..720 {
if epoch == 0 {
simulator.delegate(Coin::new(18000_000000, "unym"))
}
if epoch == 42 {
simulator.delegate(Coin::new(2000_000000, "unym"))
}
if epoch == 89 {
is_active = false;
}
if epoch == 123 {
simulator.delegate(Coin::new(6666_000000, "unym"))
}
if epoch == 167 {
performance = Percent::from_percentage_value(90).unwrap();
}
if epoch == 245 {
simulator.delegate(Coin::new(2050_000000, "unym"))
}
if epoch == 264 {
let (delegation, _reward) = simulator.undelegate(1).unwrap();
// sanity check to make sure we undelegated what we wanted to undelegate : )
assert_eq!(delegation.amount.u128(), 2000_000000);
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
}
if epoch == 345 {
is_active = true;
}
if epoch == 358 {
performance = Percent::from_percentage_value(100).unwrap();
}
if epoch == 458 {
let (delegation, _reward) = simulator.undelegate(0).unwrap();
// sanity check to make sure we undelegated what we wanted to undelegate : )
assert_eq!(delegation.amount.u128(), 18000_000000);
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
}
if epoch == 545 {
simulator.delegate(Coin::new(5000_000000, "unym"))
}
// this has to always hold
check_rewarding_invariant(&simulator);
let node_params = NodeRewardParams::new(performance, is_active);
simulator.simulate_epoch(node_params);
}
// after everyone undelegates, there should be nothing left in the delegates pool
let delegations = simulator.node_delegations.len();
for _ in 0..delegations {
simulator.undelegate(0).unwrap();
}
assert_eq!(Decimal::zero(), simulator.node_rewarding_details.delegates);
}
}
@@ -1,16 +1,111 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::DelegatorRewardParams;
use crate::error::MixnetContractError;
use crate::rewarding::helpers::truncate_decimal;
use crate::{Layer, RewardedSetNodeStatus};
use cosmwasm_std::{Addr, Uint128};
use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::{self, Display, Formatter};
use std::ops::{Index, Mul};
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
pub type SphinxKeyRef<'a> = &'a str;
pub type EpochId = u32;
pub type IntervalId = u32;
pub type NodeId = u32;
pub type EpochEventId = u32;
pub type IntervalEventId = u32;
/// Percent represents a value between 0 and 100%
/// (i.e. between 0.0 and 1.0)
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Serialize, Deserialize, JsonSchema,
)]
pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
impl Percent {
pub fn new(value: Decimal) -> Result<Self, MixnetContractError> {
if value > Decimal::one() {
Err(MixnetContractError::InvalidPercent)
} else {
Ok(Percent(value))
}
}
pub fn is_zero(&self) -> bool {
self.0 == Decimal::zero()
}
pub fn from_percentage_value(value: u64) -> Result<Self, MixnetContractError> {
Percent::new(Decimal::percent(value))
}
pub fn value(&self) -> Decimal {
self.0
}
pub fn round_to_integer(&self) -> u8 {
let hundred = Decimal::from_ratio(100u32, 1u32);
// we know the cast from u128 to u8 is a safe one since the internal value must be within 0 - 1 range
truncate_decimal(hundred * self.0).u128() as u8
}
}
impl Display for Percent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0;
write!(f, "{}%", adjusted)
}
}
impl Mul<Decimal> for Percent {
type Output = Decimal;
fn mul(self, rhs: Decimal) -> Self::Output {
self.0 * rhs
}
}
impl Mul<Percent> for Decimal {
type Output = Decimal;
fn mul(self, rhs: Percent) -> Self::Output {
rhs * self
}
}
impl Mul<Uint128> for Percent {
type Output = Uint128;
fn mul(self, rhs: Uint128) -> Self::Output {
self.0 * rhs
}
}
// implement custom Deserialize because we want to validate Percent has the correct range
fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
let v = Decimal::deserialize(deserializer)?;
if v > Decimal::one() {
Err(D::Error::custom(
"provided decimal percent is larger than 100%",
))
} else {
Ok(v)
}
}
#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)]
pub struct LayerDistribution {
pub gateways: u64,
pub layer1: u64,
pub layer2: u64,
pub layer3: u64,
@@ -25,121 +120,133 @@ impl LayerDistribution {
];
layers.iter().min_by_key(|x| x.1).unwrap().0
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ContractStateParams {
// so currently interval_length is being unused and validator API performs rewarding
// based on its own interval length config value. I guess that's fine for time being
// however, in the future, the contract constant should be controlling it instead.
// pub interval_length: u32, // length of a rewarding interval/interval, expressed in hours
pub minimum_mixnode_pledge: Uint128, // minimum amount a mixnode must pledge to get into the system
pub minimum_gateway_pledge: Uint128, // minimum amount a gateway must pledge to get into the system
pub fn increment_layer_count(&mut self, layer: Layer) {
match layer {
Layer::One => self.layer1 += 1,
Layer::Two => self.layer2 += 1,
Layer::Three => self.layer3 += 1,
}
}
// number of mixnode that are going to get rewarded during current rewarding interval (k_m)
// based on overall demand for private bandwidth-
pub mixnode_rewarded_set_size: u32,
pub fn decrement_layer_count(&mut self, layer: Layer) -> Result<(), MixnetContractError> {
match layer {
Layer::One => {
self.layer1 =
self.layer1
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer1,
subtrahend: 1,
})?
}
Layer::Two => {
self.layer2 =
self.layer2
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer2,
subtrahend: 1,
})?
}
Layer::Three => {
self.layer3 =
self.layer3
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer3,
subtrahend: 1,
})?
}
}
// subset of rewarded mixnodes that are actively receiving mix traffic
// used to handle shorter-term (e.g. hourly) fluctuations of demand
pub mixnode_active_set_size: u32,
pub staking_supply: Uint128,
}
impl Display for ContractStateParams {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Contract state parameters: [ ")?;
write!(
f,
"minimum mixnode pledge: {}; ",
self.minimum_mixnode_pledge
)?;
write!(
f,
"minimum gateway pledge: {}; ",
self.minimum_gateway_pledge
)?;
write!(
f,
"mixnode rewarded set size: {}",
self.mixnode_rewarded_set_size
)?;
write!(
f,
"mixnode active set size: {}",
self.mixnode_active_set_size
)
Ok(())
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct RewardingResult {
pub node_reward: Uint128,
impl Index<Layer> for LayerDistribution {
type Output = u64;
fn index(&self, index: Layer) -> &Self::Output {
match index {
Layer::One => &self.layer1,
Layer::Two => &self.layer2,
Layer::Three => &self.layer3,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PendingDelegatorRewarding {
// keep track of the running rewarding results so we'd known how much was the operator and its delegators rewarded
pub running_results: RewardingResult,
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ContractState {
pub owner: Addr, // only the owner account can update state
pub rewarding_validator_address: Addr,
pub next_start: Addr,
pub rewarding_params: DelegatorRewardParams,
/// Address of the vesting contract to which the mixnet contract would be sending all
/// track-related messages.
pub vesting_contract_address: Addr,
pub rewarding_denom: String,
pub params: ContractStateParams,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum RewardingStatus {
Complete(RewardingResult),
PendingNextDelegatorPage(PendingDelegatorRewarding),
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ContractStateParams {
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
pub minimum_mixnode_delegation: Option<Coin>,
/// Minimum amount a mixnode must pledge to get into the system.
pub minimum_mixnode_pledge: Coin,
/// Minimum amount a gateway must pledge to get into the system.
pub minimum_gateway_pledge: Coin,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MixnodeRewardingStatusResponse {
pub status: Option<RewardingStatus>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MixnetContractVersion {
// VERGEN_BUILD_TIMESTAMP
pub build_timestamp: String,
// VERGEN_BUILD_SEMVER
pub build_version: String,
// VERGEN_GIT_SHA
pub commit_sha: String,
// VERGEN_GIT_COMMIT_TIMESTAMP
pub commit_timestamp: String,
// VERGEN_GIT_BRANCH
pub commit_branch: String,
// VERGEN_RUSTC_SEMVER
pub rustc_version: String,
}
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct PagedRewardedSetResponse {
pub identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
pub start_next_after: Option<IdentityKey>,
pub at_height: u64,
pub nodes: Vec<(NodeId, RewardedSetNodeStatus)>,
pub start_next_after: Option<NodeId>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct RewardedSetUpdateDetails {
pub refresh_rate_blocks: u64,
pub last_refreshed_block: u64,
pub current_height: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct IntervalRewardedSetHeightsResponse {
pub interval_id: u32,
pub heights: Vec<u64>,
#[test]
fn percent_serde() {
let valid_value = Percent::from_percentage_value(80).unwrap();
let serialized = serde_json::to_string(&valid_value).unwrap();
println!("{}", serialized);
let deserialized: Percent = serde_json::from_str(&serialized).unwrap();
assert_eq!(valid_value, deserialized);
let invalid_values = vec!["\"42\"", "\"1.1\"", "\"1.00000001\"", "\"foomp\"", "\"1a\""];
for invalid_value in invalid_values {
assert!(serde_json::from_str::<'_, Percent>(invalid_value).is_err())
}
assert_eq!(
serde_json::from_str::<'_, Percent>("\"0.95\"").unwrap(),
Percent::from_percentage_value(95).unwrap()
)
}
#[test]
fn percent_to_absolute_integer() {
let p = serde_json::from_str::<'_, Percent>("\"0.0001\"").unwrap();
assert_eq!(p.round_to_integer(), 0);
let p = serde_json::from_str::<'_, Percent>("\"0.0099\"").unwrap();
assert_eq!(p.round_to_integer(), 0);
let p = serde_json::from_str::<'_, Percent>("\"0.0199\"").unwrap();
assert_eq!(p.round_to_integer(), 1);
let p = serde_json::from_str::<'_, Percent>("\"0.45123\"").unwrap();
assert_eq!(p.round_to_integer(), 45);
let p = serde_json::from_str::<'_, Percent>("\"0.999999999\"").unwrap();
assert_eq!(p.round_to_integer(), 99);
let p = serde_json::from_str::<'_, Percent>("\"1.00\"").unwrap();
assert_eq!(p.round_to_integer(), 100);
}
}
@@ -16,6 +16,8 @@ pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbondin
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
"vesting_update_mixnode_cost_params";
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
@@ -114,6 +116,10 @@ pub fn new_vesting_update_mixnode_config_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
}
pub fn new_vesting_update_mixnode_cost_params_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE)
}
pub fn new_vesting_mixnode_unbonding_event() -> Event {
Event::new(VESTING_MIXNODE_UNBONDING_EVENT_TYPE)
}
@@ -1,19 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
use mixnet_contract_common::NodeId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
use mixnet_contract_common::IdentityKey;
pub mod events;
pub mod messages;
pub fn one_ucoin(denom: String) -> Coin {
Coin::new(1, denom)
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
@@ -78,18 +74,14 @@ impl OriginalVestingResponse {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)]
pub struct VestingDelegation {
pub account_id: u32,
pub mix_identity: IdentityKey,
pub mix_id: NodeId,
pub block_timestamp: u64,
pub amount: Uint128,
}
impl VestingDelegation {
pub fn storage_key(&self) -> (u32, IdentityKey, u64) {
(
self.account_id,
self.mix_identity.clone(),
self.block_timestamp,
)
pub fn storage_key(&self) -> (u32, NodeId, u64) {
(self.account_id, self.mix_id, self.block_timestamp)
}
}
@@ -97,12 +89,12 @@ impl VestingDelegation {
pub struct DelegationTimesResponse {
pub owner: Addr,
pub account_id: u32,
pub mix_identity: IdentityKey,
pub mix_id: NodeId,
pub delegation_timestamps: Vec<u64>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)]
pub struct AllDelegationsResponse {
pub delegations: Vec<VestingDelegation>,
pub start_next_after: Option<(u32, IdentityKey, u64)>,
pub start_next_after: Option<(u32, NodeId, u64)>,
}
@@ -1,5 +1,8 @@
use cosmwasm_std::{Coin, Timestamp, Uint128};
use mixnet_contract_common::{Gateway, IdentityKey, MixNode};
use mixnet_contract_common::{
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
Gateway, MixNode, NodeId,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -12,7 +15,9 @@ pub struct InitMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
pub struct MigrateMsg {
pub v2_mixnet_contract_address: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
pub struct VestingSpecification {
@@ -56,24 +61,23 @@ pub enum ExecuteMsg {
},
ClaimOperatorReward {},
ClaimDelegatorReward {
mix_identity: String,
mix_id: NodeId,
},
CompoundDelegatorReward {
mix_identity: String,
UpdateMixnodeCostParams {
new_costs: MixNodeCostParams,
},
CompoundOperatorReward {},
UpdateMixnodeConfig {
profit_margin_percent: u8,
new_config: MixNodeConfigUpdate,
},
UpdateMixnetAddress {
address: String,
},
DelegateToMixnode {
mix_identity: IdentityKey,
mix_id: NodeId,
amount: Coin,
},
UndelegateFromMixnode {
mix_identity: IdentityKey,
mix_id: NodeId,
},
CreateAccount {
owner_address: String,
@@ -85,11 +89,12 @@ pub enum ExecuteMsg {
},
TrackUndelegation {
owner: String,
mix_identity: IdentityKey,
mix_id: NodeId,
amount: Coin,
},
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
amount: Coin,
},
@@ -119,6 +124,35 @@ pub enum ExecuteMsg {
},
}
impl ExecuteMsg {
pub fn name(&self) -> &str {
match self {
ExecuteMsg::TrackReward { .. } => "VestingExecuteMsg::TrackReward",
ExecuteMsg::ClaimOperatorReward { .. } => "VestingExecuteMsg::ClaimOperatorReward",
ExecuteMsg::ClaimDelegatorReward { .. } => "VestingExecuteMsg::ClaimDelegatorReward",
ExecuteMsg::UpdateMixnodeConfig { .. } => "VestingExecuteMsg::UpdateMixnodeConfig",
ExecuteMsg::UpdateMixnodeCostParams { .. } => {
"VestingExecuteMsg::UpdateMixnodeCostParams"
}
ExecuteMsg::UpdateMixnetAddress { .. } => "VestingExecuteMsg::UpdateMixnetAddress",
ExecuteMsg::DelegateToMixnode { .. } => "VestingExecuteMsg::DelegateToMixnode",
ExecuteMsg::UndelegateFromMixnode { .. } => "VestingExecuteMsg::UndelegateFromMixnode",
ExecuteMsg::CreateAccount { .. } => "VestingExecuteMsg::CreateAccount",
ExecuteMsg::WithdrawVestedCoins { .. } => "VestingExecuteMsg::WithdrawVestedCoins",
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
@@ -170,10 +204,10 @@ pub enum QueryMsg {
GetLockedPledgeCap {},
GetDelegationTimes {
address: String,
mix_identity: IdentityKey,
mix_id: NodeId,
},
GetAllDelegations {
start_after: Option<(u32, IdentityKey, u64)>,
start_after: Option<(u32, NodeId, u64)>,
limit: Option<u32>,
},
}
+1 -1
View File
@@ -11,7 +11,7 @@ async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{}/coconut-credential-example.sqlite", out_dir);
let mut conn = SqliteConnection::connect(&*format!("sqlite://{}?mode=rwc", database_path))
let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path))
.await
.expect("Failed to create SQLx database connection");
+1 -1
View File
@@ -144,7 +144,7 @@ impl Tau {
let tau_mem = self.0.as_raw_slice();
assert_eq!(tau_mem.len(), 1, "tau length invariant was broken");
random_oracle_builder.update(&tau_mem[0].to_be_bytes());
random_oracle_builder.update(tau_mem[0].to_be_bytes());
let oracle_output = random_oracle_builder.finalize();
debug_assert_eq!(oracle_output.len() * 8, HASH_SECURITY_PARAM);
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "ledger"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip32 = "0.3.0"
k256 = "0.10.4"
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use bip32::{PublicKey, PublicKeyBytes};
use ledger_transport::APDUAnswer;
/// SECP265K1 address of the device.
pub struct AddrSecp265k1Response {
/// SECP265K1 public key.
pub public_key: k256::PublicKey,
/// String representation of the Cosmos address.
pub address: String,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for AddrSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() < 33 {
return Err(Self::Error::InvalidAnswerLength {
expected: 33,
received: bytes.len(),
});
}
let (pub_key, addr) = bytes.split_at(33);
let public_key = k256::PublicKey::from_bytes(
PublicKeyBytes::try_from(pub_key).expect("Public key should be 33 bytes"),
)?;
let address = String::from_utf8(addr.to_vec()).unwrap();
Ok(AddrSecp265k1Response {
public_key,
address,
})
}
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
pub(crate) type Result<T> = std::result::Result<T, LedgerError>;
/// Ledger specific errors.
#[derive(Debug, Error)]
pub enum LedgerError {
#[error("HID API - {0}")]
HidAPI(#[from] ledger_transport_hid::hidapi::HidError),
#[error("HID transport - {0}")]
HidTransport(#[from] ledger_transport_hid::LedgerHIDError),
#[error("Unknown error code - {err_code}")]
UnknownErrorCode { err_code: u16 },
#[error("APDU error - {reason}")]
APDU { reason: String },
#[error("Not enough bytes in answer. Expected at least {expected}, received {received}")]
InvalidAnswerLength { expected: usize, received: usize },
#[error("Not enough components in derivation path. Expected {expected}, received {received}")]
InvalidDerivationPath { expected: usize, received: usize },
#[error("Bip32 - {0}")]
Bip32(#[from] bip32::Error),
#[error("Signature error - {0}")]
Signature(#[from] k256::ecdsa::Error),
#[error("No message found for signing transaction")]
NoMessageFound,
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::{LedgerError, Result};
use bip32::DerivationPath;
use ledger_transport::{APDUAnswer, APDUErrorCode};
pub(crate) fn answer_bytes(answer: &APDUAnswer<Vec<u8>>) -> Result<&[u8]> {
let error_code = answer
.error_code()
.map_err(|err_code| LedgerError::UnknownErrorCode { err_code })?;
match error_code {
APDUErrorCode::NoError => Ok(answer.data()),
e => Err(LedgerError::APDU {
reason: e.description(),
}),
}
}
pub(crate) fn path_bytes(path: DerivationPath) -> Result<[[u8; 4]; 5]> {
let received = path.len();
let components: Vec<[u8; 4]> = path.into_iter().map(|c| c.0.to_le_bytes()).collect();
if components.len() != 5 {
Err(LedgerError::InvalidDerivationPath {
expected: 5,
received,
})
} else {
Ok([
components[0],
components[1],
components[2],
components[3],
components[4],
])
}
}
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod addr_secp265k1;
pub mod error;
pub(crate) mod helpers;
mod sign_secp265k1;
pub mod version;
use crate::addr_secp265k1::AddrSecp265k1Response;
use crate::error::LedgerError;
use crate::helpers::path_bytes;
use crate::sign_secp265k1::SignSecp265k1Response;
use crate::version::VersionResponse;
use bip32::DerivationPath;
use error::Result;
use ledger_transport::APDUCommand;
use ledger_transport_hid::hidapi::HidApi;
use ledger_transport_hid::TransportNativeHID;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::sync::Arc;
const CLA: u8 = 0x55;
const INS_GET_VERSION: u8 = 0x00;
const INS_SIGN_SECP256K1: u8 = 0x02;
const INS_GET_ADDR_SECP256K1: u8 = 0x04;
const PAYLOAD_TYPE_INIT: u8 = 0x00;
const PAYLOAD_TYPE_ADD: u8 = 0x01;
const PAYLOAD_TYPE_LAST: u8 = 0x02;
const CHUNK_SIZE: usize = 250;
/// Manage hardware Ledger device with Cosmos specific operations, as described in the
/// specification: https://github.com/cosmos/ledger-cosmos/blob/main/docs/APDUSPEC.md
#[derive(Clone)]
pub struct CosmosLedger {
path: DerivationPath,
prefix: String,
transport: Arc<TransportNativeHID>,
}
impl Debug for CosmosLedger {
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "()")
}
}
impl CosmosLedger {
/// Create the connection to the first Ledger device that we can find.
pub fn new(path: DerivationPath, prefix: String) -> Result<Self> {
let api = HidApi::new()?;
let transport = Arc::new(TransportNativeHID::new(&api)?);
Ok(CosmosLedger {
path,
prefix,
transport,
})
}
/// Get the version of the device.
pub fn get_version(&self) -> Result<VersionResponse> {
let command = APDUCommand {
cla: CLA,
ins: INS_GET_VERSION,
p1: 0,
p2: 0,
data: vec![],
};
let response = self.transport.exchange(&command)?;
VersionResponse::try_from(response)
}
/// Get the SECP265K1 address of the device.
pub fn get_addr_secp265k1(&self, display: bool) -> Result<AddrSecp265k1Response> {
let display = u8::from(display);
let components = path_bytes(self.path.clone())?;
let data: Vec<u8> = vec![
[self.prefix.len() as u8].as_slice(),
self.prefix.as_bytes(),
components[0].as_slice(),
components[1].as_slice(),
components[2].as_slice(),
components[3].as_slice(),
components[4].as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();
let command = APDUCommand {
cla: CLA,
ins: INS_GET_ADDR_SECP256K1,
p1: display,
p2: 0,
data,
};
let response = self.transport.exchange(&command)?;
AddrSecp265k1Response::try_from(response)
}
pub fn sign_secp265k1(&self, message: String) -> Result<SignSecp265k1Response> {
let serialized_path: Vec<u8> = path_bytes(self.path.clone())?
.into_iter()
.flatten()
.collect();
let mut chunks = vec![serialized_path];
if message.is_empty() {
return Err(LedgerError::NoMessageFound);
}
for chunk in message.into_bytes().chunks(CHUNK_SIZE) {
chunks.push(chunk.to_vec());
}
let length = chunks.len();
for (idx, chunk) in chunks.into_iter().enumerate() {
let payload_desc = if idx == 0 {
PAYLOAD_TYPE_INIT
} else if idx + 1 == length {
PAYLOAD_TYPE_LAST
} else {
PAYLOAD_TYPE_ADD
};
let command = APDUCommand {
cla: CLA,
ins: INS_SIGN_SECP256K1,
p1: payload_desc,
p2: 0,
data: chunk,
};
let sign_response = self.transport.exchange(&command)?;
if payload_desc == PAYLOAD_TYPE_LAST {
return SignSecp265k1Response::try_from(sign_response);
}
}
// It should never reach this, as the message is not empty
Err(LedgerError::NoMessageFound)
}
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use k256::ecdsa::Signature;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct SignSecp265k1Response {
/// DER encoded signature data
pub signature: Signature,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for SignSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
Ok(SignSecp265k1Response {
signature: Signature::from_der(bytes)?,
})
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct VersionResponse {
/// Activation status of test mode.
pub test_mode: bool,
/// Major part of Cosmos application version.
pub major: u8,
/// Minor part of Cosmos application version.
pub minor: u8,
/// Patch part of Cosmos application version.
pub patch: u8,
/// PIN locked status.
pub device_locked: bool,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for VersionResponse {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() != 5 {
return Err(Self::Error::InvalidAnswerLength {
expected: 5,
received: bytes.len(),
});
}
Ok(VersionResponse {
test_mode: bytes[0] != 0,
major: bytes[1],
minor: bytes[2],
patch: bytes[3],
device_locked: bytes[4] != 0,
})
}
}
+4 -3
View File
@@ -321,17 +321,18 @@ impl VerlocMeasurer {
let tested_nodes = all_mixes
.into_iter()
.filter_map(|node| {
let mix_node = node.bond_information.mix_node;
// check if the node has sufficient version to be able to understand the packets
let node_version = parse_version(&node.mix_node.version).ok()?;
let node_version = parse_version(&mix_node.version).ok()?;
if node_version < self.config.minimum_compatible_node_version {
return None;
}
// try to parse the identity and host
let node_identity =
identity::PublicKey::from_base58_string(node.mix_node.identity_key).ok()?;
identity::PublicKey::from_base58_string(mix_node.identity_key).ok()?;
let verloc_host = (&*node.mix_node.host, node.mix_node.verloc_port)
let verloc_host = (&*mix_node.host, mix_node.verloc_port)
.to_socket_addrs()
.ok()?
.next()?;
+1 -1
View File
@@ -9,7 +9,7 @@ MIX_DENOM_DISPLAY=nym
STAKE_DENOM=unyx
STAKE_DENOM_DISPLAY=nyx
DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep
MIXNET_CONTRACT_ADDRESS=n1rjzps6qrmdqmf0xz4cn4x4rcmqeqzq6hnzqg4wcvd0r2lyasdq5sepn5s8
VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw
-251
View File
@@ -1,251 +0,0 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
DefaultNetworkDetails, DenomDetailsOwned, NymNetworkDetails, ValidatorDetails,
MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS,
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt, str::FromStr};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NetworkDefaultsError {
#[error("The provided network was invalid")]
MalformedNetworkProvided(String),
}
// the reason for allowing it is that this is just a temporary solution
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum Network {
QA,
SANDBOX,
MAINNET,
CUSTOM { details: NymNetworkDetails },
}
impl Network {
pub fn new_custom(details: NymNetworkDetails) -> Self {
Network::CUSTOM { details }
}
pub fn details(&self) -> NymNetworkDetails {
match self {
Self::QA => (&*QA_DEFAULTS).into(),
Self::SANDBOX => (&*SANDBOX_DEFAULTS).into(),
Self::MAINNET => (&*MAINNET_DEFAULTS).into(),
// I dislike the clone here, but for compatibility reasons we cannot define other networks with `NymNetworkDetails` directly yet
Self::CUSTOM { details } => details.clone(),
}
}
pub fn bech32_prefix(&self) -> String {
self.details().chain_details.bech32_account_prefix
}
pub fn mix_denom(&self) -> DenomDetailsOwned {
self.details().chain_details.mix_denom
}
pub fn stake_denom(&self) -> DenomDetailsOwned {
self.details().chain_details.stake_denom
}
pub fn base_mix_denom(&self) -> String {
self.details().chain_details.mix_denom.base
}
pub fn base_stake_denom(&self) -> String {
self.details().chain_details.stake_denom.base
}
pub fn mixnet_contract_address(&self) -> Option<String> {
self.details().contracts.mixnet_contract_address
}
pub fn vesting_contract_address(&self) -> Option<String> {
self.details().contracts.vesting_contract_address
}
pub fn bandwidth_claim_contract_address(&self) -> Option<String> {
self.details().contracts.bandwidth_claim_contract_address
}
pub fn coconut_bandwidth_contract_address(&self) -> Option<String> {
self.details().contracts.coconut_bandwidth_contract_address
}
pub fn multisig_contract_address(&self) -> Option<String> {
self.details().contracts.multisig_contract_address
}
pub fn validators(&self) -> Vec<ValidatorDetails> {
self.details().endpoints
}
// only used in mixnet contract tests, but I don't want to be messing with that code now
pub fn rewarding_validator_address(&self) -> &str {
match self {
Network::QA => crate::qa::REWARDING_VALIDATOR_ADDRESS,
Network::SANDBOX => crate::sandbox::REWARDING_VALIDATOR_ADDRESS,
Network::MAINNET => crate::mainnet::REWARDING_VALIDATOR_ADDRESS,
Network::CUSTOM { .. } => {
panic!("rewarding validator address is unavailable for a custom network")
}
}
}
// this should be handled differently, but I don't want to break compatibility
pub fn statistics_service_url(&self) -> &str {
match self {
Network::MAINNET => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::SANDBOX => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::QA => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::CUSTOM { .. } => {
panic!("statistics service url is unavailable for a custom network")
}
}
}
}
impl FromStr for Network {
type Err = NetworkDefaultsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"qa" => Ok(Network::QA),
"sandbox" => Ok(Network::SANDBOX),
"mainnet" => Ok(Network::MAINNET),
_ => Err(NetworkDefaultsError::MalformedNetworkProvided(
s.to_string(),
)),
}
}
}
impl fmt::Display for Network {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Network::QA => f.write_str("QA"),
Network::SANDBOX => f.write_str("Sandbox"),
Network::MAINNET => f.write_str("Mainnet"),
Network::CUSTOM { .. } => f.write_str("Custom"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct NetworkDetails {
bech32_prefix: String,
mix_denom: DenomDetailsOwned,
stake_denom: DenomDetailsOwned,
mixnet_contract_address: String,
vesting_contract_address: String,
bandwidth_claim_contract_address: String,
statistics_service_url: String,
validators: Vec<ValidatorDetails>,
}
impl From<&DefaultNetworkDetails> for NetworkDetails {
fn from(details: &DefaultNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.bech32_prefix.into(),
mix_denom: details.mix_denom.into(),
stake_denom: details.stake_denom.into(),
mixnet_contract_address: details.mixnet_contract_address.into(),
vesting_contract_address: details.vesting_contract_address.into(),
bandwidth_claim_contract_address: details.bandwidth_claim_contract_address.into(),
statistics_service_url: details.statistics_service_url.into(),
validators: details.validators.clone(),
}
}
}
// this also has to exist for compatibility reasons since I don't want to be touching the wallet now
impl From<NymNetworkDetails> for NetworkDetails {
fn from(details: NymNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.chain_details.bech32_account_prefix,
mix_denom: details.chain_details.mix_denom,
stake_denom: details.chain_details.stake_denom,
mixnet_contract_address: details
.contracts
.mixnet_contract_address
.unwrap_or_default(),
vesting_contract_address: details
.contracts
.vesting_contract_address
.unwrap_or_default(),
bandwidth_claim_contract_address: details
.contracts
.bandwidth_claim_contract_address
.unwrap_or_default(),
statistics_service_url: "".to_string(),
validators: details.endpoints,
}
}
}
impl NetworkDetails {
pub fn base_mix_denom(&self) -> &str {
&self.mix_denom.base
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct SupportedNetworks {
networks: HashMap<Network, NetworkDetails>,
}
impl SupportedNetworks {
pub fn new(support: Vec<Network>) -> Self {
SupportedNetworks {
networks: support
.into_iter()
.map(|n| {
let details = n.details().into();
(n, details)
})
.collect(),
}
}
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.bech32_prefix.as_str())
}
pub fn base_mix_denom(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.base_mix_denom())
}
pub fn mixnet_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.mixnet_contract_address.as_str())
}
pub fn vesting_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.vesting_contract_address.as_str())
}
pub fn bandwidth_claim_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.bandwidth_claim_contract_address.as_str())
}
pub fn validators(&self, network: Network) -> impl Iterator<Item = &ValidatorDetails> {
self.networks
.get(&network)
.map(|network_details| &network_details.validators)
.into_iter()
.flatten()
}
}
+41 -100
View File
@@ -1,15 +1,11 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{env::var, path::PathBuf};
use std::{env::var, ops::Not, path::PathBuf};
use url::Url;
pub mod all;
pub mod mainnet;
pub mod qa;
pub mod sandbox;
pub mod var_names;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
@@ -103,159 +99,99 @@ impl NymNetworkDetails {
}
pub fn new_mainnet() -> Self {
(&*MAINNET_DEFAULTS).into()
fn parse_optional_str(raw: &str) -> Option<String> {
raw.is_empty().not().then(|| raw.into())
}
// Consider caching this process (lazy static)
NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
mix_denom: mainnet::MIX_DENOM.into(),
stake_denom: mainnet::STAKE_DENOM.into(),
},
endpoints: mainnet::validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(mainnet::VESTING_CONTRACT_ADDRESS),
bandwidth_claim_contract_address: parse_optional_str(
mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
),
coconut_bandwidth_contract_address: parse_optional_str(
mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS),
},
}
}
#[must_use]
pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.chain_details.bech32_account_prefix = prefix.into();
self
}
#[must_use]
pub fn with_mix_denom(mut self, mix_denom: DenomDetailsOwned) -> Self {
self.chain_details.mix_denom = mix_denom;
self
}
#[must_use]
pub fn with_stake_denom(mut self, stake_denom: DenomDetailsOwned) -> Self {
self.chain_details.stake_denom = stake_denom;
self
}
#[must_use]
pub fn with_base_mix_denom<S: Into<String>>(mut self, base_mix_denom: S) -> Self {
self.chain_details.mix_denom = DenomDetailsOwned::base_only(base_mix_denom.into());
self
}
#[must_use]
pub fn with_base_stake_denom<S: Into<String>>(mut self, base_stake_denom: S) -> Self {
self.chain_details.stake_denom = DenomDetailsOwned::base_only(base_stake_denom.into());
self
}
#[must_use]
pub fn with_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self {
self.endpoints.push(endpoint);
self
}
#[must_use]
pub fn with_mixnet_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.mixnet_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_vesting_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.vesting_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_bandwidth_claim_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.bandwidth_claim_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_coconut_bandwidth_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.coconut_bandwidth_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_multisig_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.multisig_contract_address = contract.map(Into::into);
self
}
}
// This conversion only exists for convenience reasons until
// we can completely phase out `DefaultNetworkDetails`
impl<'a> From<&'a DefaultNetworkDetails> for NymNetworkDetails {
fn from(details: &'a DefaultNetworkDetails) -> Self {
fn parse_optional_str(raw: &str) -> Option<String> {
if raw.is_empty() {
None
} else {
Some(raw.into())
}
}
NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: details.bech32_prefix.into(),
mix_denom: details.mix_denom.into(),
stake_denom: details.stake_denom.into(),
},
endpoints: details.validators.clone(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(details.mixnet_contract_address),
vesting_contract_address: parse_optional_str(details.vesting_contract_address),
bandwidth_claim_contract_address: parse_optional_str(
details.bandwidth_claim_contract_address,
),
coconut_bandwidth_contract_address: parse_optional_str(
details.coconut_bandwidth_contract_address,
),
multisig_contract_address: parse_optional_str(details.multisig_contract_address),
},
}
}
}
// Since these are lazily constructed, we can afford to switch some of them to stronger types in the
// future. If we do this, and also get rid of the references we could potentially unify with
// `NetworkDetails`.
pub struct DefaultNetworkDetails {
bech32_prefix: &'static str,
mix_denom: DenomDetails,
stake_denom: DenomDetails,
mixnet_contract_address: &'static str,
vesting_contract_address: &'static str,
bandwidth_claim_contract_address: &'static str,
coconut_bandwidth_contract_address: &'static str,
multisig_contract_address: &'static str,
#[allow(dead_code)]
rewarding_validator_address: &'static str,
statistics_service_url: &'static str,
validators: Vec<ValidatorDetails>,
}
static MAINNET_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: mainnet::BECH32_PREFIX,
mix_denom: mainnet::MIX_DENOM,
stake_denom: mainnet::STAKE_DENOM,
mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: mainnet::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: mainnet::validators(),
});
static SANDBOX_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: sandbox::BECH32_PREFIX,
mix_denom: sandbox::MIX_DENOM,
stake_denom: sandbox::STAKE_DENOM,
mixnet_contract_address: sandbox::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: sandbox::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: sandbox::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: sandbox::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: sandbox::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: sandbox::validators(),
});
static QA_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: qa::BECH32_PREFIX,
mix_denom: qa::MIX_DENOM,
stake_denom: qa::STAKE_DENOM,
mixnet_contract_address: qa::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: qa::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: qa::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: qa::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: qa::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: qa::validators(),
});
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct DenomDetails {
pub base: &'static str,
@@ -349,12 +285,17 @@ pub fn setup_env(config_env_file: Option<PathBuf>) {
.expect("Invalid path to environment configuration file");
} else {
// if nothing is set, the use mainnet defaults
// if the user has not set `CONFIGURED`, then even if they set any of the env variables,
// overwrite them
crate::mainnet::export_to_env();
}
}
Err(_) => crate::mainnet::export_to_env(),
_ => {}
}
// if we haven't explicitly defined any of the constants, fallback to defaults
crate::mainnet::export_to_env_if_not_set()
}
// Name of the event triggered by the eth contract. If the event name is changed,
+82 -17
View File
@@ -31,42 +31,107 @@ pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(NYMD_VALIDATOR, Some(API_VALIDATOR))]
}
const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT";
fn set_var_to_default(var: &str, value: &str) {
std::env::set_var(var, value);
std::env::set_var(format!("{}{}", var, DEFAULT_SUFFIX), "1")
}
fn set_var_conditionally_to_default(var: &str, value: &str) {
if std::env::var(var).is_err() {
set_var_to_default(var, value)
}
}
pub fn uses_default(var: &str) -> bool {
std::env::var(format!("{}{}", var, DEFAULT_SUFFIX)).is_ok()
}
pub fn read_var_if_not_default(var: &str) -> Option<String> {
if uses_default(var) {
None
} else {
std::env::var(var).ok()
}
}
pub fn export_to_env() {
std::env::set_var(var_names::CONFIGURED, "true");
std::env::set_var(var_names::BECH32_PREFIX, BECH32_PREFIX);
std::env::set_var(var_names::MIX_DENOM, MIX_DENOM.base);
std::env::set_var(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
std::env::set_var(var_names::STAKE_DENOM, STAKE_DENOM.base);
std::env::set_var(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
std::env::set_var(
set_var_to_default(var_names::CONFIGURED, "true");
set_var_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX);
set_var_to_default(var_names::MIX_DENOM, MIX_DENOM.base);
set_var_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
set_var_to_default(var_names::STAKE_DENOM, STAKE_DENOM.base);
set_var_to_default(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
set_var_to_default(
var_names::DENOMS_EXPONENT,
STAKE_DENOM.display_exponent.to_string(),
&STAKE_DENOM.display_exponent.to_string(),
);
std::env::set_var(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
std::env::set_var(
set_var_to_default(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
set_var_to_default(
var_names::VESTING_CONTRACT_ADDRESS,
VESTING_CONTRACT_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
STATISTICS_SERVICE_DOMAIN_ADDRESS,
);
std::env::set_var(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
std::env::set_var(var_names::API_VALIDATOR, API_VALIDATOR);
set_var_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
set_var_to_default(var_names::API_VALIDATOR, API_VALIDATOR);
}
pub fn export_to_env_if_not_set() {
set_var_conditionally_to_default(var_names::CONFIGURED, "true");
set_var_conditionally_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX);
set_var_conditionally_to_default(var_names::MIX_DENOM, MIX_DENOM.base);
set_var_conditionally_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
set_var_conditionally_to_default(var_names::STAKE_DENOM, STAKE_DENOM.base);
set_var_conditionally_to_default(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
set_var_conditionally_to_default(
var_names::DENOMS_EXPONENT,
&STAKE_DENOM.display_exponent.to_string(),
);
set_var_conditionally_to_default(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
set_var_conditionally_to_default(
var_names::VESTING_CONTRACT_ADDRESS,
VESTING_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
);
set_var_conditionally_to_default(
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
STATISTICS_SERVICE_DOMAIN_ADDRESS,
);
set_var_conditionally_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
set_var_conditionally_to_default(var_names::API_VALIDATOR, API_VALIDATOR);
}
-32
View File
@@ -1,32 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "n";
pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qa-validator.nymtech.net",
Some("https://qa-validator-api.nymtech.net/api"),
)]
}
-30
View File
@@ -1,30 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "nymt";
pub const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
Some("https://sandbox-validator.nymtech.net/api"),
)]
}
+3 -6
View File
@@ -203,9 +203,8 @@ mod message_receiver {
mixes.insert(
1,
vec![mix::Node {
mix_id: 123,
owner: "foomp1".to_string(),
stake: 123,
delegation: 456,
host: "10.20.30.40".parse().unwrap(),
mix_host: "10.20.30.40:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
@@ -224,9 +223,8 @@ mod message_receiver {
mixes.insert(
2,
vec![mix::Node {
mix_id: 234,
owner: "foomp2".to_string(),
stake: 123,
delegation: 456,
host: "11.21.31.41".parse().unwrap(),
mix_host: "11.21.31.41:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
@@ -245,9 +243,8 @@ mod message_receiver {
mixes.insert(
3,
vec![mix::Node {
mix_id: 456,
owner: "foomp3".to_string(),
stake: 123,
delegation: 456,
host: "12.22.32.42".parse().unwrap(),
mix_host: "12.22.32.42:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(

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