Compare commits

...

128 Commits

Author SHA1 Message Date
Mark Sinclair e084c59b97 Network Explorer: fix uptime history display to use new API response 2021-12-16 17:57:05 +00:00
Mark Sinclair d17b77ed0c Merge pull request #966 from nymtech/feature/nova-network-explorer
Fixes to Network Explorer for new testnet
2021-12-16 11:13:06 +00:00
Mark Sinclair 48838ade3d Network Explorer: change prod env to round robin DNS 2021-12-16 10:25:30 +00:00
Mark Sinclair b63c7bbaa8 Network Explorer improvements:
- fix up API urls after Network Explorer API changes
- set currency denominations in `.env` file
- set API endpoints in `.env` file
2021-12-15 19:32:07 +00:00
Mark Sinclair 7f53b63557 Network Explorer API improvements:
- upgrade `okapi` for swagger generation across multiple resources
- switched `GET mix-node` to `GET mix-nodes`
- added error message when no geolocation env var is set and process continues
2021-12-15 18:06:03 +00:00
Mark Sinclair dd54f831b1 Network Explorer: add prod config 2021-12-15 17:35:57 +00:00
Mark Sinclair eda3b3d3db Network Explorer: configure URLs with .env file 2021-12-15 17:35:56 +00:00
Bogdan-Ștefan Neacșu eeb9be999f Update contract addresses 2021-12-14 12:33:12 +02:00
Bogdan-Ștefan Neacșu 47946ad79e Do not set proxy only for this time 2021-12-14 11:36:23 +02:00
Bogdan-Ștefan Neacșu 60d0f66ab1 Short node identity signature check
Fix tests
2021-12-14 11:36:23 +02:00
Bogdan-Ștefan Neacșu 2389cdbfeb Update network defaults 2021-12-14 11:36:23 +02:00
Jędrzej Stuczyński 8f152d42f0 Feature/simulate (#950)
* Raw scaffold for tx simulate

* Proper error handling in AbciResult parsing

* Simulate without actual signing operation

* Moved all-fee related functionalities to separate module

* Adding GasInfo to transaction results

* Automated gas estimation

* Slightly adjusted public API

* Using auto fees in eth events

* Removed old print statement

* Reorganised nymd client fee handling

* Put bandaid on wallet gas estimation

* Fixed operation re-export

* warning note on get_approximate_fee

* [ci skip] Generate TS types

* Refactored ProtoAbciResult parsing

* Explicit error on abci query failure

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-12-14 09:28:15 +00:00
Drazen Urch 791d051537 Different workshare calculations for rewarded vs active set (#951)
* Add Makefile to make running all checks easier locally

* Different workshare calculation for active vs rewarded set

* Rework omega calculation, update tests

* Remove ZERO const

* unym -> DENOM

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-13 19:11:54 +01:00
Jędrzej Stuczyński f8099cb8c8 Bugfix/rewarding fixes (#953)
* Improved rewarding-related log messages

* Skipping empty mixnode chunks
2021-12-13 15:43:02 +00:00
Tommy Verrall f1e995b076 Merge pull request #947 from nymtech/feature/wallet-ui-updates-round-2
Desktop Wallet UI Updates
2021-12-13 11:11:30 +00:00
Bogdan-Ștefan Neacşu ed1fe22db2 Check the response for multiple sends (#955) 2021-12-10 09:22:37 +01:00
dependabot[bot] b1c45dc0f8 Bump next from 11.1.1 to 11.1.3 in /wallet-web (#952)
Bumps [next](https://github.com/vercel/next.js) from 11.1.1 to 11.1.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v11.1.1...v11.1.3)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-09 09:25:25 +00:00
Drazen Urch 0e0151f781 Feature/profit margin percent config (#949)
* Profit margin percent to config, fix vesting tests

* Add bonding bounds check

* Fix contract test
2021-12-08 20:17:31 +01:00
Drazen Urch 70e148b3ed Run CI for all contracts in one workflow (#948)
* Run CI for all contracts in one workflow

* Rename

* Fix vesting tests
2021-12-08 16:15:18 +01:00
fmtabbara 53138d6292 link to transaction hash 2021-12-07 22:39:13 +00:00
fmtabbara 39650de2bd style update 2021-12-07 21:46:02 +00:00
fmtabbara 7b04093cc5 style updates 2021-12-07 21:24:16 +00:00
fmtabbara a4c9a81399 refresh balance on balance page visit 2021-12-07 21:06:29 +00:00
Drazen Urch f42f76901a Add VestingExecute and VestingQuery client traits (#944)
* Add VestingExecute and VestingQuery client traits

* cover ed25519 verification

* cargo fmt

* `Unix` newline-style

* Remove newline force

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-07 19:56:10 +01:00
fmtabbara 875bcb4e63 update bonding success UI 2021-12-07 13:42:23 +00:00
fmtabbara fa62de5bc6 undelegate page update 2021-12-07 13:17:46 +00:00
fmtabbara 5c3b08e1cd update delegate page 2021-12-07 12:36:21 +00:00
Tommy Verrall 745af89019 Merge pull request #946 from nymtech/feature/docker-updates
Docker updates
2021-12-07 11:53:37 +00:00
Tommy Verrall eb0fb90127 update arg for commit hash 2021-12-07 11:45:46 +00:00
Tommy Verrall 527d5a5d9b update 2021-12-07 11:25:03 +00:00
Tommy Verrall 2dfa0a9d2a update 2021-12-07 11:23:44 +00:00
Tommy Verrall d9bfa4562e Update docker-files
- updated the network explorer
- updated new vesting contract
- updated the version of comos-sdk to be used
2021-12-07 11:22:57 +00:00
Jędrzej Stuczyński aec0239d87 Removed reliance on cosmrs fork (#943)
* Removed reliance on cosmrs fork

* Removed the accidental optional flag on cosmrs import in the wallet
2021-12-07 11:04:32 +00:00
Jędrzej Stuczyński a2324f98f8 Feature/identity verification (#930)
* Base58 representation of ed25519 signature

* Helper for verifying ed25519 signature on sender address

* Signature verification for gateway bonding

* Signature verification for mixnode bonding

* Added owner signatures for bonding in vesting contract

* Fixed choosing mixnode layer test

* Added owner signature fields to nymd client for bonding

* 'Updated' tauri wallet with new bond requirements

* Mixnode sign command with extra address validation

* Sign command for the gateway

* Signing own gateway's address derived with known mnemonic in not(coconut) case

* Fixed imports post-merge
2021-12-07 10:28:44 +00:00
fmtabbara 001d166477 update bond page 2021-12-07 10:23:37 +00:00
fmtabbara bb14e95e61 update send page 2021-12-06 17:32:29 +00:00
Jędrzej Stuczyński 5aa1c29409 Feature/terminology update (#941)
* Corrected used bond/pledge terminology

* ibid for the wallet and explorer

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-12-06 16:30:55 +00:00
Bogdan-Ștefan Neacşu db111490b6 Check the response for other transactions as well (#937) 2021-12-06 17:21:11 +01:00
Jędrzej Stuczyński fe0bb007c9 Don't reset total delegation on mixnode rebond (#940) 2021-12-06 17:20:41 +01:00
Tommy Verrall ddad6d73db Merge pull request #934 from nymtech/feature/client_on_behalf
Feature/client on behalf
2021-12-06 15:33:24 +00:00
fmtabbara 35c04014e5 undo hard-coded data 2021-12-06 15:32:09 +00:00
fmtabbara 138daddfed add custom nav icons 2021-12-06 15:21:34 +00:00
fmtabbara ab3cfe79bc use svgs as React components with svgr 2021-12-06 12:57:46 +00:00
fmtabbara 9fcf3105e0 balance page updates 2021-12-06 10:41:41 +00:00
fmtabbara 51bf117007 fix memory leak 2021-12-06 10:24:44 +00:00
Jędrzej Stuczyński a04d4503b5 Feature/pre cosmrs updates (#935)
* Updated nymd client

* Fixed contract upload and initialisation

* Increased default contract upload fee

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-12-06 09:25:53 +00:00
neacsu 9fb44f9672 [ci skip] Generate TS types 2021-12-03 18:16:55 +00:00
Bogdan-Ștefan Neacșu f542c28e89 Batching for sends 2021-12-03 19:05:21 +01:00
Bogdan-Ștefan Neacșu b809eb6c5f Batch delegations on behalf as well 2021-12-03 16:06:23 +01:00
Bogdan-Ștefan Neacșu 51e82fe930 Add gateway bond on behalf functions 2021-12-03 15:25:47 +01:00
Bogdan-Ștefan Neacșu 67f847e674 Add batched delegated mixnode bonds 2021-12-03 15:02:39 +01:00
Bogdan-Ștefan Neacșu d4736bac27 Fix gas for operations 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 6417feaaed Fix clippy 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 718170c651 Update tauri client 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu c10038e688 Add client-side Operations as well, in case fees need to be modified 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 2f68439916 Unbond/undelegate mixnode with rust client 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu b175480ba5 Bond/delegate mixnode with rust client 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 34c9726ac9 Add vesting contract address to validator client library 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 11b2a7ad3d Rename contract_address to mixnet_contract_address 2021-12-03 15:02:19 +01:00
Drazen Urch 18978c7599 Merge branch 'develop' of https://github.com/nymtech/nym into develop 2021-12-03 10:14:59 +01:00
Drazen Urch 8e52a70685 Update vesting image 2021-12-03 10:14:47 +01:00
Drazen Urch 1f42ce57e3 Allow proxy gateway bonding (#936)
* Allow proxy gateway bonding

* Update msg text

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-02 18:16:26 +01:00
Dave Hrycyszyn 231fba34bc Spelling fix in variable name 2021-12-02 13:15:55 +00:00
Dave Hrycyszyn 0e05fc46c9 Merge remote-tracking branch 'origin/develop' into develop 2021-12-02 13:13:24 +00:00
Dave Hrycyszyn 52777efc53 Tiny spellcheck fix in comment 2021-12-02 13:13:03 +00:00
Jędrzej Stuczyński 54bc198885 Feature/mixnet contract further adjustments (#928)
* Upgraded code to be cosmwasm 1.0-beta.2 compatible (#923)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Feature/cosmwasm plus storage (#924)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* Added cw-storage-plus dependency

* Experimentally replaced storage for config and layers with cw plus Item

* The same for main mixnode storage

* Usingn IndexedMap for mixnodes

* Split delegations from mixnodes into separate module

* MixnodeIndex on Addr directly

* Moved namespace values to constants

* Outdated comment

* [ci skip] Generate TS types

* Removed redundant identity index on mixnodes

* IndexMap for gateways storage

* Moved total delegation into a Map

* Compiling contract code after delegation storage upgrades

Tests dont compile yet and neither, I would assume, the client code

* Delegation type cleanup

* Client fixes

* Migrated delegation tests + fixed them

* Moved Rewarding Status to rewards

* Reward pool

* Rewarding status migrated

* Made clippy happier

* Added explorer API to default workspace members

* Updated delegation types in explorer-api

* Fixed tauri wallet

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Missing license notices

* Dead code removal

* Changed RewardMixnodeV2 to RewardMixnode

* Adjusted module visibility

* Setting rewarding validator address in init msg

* ContractSettings => ContractState

* Transaction-related cleanup

* Changed ownership queries to return full bond information instead of just a bool

* Function for updating post rewarding storage

* Changed the order of arguments in decrementing reward pool

* Helpers for updating storage after rewarding

* Removed redundant turbofish

* [ci skip] Generate TS types

* Changed bond/delegation validation

* Made clippy happier

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-12-02 12:16:16 +00:00
Tommy Verrall 577c5dc1ee Merge pull request #933 from nymtech/test/webpack-wallet
Webpack wallet prod configuration
2021-12-02 10:16:51 +00:00
Dave Hrycyszyn 7927846390 Merge branch 'develop' of github.com:nymtech/nym into develop 2021-12-02 10:12:09 +00:00
Dave Hrycyszyn e41f02ad0d Tiny readme fixup 2021-12-02 10:12:01 +00:00
tommyv1987 e57a5b73a2 [ci skip] Generate TS types 2021-12-02 09:54:58 +00:00
Tommy Verrall 608de11377 Merge pull request #932 from nymtech/feature/add-tx-hash-to-wallet-result
Adding tx_hash to wallet response
2021-12-02 09:44:54 +00:00
futurechimp b88153a2bd [ci skip] Generate TS types 2021-12-01 18:30:05 +00:00
Tommy Verrall 0fd2f946dc dev 2021-12-01 18:23:46 +00:00
Dave Hrycyszyn c5cdbd5bfe Params formatting 2021-12-01 18:23:11 +00:00
Tommy Verrall 3c05cf29e6 fix duplication 2021-12-01 18:22:07 +00:00
futurechimp 6cb58d1f1c Adding tx_hash to wallet response 2021-12-01 18:19:14 +00:00
Tommy Verrall 5727ac5161 minor changes, due to windows complaining on builds. 2021-12-01 18:00:47 +00:00
Tommy Verrall 804f254e16 Compiler complains...
The compiler complains, however, these references are needed to enabled menu actions on mac builds.
2021-12-01 17:30:54 +00:00
Tommy Verrall f03ce6e07b fix typo 2021-12-01 17:11:23 +00:00
Tommy Verrall 2631ed4d0c Add prod config for the wallet
When supplying `production` as the mode in the config for webpack, it complains about an unmet dependency issue with fav-icons. Then trying to supply a favicon.ico, returns a mimetype error. By using a png in alignment to the .ico. It builds. Tested on Linux.
2021-12-01 17:06:47 +00:00
Tommy Verrall 0f93dde8fc Merge pull request #925 from nymtech/feature/ui-enhancements
Feature/UI enhancements for Desktop Wallet
2021-12-01 14:47:21 +00:00
Drazen Urch eb93b428cf Release/1.0.0 pre1 (#931)
* Upgraded code to be cosmwasm 1.0-beta.2 compatible (#923)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Feature/cosmwasm plus storage (#924)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* Added cw-storage-plus dependency

* Experimentally replaced storage for config and layers with cw plus Item

* The same for main mixnode storage

* Usingn IndexedMap for mixnodes

* Split delegations from mixnodes into separate module

* MixnodeIndex on Addr directly

* Moved namespace values to constants

* Outdated comment

* [ci skip] Generate TS types

* Removed redundant identity index on mixnodes

* IndexMap for gateways storage

* Moved total delegation into a Map

* Compiling contract code after delegation storage upgrades

Tests dont compile yet and neither, I would assume, the client code

* Delegation type cleanup

* Client fixes

* Migrated delegation tests + fixed them

* Moved Rewarding Status to rewards

* Reward pool

* Rewarding status migrated

* Made clippy happier

* Added explorer API to default workspace members

* Updated delegation types in explorer-api

* Fixed tauri wallet

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Vesting contract (#900)

* Initial interface spec

* .gitignore

* Finalize implementation

* Correct assumptions, use wasm_execute

* Cleanup

* Track delegation balance

* Add delegation flow img

* Proper messaging from the vesting side

* Add proxy_address to RawDelegationData

* Wrap up (un)delegation

* Add proxy: Addr to MixNodeBond

* Stub in bonding/unbonding

* Migrate vesting to cosmwasm 1.0

* Rebase on top of 1.0.0-pre1

* Reimplement delegations tracking with a Map

* Migrate to cw-storage-plus

* Restructure code, add tests

* Streamline contract code, as per review

* Address review comments

* Pre-merge rebase

* Few more nits

* Few more nits

* Fix test

* cargo fmt

* Fix beta CI

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-01 15:42:34 +01:00
fmtabbara 04b6f83d99 pr updates 2021-12-01 12:35:41 +00:00
fmtabbara f5612cc64b update large button height 2021-11-30 13:39:51 +00:00
fmtabbara d525563a46 link to block explorer 2021-11-30 13:11:51 +00:00
fmtabbara 560b306d32 update admin nav item style 2021-11-30 10:08:29 +00:00
fmtabbara a4a4da1121 style updates 2021-11-29 22:31:11 +00:00
fmtabbara d69cb47c6c style updates 2021-11-29 22:11:28 +00:00
Aid19801 3e93b4ffd5 Merge pull request #926 from nymtech/copy-change-nodemap
typo copy change for nodemap
2021-11-29 13:15:22 +00:00
Aid Thompson 6da660623b typo copy change for nodemap 2021-11-29 13:09:13 +00:00
fmtabbara a85a67199a theme tweaks 2021-11-29 09:52:00 +00:00
fmtabbara 09b6d37ca6 [ci skip] Generate TS types 2021-11-27 22:59:48 +00:00
fmtabbara 4029b1b830 resolve conflict 2021-11-27 22:50:13 +00:00
fmtabbara e075759461 add tooltip hide delay 2021-11-27 22:36:16 +00:00
fmtabbara 1069032cd7 remove unecessary fullscreen property 2021-11-27 22:29:17 +00:00
fmtabbara b97b7410da update balance page 2021-11-27 22:15:57 +00:00
fmtabbara f259012faf update copy component 2021-11-27 22:15:42 +00:00
fmtabbara c00cdafc8c update getBalance hook name 2021-11-27 22:15:13 +00:00
fmtabbara 82c56351de add appbar component 2021-11-27 22:14:04 +00:00
Dave Hrycyszyn 5eeb55aa78 Fixing some clippy warnings (#922)
Co-authored-by: dave <dave@nym-mbp.lan>
2021-11-26 12:51:41 +00:00
Dave Hrycyszyn 94357c6132 Fixing go warning re unused btc lib (#921)
* Fixing go warning re unused btc lib

* Removed commented code

Co-authored-by: dave <dave@nym-mbp.lan>
2021-11-26 12:51:01 +00:00
Mark Sinclair 77c5296c39 testnet-faucet: fix line break in destructuring 2021-11-26 11:57:37 +00:00
Aid19801 6ee5b68b46 Merge pull request #920 from nymtech/nodemap-responsiveness
quick fix adding dimensions to nodemap page for consistency
2021-11-26 10:47:40 +00:00
Aid Thompson 56fa48215b changed country names from official to their aliases to fit in cells 2021-11-26 10:15:59 +00:00
Aid Thompson 26b0d9555f quick fix adding dimensions to nodemap page for consistency 2021-11-26 07:41:58 +00:00
fmtabbara 61a67ac334 add new sign in and create account pages 2021-11-25 23:22:14 +00:00
fmtabbara c2e268a1c9 update app layout to use mui v5 system 2021-11-25 23:21:19 +00:00
fmtabbara cfd0b7868a refine copytoclipboard component 2021-11-25 17:41:35 +00:00
fmtabbara 69b52ae629 new styling + new signin and create account pages 2021-11-25 17:41:15 +00:00
Jędrzej Stuczyński c361de62a5 Introduces query for contract build information (#919)
* Introduces query for contract build information

* Removed temporary test garbage

* Removed unused imports in validator-client if built without nymd-client feature
2021-11-25 15:17:56 +00:00
fmtabbara 7d3d0d8874 upgrade to mui v5 2021-11-25 14:35:26 +00:00
Jędrzej Stuczyński 39c7b131b6 Bugfix/remove mixnode bonding overwrite (#917)
* Disallow double bonding

* Adjusted test assertions
2021-11-25 10:27:31 +00:00
dependabot[bot] 4071a20bb5 Bump nth-check from 2.0.0 to 2.0.1 in /nym-wallet (#918)
Bumps [nth-check](https://github.com/fb55/nth-check) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/fb55/nth-check/releases)
- [Commits](https://github.com/fb55/nth-check/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: nth-check
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-25 10:16:14 +00:00
Tommy Verrall b4519cd77e Merge pull request #911 from nymtech/feature/faucet-page-react
Feature/faucet page react
2021-11-25 09:59:54 +00:00
Dave Hrycyszyn 35748e07c4 Feature/mixnet contract refactor (#910)
* Starting a refactor to cut the huge files into chunks

* Fixing some lints

* ibid

* Mixnode and gateway bonding tests moved

* All transaction test moved into submodules

* Finished splitting out transactions.rs from root into submodules.

* Moved mixnet params state into submodule

* Recombined modules for few top-level actions

* Moving mixnode bonding queries into their own file

* Removed some unused imports

* Got tests running again. Max limit tests not right.

* Fixed tests

* Started moving delegation queries into own module

* Finished moving delegation queries into their own module

* Cleanup

* Moving query limits into relevant modules

* Putting query limits back at top-level

* Using prefix to make storage usage a little more explicit

* Separating storage into smaller chunks

* More storage refactoring

* Finished moving all storage into modules

* Moved all storage prefixes into relevant modules and made them not-public

* Renamed the mostly-empty queries module to query_support

* ibid

* Fixed query support rename problems

* Started to move rewards-related helpers into their own module

* Started moving delegations-related helpers into their own module

* Moved more code from global helpers into delegations helpers

* Moved all remaining test helper code from main helpers file into test helpers

* Made use of test_helpers explicit via a module rename.

Also got rid of non-explicit usages

* Moved mixnode storage retrieval limits into mixnodes storage module

* Moved bond retrieval max limit into storage moduel

* Moved more storage limits into mixnodes storage file; fixed a gateways limit test.

* Added a note on gateways limits constants

I'll re-use the mixnodes values, but it doesn't have to be this way.

We could easily make a specific constant for gateways instaed.

* Renamed "state" to GlobalContractParams

* Pulled bit of test helper code up a level

* Small cleanup of zero spacing in constants

* Made a local helper method private

* Renaming GlobalContractParams to ContractSettings and StateParams to ContractSettingsParams

* ibid

* Renamed contract settings storage methods from "config" (which is a bit vage to "contract_settings"

* Indulging a fullword as a personal protest vs the Go programming language

* Renaming mixnet settings to mixnet contract settings

* Making validate_mixnode_bond private and moving it downwards in the file

* Moving gateway bond validation to the bottom of the tests file

* Getting the wallet compiling again.

* Updated TypeScript client with new types and contract method names

* Updating rust validator client with new contract method names and types

* Fixed type error in mixnet-contract shared msg.

* Used new contract method names and types

* Fixed warnings in non-test code

* All tests compiling

But not passing yet

* Fixed test compilation warnings

* Fixed tests

* Test-locked Delegations struct

Co-authored-by: dave <dave@nym-mbp.lan>
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2021-11-24 22:14:38 +00:00
fmtabbara 54e217ccea update logic 2021-11-23 15:01:05 +00:00
fmtabbara c8f81d118b fix bug with successful response ui 2021-11-23 12:16:07 +00:00
fmtabbara a193e948e6 link address to block explorer + refresh balance after tokens sent 2021-11-23 11:34:05 +00:00
fmtabbara cc77f6e392 add favicon 2021-11-22 14:48:37 +00:00
Tommy Verrall 58f02109be Add data-testids 2021-11-22 11:00:20 +00:00
Fouad 724e0f78c6 Update README.md 2021-11-20 21:27:07 +00:00
fmtabbara d764b11122 responsive tweaks 2021-11-20 21:24:30 +00:00
fmtabbara 4953b40a42 add readme file 2021-11-20 21:12:06 +00:00
fmtabbara 66e5afe485 add form validation and error handling 2021-11-20 21:04:44 +00:00
fmtabbara 9b17a1ca77 add responses 2021-11-19 22:46:32 +00:00
fmtabbara 5ce6147839 set up validator connection 2021-11-19 18:49:52 +00:00
fmtabbara dd3643a1bb set up form ui 2021-11-19 17:41:21 +00:00
fmtabbara 52fedd9866 create base project 2021-11-19 16:27:45 +00:00
275 changed files with 27263 additions and 9764 deletions
@@ -1,4 +1,4 @@
name: Mixnet Contract
name: Contracts
on:
push:
@@ -21,7 +21,7 @@ jobs:
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
mixnet-contract:
contracts:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
@@ -45,20 +45,20 @@ jobs:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/mixnet/Cargo.toml
args: --manifest-path contracts/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/mixnet/Cargo.toml -- --check
args: --manifest-path contracts/Cargo.toml --all -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/mixnet/Cargo.toml -- -D warnings
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
@@ -1,58 +0,0 @@
name: ERC20 Bridge Contract
on: [ push, pull_request ]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
erc20-bridge-contract:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/erc20-bridge/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/erc20-bridge/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- -D warnings
+2
View File
@@ -22,6 +22,8 @@ jobs:
node-version: '14'
- run: npm install
continue-on-error: true
- name: Set environment from the example
run: cp .env.prod .env
- run: npm run test
continue-on-error: true
- run: npm run build
@@ -1,4 +1,4 @@
name: Publish Tauri Wallet
name: Publish Nym Wallet
on:
push:
tags:
+2 -1
View File
@@ -34,4 +34,5 @@ contracts/mixnet/code_id
contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
*.patch
validator-api-config.toml
Generated
+49 -16
View File
@@ -868,8 +868,7 @@ dependencies = [
[[package]]
name = "cosmos-sdk-proto"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb5204c6ddc4352c74297638b5561f2929d6334866c156e5f3c75e1e1a1436a"
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
dependencies = [
"prost",
"prost-types",
@@ -879,8 +878,7 @@ dependencies = [
[[package]]
name = "cosmrs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d31147fe89e547e74e2692e4bec35387e1ea406fe8cfb14c0ea177b58fd2a8a9"
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -901,8 +899,9 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
@@ -913,16 +912,18 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -1207,6 +1208,17 @@ dependencies = [
"zeroize",
]
[[package]]
name = "cw-storage-plus"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3b8b840947313c1a1cccf056836cd79a60b4526bdcd6582995be37dc97be4ae"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "darling"
version = "0.10.2"
@@ -3562,8 +3574,10 @@ name = "nym-gateway"
version = "0.11.0"
dependencies = [
"bip39",
"bs58",
"clap",
"coconut-interface",
"colored",
"config",
"credentials",
"crypto",
@@ -3585,6 +3599,7 @@ dependencies = [
"rand 0.7.3",
"serde",
"sqlx",
"subtle-encoding",
"thiserror",
"tokio",
"tokio-stream",
@@ -3620,6 +3635,7 @@ dependencies = [
"rocket",
"serde",
"serial_test",
"subtle-encoding",
"tokio",
"tokio-util",
"toml",
@@ -3894,10 +3910,11 @@ dependencies = [
[[package]]
name = "okapi"
version = "0.6.0-alpha-1"
version = "0.7.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb085e00daf8d75b9dbf0ffdb4738e69503e28898d9641fa8bdc6ad536c7bcf4"
checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893"
dependencies = [
"log",
"schemars",
"serde",
"serde_json",
@@ -5134,10 +5151,12 @@ dependencies = [
[[package]]
name = "rocket_okapi"
version = "0.7.0-alpha-1"
version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2f4f48fb070f9f6c56d5663df5fa8a514406207744f4abd84661bfb24efd7d"
checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20"
dependencies = [
"either",
"log",
"okapi",
"rocket",
"rocket_okapi_codegen",
@@ -5148,9 +5167,9 @@ dependencies = [
[[package]]
name = "rocket_okapi_codegen"
version = "0.7.0-alpha-1"
version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ccf1550e1c806461a6b08e2ab64eb10701d41bf50bde59ab9aa3a57ab14d41"
checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612"
dependencies = [
"darling 0.13.0",
"proc-macro2",
@@ -7129,6 +7148,7 @@ dependencies = [
"thiserror",
"ts-rs",
"url",
"vesting-contract",
]
[[package]]
@@ -7168,6 +7188,19 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "vesting-contract"
version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"mixnet-contract",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "waker-fn"
version = "1.1.0"
+1
View File
@@ -62,6 +62,7 @@ default-members = [
"service-providers/network-requester",
"mixnode",
"validator-api",
"explorer-api",
]
exclude = ["explorer", "contracts", "tokenomics-py"]
+35
View File
@@ -0,0 +1,35 @@
all: clippy test fmt
clippy: clippy-main clippy-contracts clippy-wallet
test: test-main test-contracts test-wallet
fmt: fmt-main fmt-contracts fmt-wallet
clippy-main:
cargo clippy
clippy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml
clippy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
test-main:
cargo test
test-contracts:
cargo test --manifest-path contracts/Cargo.toml
test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml
fmt-main:
cargo fmt --all
fmt-contracts:
cargo fmt --manifest-path contracts/Cargo.toml --all
fmt-wallet:
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
+1 -1
View File
@@ -13,7 +13,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
* nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
@@ -3,6 +3,5 @@ module github.com/nymtech/nym/clients/native/examples/go
go 1.14
require (
github.com/btcsuite/btcutil v1.0.2 // indirect
github.com/gorilla/websocket v1.4.2
)
+26 -26
View File
@@ -1,6 +1,6 @@
import NetClient, {INetClient} from "./net-client";
import NetClient, { INetClient } from "./net-client";
import {
StateParams,
ContractSettingsParams,
Delegation,
PagedMixDelegationsResponse,
PagedGatewayDelegationsResponse,
@@ -10,10 +10,10 @@ import {
Gateway,
SendRequest
} from "./types";
import {Bip39, Random} from "@cosmjs/crypto";
import {DirectSecp256k1HdWallet, EncodeObject} from "@cosmjs/proto-signing";
import { Bip39, Random } from "@cosmjs/crypto";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import MixnodesCache from "./caches/mixnodes";
import {buildFeeTable, coin, Coin, coins, StdFee} from "@cosmjs/stargate";
import { buildFeeTable, coin, Coin, coins, StdFee } from "@cosmjs/stargate";
import {
ExecuteResult,
InstantiateOptions,
@@ -32,17 +32,17 @@ import {
nativeToPrintable
} from "./currency";
import GatewaysCache from "./caches/gateways";
import QueryClient, {IQueryClient} from "./query-client";
import {nymGasLimits, nymGasPrice} from "./stargate-helper";
import {BroadcastTxSuccess, isBroadcastTxFailure} from "@cosmjs/stargate";
import {makeBankMsgSend} from "./utils";
import QueryClient, { IQueryClient } from "./query-client";
import { nymGasLimits, nymGasPrice } from "./stargate-helper";
import { BroadcastTxSuccess, isBroadcastTxFailure } from "@cosmjs/stargate";
import { makeBankMsgSend } from "./utils";
export const VALIDATOR_API_PORT = "8080";
export const VALIDATOR_API_GATEWAYS = "v1/gateways";
export const VALIDATOR_API_MIXNODES = "v1/mixnodes";
export {coins, coin};
export {Coin};
export { coins, coin };
export { Coin };
export {
displayAmountToNative,
nativeCoinToDisplay,
@@ -52,7 +52,7 @@ export {
MappedCoin,
CoinMap
}
export {nymGasLimits, nymGasPrice}
export { nymGasLimits, nymGasPrice }
export default class ValidatorClient {
private readonly client: INetClient | IQueryClient
@@ -226,12 +226,12 @@ export default class ValidatorClient {
*/
static async mnemonicToAddress(mnemonic: string, prefix: string): Promise<string> {
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
const [{address}] = await wallet.getAccounts()
const [{ address }] = await wallet.getAccounts()
return address
}
static async buildWallet(mnemonic: string, prefix: string): Promise<DirectSecp256k1HdWallet> {
const signerOptions = {prefix: prefix};
const signerOptions = { prefix: prefix };
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions);
}
@@ -239,7 +239,7 @@ export default class ValidatorClient {
return this.client.getBalance(address, this.denom).catch((err) => this.handleRequestFailure(err));
}
async getStateParams(): Promise<StateParams> {
async getStateParams(): Promise<ContractSettingsParams> {
return this.client.getStateParams(this.contractAddress).catch((err) => this.handleRequestFailure(err))
}
@@ -293,7 +293,7 @@ export default class ValidatorClient {
*/
async bondMixnode(mixNode: MixNode, bond: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {bond_mixnode: {mix_node: mixNode}}, "adding mixnode", [bond]).catch((err) => this.handleRequestFailure(err));
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { bond_mixnode: { mix_node: mixNode } }, "adding mixnode", [bond]).catch((err) => this.handleRequestFailure(err));
console.log(`account ${this.client.clientAddress} added mixnode with ${mixNode.host}`);
return result;
} else {
@@ -307,7 +307,7 @@ export default class ValidatorClient {
*/
async unbondMixnode(): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {unbond_mixnode: {}}).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { unbond_mixnode: {} }).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} unbonded mixnode`);
return result;
} else {
@@ -324,7 +324,7 @@ export default class ValidatorClient {
// requires coin type to ensure correct denomination (
async delegateToMixnode(mixIdentity: string, amount: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {delegate_to_mixnode: {mix_identity: mixIdentity}}, `delegating to ${mixIdentity}`, [amount]).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { delegate_to_mixnode: { mix_identity: mixIdentity } }, `delegating to ${mixIdentity}`, [amount]).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} delegated ${amount} to mixnode ${mixIdentity}`);
return result;
} else {
@@ -339,7 +339,7 @@ export default class ValidatorClient {
*/
async removeMixnodeDelegation(mixIdentity: string): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {undelegate_from_mixnode: {mix_identity: mixIdentity}}).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { undelegate_from_mixnode: { mix_identity: mixIdentity } }).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} removed delegation from mixnode ${mixIdentity}`);
return result;
} else {
@@ -356,7 +356,7 @@ export default class ValidatorClient {
// requires coin type to ensure correct denomination (
async delegateToGateway(gatewayIdentity: string, amount: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {delegate_to_gateway: {gateway_identity: gatewayIdentity}}, `delegating to ${gatewayIdentity}`, [amount]).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { delegate_to_gateway: { gateway_identity: gatewayIdentity } }, `delegating to ${gatewayIdentity}`, [amount]).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} delegated ${amount} to gateway ${gatewayIdentity}`);
return result;
} else {
@@ -371,7 +371,7 @@ export default class ValidatorClient {
*/
async removeGatewayDelegation(gatewayIdentity: string): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {undelegate_from_gateway: {gateway_identity: gatewayIdentity}}).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { undelegate_from_gateway: { gateway_identity: gatewayIdentity } }).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} removed delegation from gateway ${gatewayIdentity}`);
return result;
} else {
@@ -450,7 +450,7 @@ export default class ValidatorClient {
*/
async bondGateway(gateway: Gateway, bond: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {bond_gateway: {gateway: gateway}}, "adding gateway", [bond]).catch((err) => this.handleRequestFailure(err));
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { bond_gateway: { gateway: gateway } }, "adding gateway", [bond]).catch((err) => this.handleRequestFailure(err));
console.log(`account ${this.client.clientAddress} added gateway with ${gateway.host}`);
return result;
} else {
@@ -463,7 +463,7 @@ export default class ValidatorClient {
*/
async unbondGateway(): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {unbond_gateway: {}}).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { unbond_gateway: {} }).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} unbonded gateway`);
return result;
} else {
@@ -471,9 +471,9 @@ export default class ValidatorClient {
}
}
async updateStateParams(newParams: StateParams): Promise<ExecuteResult> {
async updateStateParams(newParams: ContractSettingsParams): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
return await this.client.executeContract(this.client.clientAddress, this.contractAddress, {update_state_params: newParams}, "updating contract state").catch((err) => this.handleRequestFailure(err));
return await this.client.executeContract(this.client.clientAddress, this.contractAddress, { update_contract_settings: newParams }, "updating contract settings").catch((err) => this.handleRequestFailure(err));
} else {
throw new Error("Tried to update state params with a query client")
}
@@ -580,7 +580,7 @@ export default class ValidatorClient {
// the function to calculate fee for a single entry is not exposed...
console.log(`this.denom is ${this.denom}`);
const table = buildFeeTable(nymGasPrice(this.prefix), {sendMultiple: nymGasLimits.send * data.length}, {sendMultiple: nymGasLimits.send * data.length})
const table = buildFeeTable(nymGasPrice(this.prefix), { sendMultiple: nymGasLimits.send * data.length }, { sendMultiple: nymGasLimits.send * data.length })
const fee = table.sendMultiple
const result = await this.client.signAndBroadcast(senderAddress, encoded, fee, memo)
if (isBroadcastTxFailure(result)) {
+8 -8
View File
@@ -80,7 +80,7 @@ export default class NetClient implements INetClient {
}
public static async connect(wallet: DirectSecp256k1HdWallet, url: string, prefix: string): Promise<INetClient> {
const [{address}] = await wallet.getAccounts();
const [{ address }] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
gasLimits: nymGasLimits,
@@ -95,17 +95,17 @@ export default class NetClient implements INetClient {
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit, start_after } });
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit, start_after } });
}
}
@@ -166,11 +166,11 @@ export default class NetClient implements INetClient {
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
return this.cosmClient.queryContractSmart(contractAddress, { owns_mixnode: { address } });
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
return this.cosmClient.queryContractSmart(contractAddress, { owns_gateway: { address } });
}
public getBalance(address: string, denom: string): Promise<Coin | null> {
@@ -178,7 +178,7 @@ export default class NetClient implements INetClient {
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
return this.cosmClient.queryContractSmart(contractAddress, { contract_settings_params: {} });
}
public executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult> {
+10 -10
View File
@@ -6,7 +6,7 @@ import {
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
ContractSettingsParams
} from "./types";
export interface IQueryClient {
@@ -28,7 +28,7 @@ export interface IQueryClient {
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<StateParams>;
getStateParams(contractAddress: string): Promise<ContractSettingsParams>;
changeValidator(newUrl: string): Promise<void>
}
@@ -59,17 +59,17 @@ export default class QueryClient implements IQueryClient {
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit, start_after } });
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit, start_after } });
}
}
@@ -130,18 +130,18 @@ export default class QueryClient implements IQueryClient {
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
return this.cosmClient.queryContractSmart(contractAddress, { owns_mixnode: { address } });
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
return this.cosmClient.queryContractSmart(contractAddress, { owns_gateway: { address } });
}
public getBalance(address: string, stakeDenom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, stakeDenom);
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
public getStateParams(contractAddress: string): Promise<ContractSettingsParams> {
return this.cosmClient.queryContractSmart(contractAddress, { contract_settings_params: {} });
}
}
+2 -2
View File
@@ -42,7 +42,7 @@ export type GatewayOwnershipResponse = {
has_gateway: boolean,
}
export type StateParams = {
export type ContractSettingsParams = {
epoch_length: number,
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and Decimal that don't have
@@ -103,7 +103,7 @@ export type MixNode = {
export type GatewayBond = {
owner: string
gateway: Gateway,
bond_amount: Coin,
total_delegation: Coin,
}
@@ -10,6 +10,7 @@ rust-version = "1.56"
[dependencies]
base64 = "0.13"
mixnet-contract = { path="../../../common/mixnet-contract" }
vesting-contract = { path="../../../contracts/vesting" }
serde = { version="1", features=["derive"] }
serde_json = "1"
reqwest = { version="0.11", features=["json"] }
@@ -26,12 +27,13 @@ network-defaults = { path = "../../network-defaults" }
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
#cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"], optional = true }
prost = { version = "0.9", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
cosmwasm-std = { version = "1.0.0-beta2", optional = true }
ts-rs = {version = "5.1", optional = true}
[features]
@@ -6,13 +6,18 @@ use crate::nymd::{
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "nymd-client")]
use mixnet_contract::StateParams;
use mixnet_contract::ContractStateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond, MixnodeRewardingStatusResponse};
#[cfg(feature = "nymd-client")]
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
use mixnet_contract::{
Delegation, MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse,
};
use mixnet_contract::{GatewayBond, MixNodeBond};
#[cfg(feature = "nymd-client")]
use std::str::FromStr;
use url::Url;
#[cfg(feature = "nymd-client")]
@@ -20,6 +25,7 @@ pub struct Config {
api_url: Url,
nymd_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
@@ -32,10 +38,12 @@ impl Config {
nymd_url: Url,
api_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
) -> Self {
Config {
nymd_url,
mixnet_contract_address,
vesting_contract_address,
api_url,
mixnode_page_limit: None,
gateway_page_limit: None,
@@ -62,6 +70,7 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
@@ -83,11 +92,14 @@ impl Client<SigningNymdClient> {
let nymd_client = NymdClient::connect_with_mnemonic(
config.nymd_url.as_str(),
config.mixnet_contract_address.clone(),
config.vesting_contract_address.clone(),
mnemonic.clone(),
None,
)?;
Ok(Client {
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -101,7 +113,9 @@ impl Client<SigningNymdClient> {
self.nymd = NymdClient::connect_with_mnemonic(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone(),
self.vesting_contract_address.clone(),
self.mnemonic.clone().unwrap(),
None,
)?;
Ok(())
}
@@ -113,16 +127,19 @@ impl Client<QueryNymdClient> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let nymd_client = NymdClient::connect(
config.nymd_url.as_str(),
config
.mixnet_contract_address
.clone()
.ok_or(ValidatorClientError::NymdError(
NymdError::NoContractAddressAvailable,
))?,
config.mixnet_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS)
.unwrap()
}),
config.vesting_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_VESTING_CONTRACT_ADDRESS)
.unwrap()
}),
)?;
Ok(Client {
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -136,6 +153,7 @@ impl Client<QueryNymdClient> {
self.nymd = NymdClient::connect(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone().unwrap(),
self.vesting_contract_address.clone().unwrap(),
)?;
Ok(())
}
@@ -165,11 +183,18 @@ impl<C> Client<C> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_state_params().await?)
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_mixnet_contract_version().await?)
}
pub async fn get_current_rewarding_interval(
@@ -300,9 +325,7 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_delegations(
&self,
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -311,7 +334,7 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nymd
.get_all_mix_delegations_paged(
.get_all_network_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
@@ -328,10 +351,10 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_reverse_mixnode_delegations(
pub async fn get_all_delegator_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -340,13 +363,13 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nymd
.get_reverse_mix_delegations_paged(
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
.get_delegator_delegations_paged(
delegation_owner.to_string(),
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegated_nodes);
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
@@ -358,28 +381,6 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_delegations_of_owner(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
for node_identity in self
.get_all_nymd_reverse_mixnode_delegations(delegation_owner)
.await?
{
let delegation = self
.nymd
.get_mix_delegation(node_identity, delegation_owner)
.await?;
delegations.push(delegation);
}
Ok(delegations)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -4,7 +4,7 @@
use crate::nymd::cosmwasm_client::helpers::create_pagination;
use crate::nymd::cosmwasm_client::types::{
Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId,
SequenceResponse,
SequenceResponse, SimulateResponse,
};
use crate::nymd::error::NymdError;
use async_trait::async_trait;
@@ -14,15 +14,19 @@ use cosmrs::proto::cosmos::auth::v1beta1::{
use cosmrs::proto::cosmos::bank::v1beta1::{
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
};
use cosmrs::proto::cosmwasm::wasm::v1beta1::*;
use cosmrs::proto::cosmos::tx::v1beta1::{
SimulateRequest, SimulateResponse as ProtoSimulateResponse,
};
use cosmrs::proto::cosmwasm::wasm::v1::*;
use cosmrs::rpc::endpoint::block::Response as BlockResponse;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::endpoint::tx::Response as TxResponse;
use cosmrs::rpc::query::Query;
use cosmrs::rpc::{self, HttpClient, Order};
use cosmrs::tendermint::abci::Code as AbciCode;
use cosmrs::tendermint::abci::Transaction;
use cosmrs::tendermint::{abci, block, chain};
use cosmrs::{tx, AccountId, Coin, Denom};
use cosmrs::{tx, AccountId, Coin, Denom, Tx};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -49,6 +53,11 @@ pub trait CosmWasmClient: rpc::Client {
let res = self.abci_query(path, buf, None, false).await?;
match res.code {
AbciCode::Err(code) => return Err(NymdError::AbciError(code, res.log)),
AbciCode::Ok => (),
}
Ok(Res::decode(res.value.as_ref())?)
}
@@ -213,7 +222,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_codes(&self) -> Result<Vec<Code>, NymdError> {
let path = Some("/cosmwasm.wasm.v1beta1.Query/Codes".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap());
let mut raw_codes = Vec::new();
let mut pagination = None;
@@ -240,7 +249,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_code_details(&self, code_id: ContractCodeId) -> Result<CodeDetails, NymdError> {
let path = Some("/cosmwasm.wasm.v1beta1.Query/Code".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1.Query/Code".parse().unwrap());
let req = QueryCodeRequest { code_id };
@@ -255,11 +264,7 @@ pub trait CosmWasmClient: rpc::Client {
}
}
async fn get_contracts(&self, code_id: ContractCodeId) -> Result<Vec<AccountId>, NymdError> {
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/ContractsByCode"
.parse()
.unwrap(),
);
let path = Some("/cosmwasm.wasm.v1.Query/ContractsByCode".parse().unwrap());
let mut raw_contracts = Vec::new();
let mut pagination = None;
@@ -290,7 +295,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_contract(&self, address: &AccountId) -> Result<Contract, NymdError> {
let path = Some("/cosmwasm.wasm.v1beta1.Query/ContractInfo".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1.Query/ContractInfo".parse().unwrap());
let req = QueryContractInfoRequest {
address: address.to_string(),
@@ -315,11 +320,7 @@ pub trait CosmWasmClient: rpc::Client {
&self,
address: &AccountId,
) -> Result<Vec<ContractCodeHistoryEntry>, NymdError> {
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/ContractHistory"
.parse()
.unwrap(),
);
let path = Some("/cosmwasm.wasm.v1.Query/ContractHistory".parse().unwrap());
let mut raw_entries = Vec::new();
let mut pagination = None;
@@ -353,11 +354,7 @@ pub trait CosmWasmClient: rpc::Client {
address: &AccountId,
query_data: Vec<u8>,
) -> Result<Vec<u8>, NymdError> {
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/RawContractState"
.parse()
.unwrap(),
);
let path = Some("/cosmwasm.wasm.v1.Query/RawContractState".parse().unwrap());
let req = QueryRawContractStateRequest {
address: address.to_string(),
@@ -381,7 +378,7 @@ pub trait CosmWasmClient: rpc::Client {
for<'a> T: Deserialize<'a>,
{
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/SmartContractState"
"/cosmwasm.wasm.v1.Query/SmartContractState"
.parse()
.unwrap(),
);
@@ -400,4 +397,27 @@ pub trait CosmWasmClient: rpc::Client {
Ok(serde_json::from_slice(&res.data)?)
}
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
// of writing this, the option does not work
#[allow(deprecated)]
async fn query_simulate(
&self,
tx: Option<Tx>,
tx_bytes: Vec<u8>,
) -> Result<SimulateResponse, NymdError> {
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".parse().unwrap());
let req = SimulateRequest {
tx: tx.map(Into::into),
tx_bytes,
};
let res = self
.make_abci_query::<_, ProtoSimulateResponse>(path, req)
.await?;
res.try_into()
}
}
@@ -29,7 +29,7 @@ pub(crate) fn find_attribute<'a>(
) -> Option<&'a cosmwasm_std::Attribute> {
logs.iter()
.flat_map(|log| log.events.iter())
.find(|event| event.kind == event_type)?
.find(|event| event.ty == event_type)?
.attributes
.iter()
.find(|attr| attr.key == attribute_key)
@@ -61,7 +61,7 @@ mod tests {
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].msg_index, 0);
assert_eq!(parsed[0].events.len(), 1);
assert_eq!(parsed[0].events[0].kind, "message");
assert_eq!(parsed[0].events[0].ty, "message");
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
assert_eq!(parsed[0].events[0].attributes[3].value, "1");
}
@@ -76,12 +76,12 @@ mod tests {
assert_eq!(parsed[2].msg_index, 2);
assert_eq!(parsed[0].events.len(), 1);
assert_eq!(parsed[0].events[0].kind, "message");
assert_eq!(parsed[0].events[0].ty, "message");
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
assert_eq!(parsed[0].events[0].attributes[3].value, "9");
assert_eq!(parsed[2].events.len(), 1);
assert_eq!(parsed[2].events[0].kind, "message");
assert_eq!(parsed[2].events[0].ty, "message");
assert_eq!(parsed[2].events[0].attributes[2].key, "signer");
assert_eq!(
parsed[2].events[0].attributes[2].value,
@@ -3,6 +3,7 @@
use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::GasPrice;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use std::convert::TryInto;
@@ -23,9 +24,10 @@ where
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<signing_client::Client, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
signing_client::Client::connect_with_signer(endpoint, signer)
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
}
@@ -1,37 +1,100 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryInto;
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{self, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, AccountId, Any, Coin, Tx};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::error::NymdError;
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{Fee, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Any, Coin};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use std::convert::TryInto;
use crate::nymd::{CosmosCoin, GasPrice};
// we need to have **a** valid secp256k1 signature for simulation purposes.
// it doesn't matter what it is as long as it parses correctly
const DUMMY_SECP256K1_SIGNATURE: &[u8] = &[
54, 167, 169, 61, 100, 173, 231, 87, 1, 113, 179, 49, 102, 141, 67, 22, 170, 153, 52, 88, 178,
159, 200, 11, 37, 138, 76, 221, 187, 70, 104, 123, 98, 216, 190, 249, 149, 81, 1, 158, 0, 220,
32, 147, 101, 60, 64, 77, 44, 83, 221, 119, 170, 124, 109, 177, 73, 116, 46, 57, 102, 181, 98,
91,
];
#[async_trait]
pub trait SigningCosmWasmClient: CosmWasmClient {
fn signer(&self) -> &DirectSecp256k1HdWallet;
fn gas_price(&self) -> &GasPrice;
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
let signer_accounts = self.signer().try_derive_accounts().ok()?;
let account_from_signer = signer_accounts
.iter()
.find(|account| &account.address == signer_address)?;
let public_key = account_from_signer.public_key;
Some(public_key.into())
}
async fn simulate(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NymdError> {
let public_key = self.signer_public_key(signer_address);
let sequence_response = self.get_sequence(signer_address).await?;
let partial_tx = Tx {
body: tx::Body {
messages,
memo: memo.into(),
timeout_height: 0u32.into(),
extension_options: vec![],
non_critical_extension_options: vec![],
},
auth_info: tx::AuthInfo {
signer_infos: vec![tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_response.sequence,
}],
fee: tx::Fee::from_amount_and_gas(
CosmosCoin {
denom: "".parse().unwrap(),
amount: 0u64.into(),
},
0,
),
},
signatures: vec![DUMMY_SECP256K1_SIGNATURE.try_into().unwrap()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
}
async fn upload(
&self,
sender_address: &AccountId,
wasm_code: Vec<u8>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
mut meta: Option<UploadMeta>,
) -> Result<UploadResult, NymdError> {
let compressed = compress_wasm_code(&wasm_code)?;
let compressed_size = compressed.len();
@@ -42,14 +105,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let upload_msg = cosmwasm::MsgStoreCode {
sender: sender_address.clone(),
wasm_byte_code: compressed,
source: meta
.as_mut()
.map(|meta| meta.source.take())
.unwrap_or_default(),
builder: meta
.as_mut()
.map(|meta| meta.builder.take())
.unwrap_or_default(),
instantiate_permission: Default::default(),
}
.to_any()
@@ -61,12 +116,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()?;
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
// TODO: should those strings be extracted into some constants?
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or code_id is not a number, there's no way we can recover, we're probably connected
// to wrong validator or something
let code_id = logs::find_attribute(&logs, "message", "code_id")
let code_id = logs::find_attribute(&logs, "store_code", "code_id")
.unwrap()
.value
.parse()
@@ -80,6 +136,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
code_id,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -111,7 +168,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
// now this is a weird one. the protobuf files say this field is optional,
// but if you omit it, the initialisation will fail CheckTx
label: Some(label),
init_msg: serde_json::to_vec(msg)?,
msg: serde_json::to_vec(msg)?,
funds: options.map(|options| options.funds).unwrap_or_default(),
}
.to_any()
@@ -123,12 +180,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()?;
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
// TODO: should those strings be extracted into some constants?
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or address is malformed, there's no way we can recover, we're probably connected
// to wrong validator or something
let contract_address = logs::find_attribute(&logs, "message", "contract_address")
let contract_address = logs::find_attribute(&logs, "instantiate", "_contract_address")
.unwrap()
.value
.parse()
@@ -138,6 +196,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
contract_address,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -162,9 +221,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -187,9 +249,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -209,7 +274,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
sender: sender_address.clone(),
contract: contract_address.clone(),
code_id,
migrate_msg: serde_json::to_vec(msg)?,
msg: serde_json::to_vec(msg)?,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
@@ -219,9 +284,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(MigrateResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -251,9 +319,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -288,14 +359,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
debug!(
"gas wanted: {:?}, gas used: {:?}",
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
);
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -316,7 +385,36 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
.await
.await?
.check_response()
}
async fn send_tokens_multiple<I>(
&self,
sender_address: &AccountId,
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError>
where
I: IntoIterator<Item = (AccountId, Vec<Coin>)> + Send,
{
let messages = msgs
.into_iter()
.map(|(to_address, amount)| {
MsgSend {
from_address: sender_address.clone(),
to_address,
amount,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
})
.collect::<Result<_, _>>()?;
self.sign_and_broadcast_commit(sender_address, messages, fee, memo)
.await?
.check_response()
}
async fn delegate_tokens(
@@ -336,7 +434,8 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
.await
.await?
.check_response()
}
async fn undelegate_tokens(
@@ -356,7 +455,8 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
.await
.await?
.check_response()
}
async fn withdraw_rewards(
@@ -374,7 +474,45 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
.await
.await?
.check_response()
}
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
#[allow(clippy::ptr_arg)]
async fn determine_transaction_fee(
&self,
signer_address: &AccountId,
messages: &[Any],
fee: Fee,
memo: &String,
) -> Result<tx::Fee, NymdError> {
let fee = match fee {
Fee::Manual(fee) => fee,
Fee::Auto(multiplier) => {
debug!("Trying to simulate gas costs...");
// from what I've seen in manual testing, gas estimation does not exist if transaction
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
let gas_estimation = self
.simulate(signer_address, messages.to_vec(), memo.clone())
.await?
.gas_info
.ok_or(NymdError::GasEstimationFailure)?
.gas_used;
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
let gas = ((gas_estimation.value() as f32 * multiplier) as u64).into();
debug!("Gas estimation: {}", gas_estimation);
debug!("Multiplying the estimation by {}", multiplier);
debug!("Final gas limit used: {}", gas);
let fee = self.gas_price() * gas;
tx::Fee::from_amount_and_gas(fee, gas)
}
};
debug!("Fee used for the transaction: {:?}", fee);
Ok(fee)
}
/// Broadcast a transaction, returning immediately.
@@ -385,6 +523,10 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_async::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -401,6 +543,10 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_sync::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -417,6 +563,11 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -429,7 +580,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: Fee,
fee: tx::Fee,
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
) -> Result<tx::Raw, NymdError> {
@@ -467,7 +618,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: Fee,
fee: tx::Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<tx::Raw, NymdError> {
// TODO: Future optimisation: rather than grabbing current account_number and sequence
@@ -489,18 +640,24 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
pub struct Client {
rpc_client: HttpClient,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
}
impl Client {
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<Self, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let rpc_client = HttpClient::new(endpoint)?;
Ok(Client { rpc_client, signer })
Ok(Client {
rpc_client,
signer,
gas_price: gas_price.unwrap_or_default(),
})
}
}
@@ -522,4 +679,8 @@ impl SigningCosmWasmClient for Client {
fn signer(&self) -> &DirectSecp256k1HdWallet {
&self.signer
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
@@ -7,15 +7,19 @@ use crate::nymd::cosmwasm_client::logs::Log;
use crate::nymd::error::NymdError;
use cosmrs::crypto::PublicKey;
use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount;
use cosmrs::proto::cosmwasm::wasm::v1beta1::{
use cosmrs::proto::cosmos::base::abci::v1beta1::{
GasInfo as ProtoGasInfo, Result as ProtoAbciResult,
};
use cosmrs::proto::cosmos::tx::v1beta1::SimulateResponse as ProtoSimulateResponse;
use cosmrs::proto::cosmwasm::wasm::v1::{
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
};
use cosmrs::tendermint::chain;
use cosmrs::tx::{AccountNumber, SequenceNumber};
use cosmrs::tendermint::{abci, chain};
use cosmrs::tx::{AccountNumber, Gas, SequenceNumber};
use cosmrs::{tx, AccountId, Coin};
use serde::Serialize;
use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};
pub type ContractCodeId = u64;
@@ -70,18 +74,6 @@ pub struct Code {
/// sha256 hash of the code stored
pub data_hash: Vec<u8>,
/// An URL to a .tar.gz archive of the source code of the contract,
/// which can be used to reproducibly build the Wasm bytecode.
///
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub source: Option<String>,
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
///
/// @example ```cosmwasm/rust-optimizer:0.8.0```
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub builder: Option<String>,
}
impl TryFrom<CodeInfoResponse> for Code {
@@ -92,31 +84,16 @@ impl TryFrom<CodeInfoResponse> for Code {
code_id,
creator,
data_hash,
source,
builder,
} = value;
let creator = creator
.parse()
.map_err(|_| NymdError::MalformedAccountAddress(creator))?;
let source = if source.is_empty() {
None
} else {
Some(source)
};
let builder = if builder.is_empty() {
None
} else {
Some(builder)
};
Ok(Code {
code_id,
creator,
data_hash,
source,
builder,
})
}
}
@@ -242,6 +219,101 @@ impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
}
}
#[derive(Debug)]
pub struct GasInfo {
/// GasWanted is the maximum units of work we allow this tx to perform.
pub gas_wanted: Gas,
/// GasUsed is the amount of gas actually consumed.
pub gas_used: Gas,
}
impl From<ProtoGasInfo> for GasInfo {
fn from(value: ProtoGasInfo) -> Self {
GasInfo {
gas_wanted: value.gas_wanted.into(),
gas_used: value.gas_used.into(),
}
}
}
impl GasInfo {
pub fn new(gas_wanted: Gas, gas_used: Gas) -> Self {
GasInfo {
gas_wanted,
gas_used,
}
}
}
#[derive(Debug)]
pub struct AbciResult {
/// Data is any data returned from message or handler execution. It MUST be
/// length prefixed in order to separate data from multiple message executions.
pub data: Vec<u8>,
/// Log contains the log information from message or handler execution.
// todo: try to parse into Log?
pub log: String,
/// Events contains a slice of Event objects that were emitted during message
/// or handler execution.
pub events: Vec<abci::Event>,
}
impl TryFrom<ProtoAbciResult> for AbciResult {
type Error = NymdError;
fn try_from(value: ProtoAbciResult) -> Result<Self, Self::Error> {
let mut events = Vec::with_capacity(value.events.len());
for proto_event in value.events.into_iter() {
let type_str = proto_event.r#type;
let mut attributes = Vec::with_capacity(proto_event.attributes.len());
for proto_attribute in proto_event.attributes.into_iter() {
let stringified_ked = String::from_utf8(proto_attribute.key)
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
let stringified_value = String::from_utf8(proto_attribute.value)
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
attributes.push(abci::tag::Tag {
key: stringified_ked.parse().unwrap(),
value: stringified_value.parse().unwrap(),
})
}
events.push(abci::Event {
type_str,
attributes,
})
}
Ok(AbciResult {
data: value.data,
log: value.log,
events,
})
}
}
#[derive(Debug)]
pub struct SimulateResponse {
pub gas_info: Option<GasInfo>,
pub result: Option<AbciResult>,
}
impl TryFrom<ProtoSimulateResponse> for SimulateResponse {
type Error = NymdError;
fn try_from(value: ProtoSimulateResponse) -> Result<Self, Self::Error> {
Ok(SimulateResponse {
gas_info: value.gas_info.map(|gas_info| gas_info.into()),
result: value.result.map(|result| result.try_into()).transpose()?,
})
}
}
// ##############################################################################
// types specific to the signing client (perhaps they should go to separate file)
// ##############################################################################
@@ -254,21 +326,6 @@ pub struct SignerData {
pub chain_id: chain::Id,
}
#[derive(Debug)]
pub struct UploadMeta {
/// An URL to a .tar.gz archive of the source code of the contract,
/// which can be used to reproducibly build the Wasm bytecode.
///
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub source: Option<String>,
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
///
/// @example ```cosmwasm/rust-optimizer:0.8.0```
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub builder: Option<String>,
}
#[derive(Debug)]
pub struct UploadResult {
/// Size of the original wasm code in bytes
@@ -290,6 +347,8 @@ pub struct UploadResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -316,6 +375,8 @@ pub struct InstantiateResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -324,6 +385,8 @@ pub struct ChangeAdminResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -332,6 +395,8 @@ pub struct MigrateResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -340,4 +405,6 @@ pub struct ExecuteResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::types::ContractCodeId;
use cosmrs::tendermint::block;
use cosmrs::tendermint::{abci, block};
use cosmrs::{bip32, tx, AccountId};
use std::io;
use thiserror::Error;
@@ -102,6 +102,12 @@ pub enum NymdError {
#[error("The provided gas price is malformed")]
MalformedGasPrice,
#[error("Failed to estimate gas price for the transaction")]
GasEstimationFailure,
#[error("Abci query failed with code {0} - {1}")]
AbciError(u32, abci::Log),
}
impl NymdError {
@@ -38,7 +38,7 @@ impl<'a> Mul<Gas> for &'a GasPrice {
// however, realistically that is impossible to happen as the resultant value
// would have to be way higher than our token limit of 10^15 (1 billion of tokens * 1 million for denomination)
// and max value of u128 is approximately 10^38
if limit_uint128.u128() * gas_price_numerator > amount.u128() * gas_price_denominator {
if limit_uint128 * gas_price_numerator > amount * gas_price_denominator {
amount += Uint128::new(1);
}
@@ -17,17 +17,29 @@ pub enum Operation {
Send,
BondMixnode,
BondMixnodeOnBehalf,
UnbondMixnode,
UnbondMixnodeOnBehalf,
DelegateToMixnode,
DelegateToMixnodeOnBehalf,
UndelegateFromMixnode,
UndelegateFromMixnodeOnBehalf,
BondGateway,
BondGatewayOnBehalf,
UnbondGateway,
UnbondGatewayOnBehalf,
UpdateStateParams,
UpdateContractSettings,
BeginMixnodeRewarding,
FinishMixnodeRewarding,
TrackUnbondGateway,
TrackUnbondMixnode,
WithdrawVestedCoins,
TrackUndelegation,
CreatePeriodicVestingAccount,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -43,14 +55,27 @@ impl fmt::Display for Operation {
Operation::ChangeAdmin => f.write_str("ChangeAdmin"),
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::BondMixnodeOnBehalf => f.write_str("BondMixnodeOnBehalf"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
Operation::UnbondMixnodeOnBehalf => f.write_str("UnbondMixnodeOnBehalf"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::BondGatewayOnBehalf => f.write_str("BondGatewayOnBehalf"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::UnbondGatewayOnBehalf => f.write_str("UnbondGatewayOnBehalf"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::DelegateToMixnodeOnBehalf => f.write_str("DelegateToMixnodeOnBehalf"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
Operation::UndelegateFromMixnodeOnBehalf => {
f.write_str("UndelegateFromMixnodeOnBehalf")
}
Operation::UpdateContractSettings => f.write_str("UpdateContractSettings"),
Operation::BeginMixnodeRewarding => f.write_str("BeginMixnodeRewarding"),
Operation::FinishMixnodeRewarding => f.write_str("FinishMixnodeRewarding"),
Operation::TrackUnbondGateway => f.write_str("TrackUnbondGateway"),
Operation::TrackUnbondMixnode => f.write_str("TrackUnbondMixnode"),
Operation::WithdrawVestedCoins => f.write_str("WithdrawVestedCoins"),
Operation::TrackUndelegation => f.write_str("TrackUndelegation"),
Operation::CreatePeriodicVestingAccount => f.write_str("CreatePeriodicVestingAccount"),
}
}
}
@@ -59,23 +84,34 @@ impl Operation {
// TODO: some value tweaking
pub fn default_gas_limit(&self) -> Gas {
match self {
Operation::Upload => 2_500_000u64.into(),
Operation::Upload => 3_000_000u64.into(),
Operation::Init => 500_000u64.into(),
Operation::Migrate => 200_000u64.into(),
Operation::ChangeAdmin => 80_000u64.into(),
Operation::Send => 80_000u64.into(),
Operation::BondMixnode => 175_000u64.into(),
Operation::BondMixnodeOnBehalf => 200_000u64.into(),
Operation::UnbondMixnode => 175_000u64.into(),
Operation::UnbondMixnodeOnBehalf => 175_000u64.into(),
Operation::DelegateToMixnode => 175_000u64.into(),
Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(),
Operation::UndelegateFromMixnode => 175_000u64.into(),
Operation::UndelegateFromMixnodeOnBehalf => 175_000u64.into(),
Operation::BondGateway => 175_000u64.into(),
Operation::BondGatewayOnBehalf => 200_000u64.into(),
Operation::UnbondGateway => 175_000u64.into(),
Operation::UnbondGatewayOnBehalf => 200_000u64.into(),
Operation::UpdateStateParams => 175_000u64.into(),
Operation::UpdateContractSettings => 175_000u64.into(),
Operation::BeginMixnodeRewarding => 175_000u64.into(),
Operation::FinishMixnodeRewarding => 175_000u64.into(),
Operation::TrackUnbondGateway => 175_000u64.into(),
Operation::TrackUnbondMixnode => 175_000u64.into(),
Operation::WithdrawVestedCoins => 175_000u64.into(),
Operation::TrackUndelegation => 175_000u64.into(),
Operation::CreatePeriodicVestingAccount => 175_000u64.into(),
}
}
@@ -89,9 +125,8 @@ impl Operation {
Fee::from_amount_and_gas(fee, gas_limit)
}
pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
Self::determine_custom_fee(gas_price, gas_limit)
pub fn default_fee(&self, gas_price: &GasPrice) -> Fee {
Self::determine_custom_fee(gas_price, self.default_gas_limit())
}
}
@@ -0,0 +1,33 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmrs::tx;
pub mod gas_price;
pub mod helpers;
pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: f32 = 1.3;
#[derive(Debug, Clone)]
pub enum Fee {
Manual(tx::Fee),
Auto(Option<f32>),
}
impl From<tx::Fee> for Fee {
fn from(fee: tx::Fee) -> Self {
Fee::Manual(fee)
}
}
impl From<f32> for Fee {
fn from(multiplier: f32) -> Self {
Fee::Auto(Some(multiplier))
}
}
impl Default for Fee {
fn default() -> Self {
Fee::Auto(Some(DEFAULT_SIMULATED_GAS_MULTIPLIER))
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod vesting_query_client;
mod vesting_signing_client;
pub use vesting_query_client::VestingQueryClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -0,0 +1,176 @@
// Copyright 2021 - 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 cosmwasm_std::{Coin, Timestamp};
use vesting_contract::messages::QueryMsg as VestingQueryMsg;
#[async_trait]
pub trait VestingQueryClient {
async fn locked_coins(
&self,
address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError>;
async fn vesting_end_time(&self, vesting_account_address: &str)
-> Result<Timestamp, NymdError>;
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError>;
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn delegated_vesting(
&self,
block_time: Option<Timestamp>,
vesting_account_address: &str,
) -> Result<Coin, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn locked_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::LockedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::SpendableCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetVestedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetVestingCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError> {
let request = VestingQueryMsg::GetStartTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_end_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError> {
let request = VestingQueryMsg::GetEndTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetOriginalVesting {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedFree {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn delegated_vesting(
&self,
block_time: Option<Timestamp>,
vesting_account_address: &str,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedVesting {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
}
@@ -0,0 +1,290 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::fee::helpers::Operation;
use crate::nymd::{cosmwasm_coin_to_cosmos_coin, NymdClient};
use async_trait::async_trait;
use cosmwasm_std::Coin;
use mixnet_contract::{Gateway, IdentityKey, MixNode};
use vesting_contract::messages::ExecuteMsg as VestingExecuteMsg;
#[async_trait]
pub trait VestingSigningClient {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
pledge: Coin,
owner_signature: &str,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
pledge: Coin,
owner_signature: &str,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode(
&self,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode(
&self,
mix_identity: IdentityKey,
) -> Result<ExecuteResult, NymdError>;
async fn create_periodic_vesting_account(
&self,
address: &str,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
pledge: Coin,
owner_signature: &str,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondGateway);
let req = VestingExecuteMsg::BondGateway {
gateway,
owner_signature: owner_signature.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::BondGateway",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
)
.await
}
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondGateway);
let req = VestingExecuteMsg::UnbondGateway {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UnbondGateway",
vec![],
)
.await
}
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondGateway);
let req = VestingExecuteMsg::TrackUnbondGateway {
owner: owner.to_string(),
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUnbondGateway",
vec![],
)
.await
}
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
pledge: Coin,
owner_signature: &str,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondMixnode);
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::BondMixnode",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
)
.await
}
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondMixnode);
let req = VestingExecuteMsg::UnbondMixnode {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UnbondMixnode",
vec![],
)
.await
}
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondMixnode);
let req = VestingExecuteMsg::TrackUnbondMixnode {
owner: owner.to_string(),
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUnbondMixnode",
vec![],
)
.await
}
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::WithdrawVestedCoins);
let req = VestingExecuteMsg::WithdrawVestedCoins { amount };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::WithdrawVested",
vec![],
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUndelegation);
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUndelegation",
vec![],
)
.await
}
async fn vesting_delegate_to_mixnode(
&self,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::DelegateToMixnode);
let req = VestingExecuteMsg::DelegateToMixnode { mix_identity };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::DeledateToMixnode",
vec![cosmwasm_coin_to_cosmos_coin(amount)],
)
.await
}
async fn vesting_undelegate_from_mixnode(
&self,
mix_identity: IdentityKey,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UndelegateFromMixnode);
let req = VestingExecuteMsg::UndelegateFromMixnode { mix_identity };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UndelegateFromMixnode",
vec![],
)
.await
}
async fn create_periodic_vesting_account(
&self,
address: &str,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::CreatePeriodicVestingAccount);
let req = VestingExecuteMsg::CreateAccount {
address: address.to_string(),
start_time,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CreatePeriodicVestingAccount",
vec![cosmwasm_coin_to_cosmos_coin(amount)],
)
.await
}
}
@@ -207,9 +207,9 @@ mod tests {
fn generating_account_addresses() {
// test vectors produced from our js wallet
let mnemonic_address = vec![
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2"),
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn"),
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962")
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94"),
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv"),
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4")
];
for (mnemonic, address) in mnemonic_address.into_iter() {
+27 -18
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
pub use ed25519_dalek::SignatureError;
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
@@ -10,33 +10,33 @@ use rand::{CryptoRng, RngCore};
use std::fmt::{self, Display, Formatter};
#[derive(Debug)]
pub enum KeyRecoveryError {
pub enum Ed25519RecoveryError {
MalformedBytes(SignatureError),
MalformedString(bs58::decode::Error),
}
impl From<SignatureError> for KeyRecoveryError {
impl From<SignatureError> for Ed25519RecoveryError {
fn from(err: SignatureError) -> Self {
KeyRecoveryError::MalformedBytes(err)
Ed25519RecoveryError::MalformedBytes(err)
}
}
impl From<bs58::decode::Error> for KeyRecoveryError {
impl From<bs58::decode::Error> for Ed25519RecoveryError {
fn from(err: bs58::decode::Error) -> Self {
KeyRecoveryError::MalformedString(err)
Ed25519RecoveryError::MalformedString(err)
}
}
impl fmt::Display for KeyRecoveryError {
impl fmt::Display for Ed25519RecoveryError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
KeyRecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
Ed25519RecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
Ed25519RecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
}
}
}
impl std::error::Error for KeyRecoveryError {}
impl std::error::Error for Ed25519RecoveryError {}
/// Keypair for usage in ed25519 EdDSA.
pub struct KeyPair {
@@ -62,7 +62,7 @@ impl KeyPair {
&self.public_key
}
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, KeyRecoveryError> {
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Ok(KeyPair {
private_key: PrivateKey::from_bytes(priv_bytes)?,
public_key: PublicKey::from_bytes(pub_bytes)?,
@@ -115,7 +115,7 @@ impl PublicKey {
self.0.to_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Ok(PublicKey(ed25519_dalek::PublicKey::from_bytes(b)?))
}
@@ -123,7 +123,7 @@ impl PublicKey {
bs58::encode(self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, KeyRecoveryError> {
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
@@ -134,7 +134,7 @@ impl PublicKey {
}
impl PemStorableKey for PublicKey {
type Error = KeyRecoveryError;
type Error = Ed25519RecoveryError;
fn pem_type() -> &'static str {
"ED25519 PUBLIC KEY"
@@ -170,7 +170,7 @@ impl PrivateKey {
self.0.to_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Ok(PrivateKey(ed25519_dalek::SecretKey::from_bytes(b)?))
}
@@ -178,7 +178,7 @@ impl PrivateKey {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, KeyRecoveryError> {
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
@@ -192,7 +192,7 @@ impl PrivateKey {
}
impl PemStorableKey for PrivateKey {
type Error = KeyRecoveryError;
type Error = Ed25519RecoveryError;
fn pem_type() -> &'static str {
"ED25519 PRIVATE KEY"
@@ -211,11 +211,20 @@ impl PemStorableKey for PrivateKey {
pub struct Signature(ed25519_dalek::Signature);
impl Signature {
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError> {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Ok(Signature(ed25519_dalek::Signature::from_bytes(bytes)?))
}
}
+1 -3
View File
@@ -7,9 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" }
#cosmwasm-std = { version = "0.14.1" }
cosmwasm-std = "1.0.0-beta2"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
+52 -64
View File
@@ -1,3 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
@@ -7,51 +10,41 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct UnpackedDelegation<T> {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct Delegation {
pub owner: Addr,
pub node_identity: IdentityKey,
pub delegation_data: T,
}
impl<T> UnpackedDelegation<T> {
pub fn new(owner: Addr, node_identity: IdentityKey, delegation_data: T) -> Self {
UnpackedDelegation {
owner,
node_identity,
delegation_data,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct RawDelegationData {
pub amount: Uint128,
pub amount: Coin,
pub block_height: u64,
}
impl RawDelegationData {
pub fn new(amount: Uint128, block_height: u64) -> Self {
RawDelegationData {
amount,
block_height,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct Delegation {
owner: Addr,
amount: Coin,
block_height: u64,
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of anouther address
}
impl Delegation {
pub fn new(owner: Addr, amount: Coin, block_height: u64) -> Self {
pub fn new(
owner: Addr,
node_identity: IdentityKey,
amount: Coin,
block_height: u64,
proxy: Option<Addr>,
) -> Self {
Delegation {
owner,
node_identity,
amount,
block_height,
proxy,
}
}
// TODO: change that to use .joined_key() and return Vec<u8>
pub fn storage_key(&self) -> (IdentityKey, Addr) {
(self.node_identity(), self.owner())
}
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;
}
}
@@ -59,6 +52,10 @@ impl Delegation {
&self.amount
}
pub fn node_identity(&self) -> IdentityKey {
self.node_identity.clone()
}
pub fn owner(&self) -> Addr {
self.owner.clone()
}
@@ -72,65 +69,56 @@ impl Display for Delegation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{} {} delegated by {} at block {}",
self.amount.amount, self.amount.denom, self.owner, self.block_height
"{} delegated towards {} by {} at block {}",
self.amount, self.node_identity, self.owner, self.block_height
)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedMixDelegationsResponse {
pub node_identity: IdentityKey,
pub delegations: Vec<Delegation>,
pub start_next_after: Option<Addr>,
pub start_next_after: Option<String>,
}
impl PagedMixDelegationsResponse {
pub fn new(
node_identity: IdentityKey,
delegations: Vec<Delegation>,
start_next_after: Option<Addr>,
) -> Self {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
PagedMixDelegationsResponse {
node_identity,
delegations,
start_next_after,
start_next_after: start_next_after.map(|s| s.to_string()),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedReverseMixDelegationsResponse {
pub delegation_owner: Addr,
pub delegated_nodes: Vec<IdentityKey>,
pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedReverseMixDelegationsResponse {
pub fn new(
delegation_owner: Addr,
delegated_nodes: Vec<IdentityKey>,
start_next_after: Option<IdentityKey>,
) -> Self {
PagedReverseMixDelegationsResponse {
delegation_owner,
delegated_nodes,
impl PagedDelegatorDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<IdentityKey>) -> Self {
PagedDelegatorDelegationsResponse {
delegations,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse<T> {
pub delegations: Vec<UnpackedDelegation<T>>,
pub start_next_after: Option<Vec<u8>>,
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(IdentityKey, String)>,
}
impl<T> PagedAllDelegationsResponse<T> {
pub fn new(delegations: Vec<UnpackedDelegation<T>>, start_next_after: Option<Vec<u8>>) -> Self {
impl PagedAllDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(IdentityKey, Addr)>,
) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after,
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
}
}
}
+35 -19
View File
@@ -23,19 +23,27 @@ pub struct Gateway {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct GatewayBond {
pub bond_amount: Coin,
pub pledge_amount: Coin,
pub owner: Addr,
pub block_height: u64,
pub gateway: Gateway,
pub proxy: Option<Addr>,
}
impl GatewayBond {
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
pub fn new(
pledge_amount: Coin,
owner: Addr,
block_height: u64,
gateway: Gateway,
proxy: Option<Addr>,
) -> Self {
GatewayBond {
bond_amount,
pledge_amount,
owner,
block_height,
gateway,
proxy,
}
}
@@ -43,8 +51,8 @@ impl GatewayBond {
&self.gateway.identity_key
}
pub fn bond_amount(&self) -> Coin {
self.bond_amount.clone()
pub fn pledge_amount(&self) -> Coin {
self.pledge_amount.clone()
}
pub fn owner(&self) -> &Addr {
@@ -59,17 +67,17 @@ impl GatewayBond {
impl PartialOrd for GatewayBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.bond_amount.denom != other.bond_amount.denom {
if self.pledge_amount.denom != other.pledge_amount.denom {
return None;
}
// try to order by total bond
let bond_cmp = self
.bond_amount
// try to order by total pledge
let pledge_cmp = self
.pledge_amount
.amount
.partial_cmp(&other.bond_amount.amount)?;
if bond_cmp != Ordering::Equal {
return Some(bond_cmp);
.partial_cmp(&other.pledge_amount.amount)?;
if pledge_cmp != Ordering::Equal {
return Some(pledge_cmp);
}
// then check block height
@@ -94,7 +102,10 @@ impl Display for GatewayBond {
write!(
f,
"amount: {} {}, owner: {}, identity: {}",
self.bond_amount.amount, self.bond_amount.denom, self.owner, self.gateway.identity_key
self.pledge_amount.amount,
self.pledge_amount.denom,
self.owner,
self.gateway.identity_key
)
}
}
@@ -123,7 +134,7 @@ impl PagedGatewayResponse {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct GatewayOwnershipResponse {
pub address: Addr,
pub has_gateway: bool,
pub gateway: Option<GatewayBond>,
}
#[cfg(test)]
@@ -150,38 +161,43 @@ mod tests {
let _0foos = Coin::new(0, "foo");
let gate1 = GatewayBond {
bond_amount: _150foos.clone(),
pledge_amount: _150foos.clone(),
owner: Addr::unchecked("foo1"),
block_height: 100,
gateway: gateway_fixture(),
proxy: None,
};
let gate2 = GatewayBond {
bond_amount: _150foos,
pledge_amount: _150foos,
owner: Addr::unchecked("foo2"),
block_height: 120,
gateway: gateway_fixture(),
proxy: None,
};
let gate3 = GatewayBond {
bond_amount: _50foos,
pledge_amount: _50foos,
owner: Addr::unchecked("foo3"),
block_height: 120,
gateway: gateway_fixture(),
proxy: None,
};
let gate4 = GatewayBond {
bond_amount: _140foos,
pledge_amount: _140foos,
owner: Addr::unchecked("foo4"),
block_height: 120,
gateway: gateway_fixture(),
proxy: None,
};
let gate5 = GatewayBond {
bond_amount: _0foos,
pledge_amount: _0foos,
owner: Addr::unchecked("foo5"),
block_height: 120,
gateway: gateway_fixture(),
proxy: None,
};
// summary:
+3 -3
View File
@@ -12,10 +12,10 @@ pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
pub use cosmwasm_std::{Addr, Coin};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse,
PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation,
Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixDelegationsResponse,
};
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
pub use msg::*;
pub use types::*;
+99 -60
View File
@@ -5,7 +5,7 @@ use crate::{IdentityKey, SphinxKey};
use az::CheckedCast;
use cosmwasm_std::{coin, Addr, Coin, Uint128};
use log::error;
use network_defaults::{DEFAULT_OPERATOR_EPOCH_COST, DEFAULT_PROFIT_MARGIN};
use network_defaults::DEFAULT_OPERATOR_EPOCH_COST;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@@ -29,6 +29,7 @@ pub struct MixNode {
/// Base58 encoded ed25519 EdDSA public key.
pub identity_key: IdentityKey,
pub version: String,
pub profit_margin_percent: u8,
}
#[derive(
@@ -55,32 +56,68 @@ pub enum Layer {
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
period_reward_pool: Uint128,
k: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
reward_blockstamp: u64,
circulating_supply: Uint128,
uptime: Uint128,
sybil_resistance_percent: u8,
in_active_set: bool,
active_set_work_factor: u8,
}
impl NodeRewardParams {
#[allow(clippy::too_many_arguments)]
pub fn new(
period_reward_pool: u128,
k: u128,
rewarded_set_size: u128,
active_set_size: u128,
reward_blockstamp: u64,
circulating_supply: u128,
uptime: u128,
sybil_resistance_percent: u8,
in_active_set: bool,
active_set_work_factor: u8,
) -> NodeRewardParams {
NodeRewardParams {
period_reward_pool: Uint128(period_reward_pool),
k: Uint128(k),
period_reward_pool: Uint128::new(period_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
reward_blockstamp,
circulating_supply: Uint128(circulating_supply),
uptime: Uint128(uptime),
circulating_supply: Uint128::new(circulating_supply),
uptime: Uint128::new(uptime),
sybil_resistance_percent,
in_active_set,
active_set_work_factor,
}
}
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 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.rewarded_set_size - self.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.in_active_set
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
@@ -97,8 +134,8 @@ impl NodeRewardParams {
self.period_reward_pool.u128()
}
pub fn k(&self) -> u128 {
self.k.u128()
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
@@ -114,7 +151,7 @@ impl NodeRewardParams {
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.k.u128())
ONE / U128::from_num(self.rewarded_set_size.u128())
}
pub fn alpha(&self) -> U128 {
@@ -229,46 +266,45 @@ impl NodeRewardResult {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub bond_amount: Coin,
pub pledge_amount: Coin,
pub total_delegation: Coin,
pub owner: Addr,
pub layer: Layer,
pub block_height: u64,
pub mix_node: MixNode,
pub profit_margin_percent: Option<u8>,
pub proxy: Option<Addr>,
}
impl MixNodeBond {
pub fn new(
bond_amount: Coin,
pledge_amount: Coin,
owner: Addr,
layer: Layer,
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
proxy: Option<Addr>,
) -> Self {
MixNodeBond {
total_delegation: coin(0, &bond_amount.denom),
bond_amount,
total_delegation: coin(0, &pledge_amount.denom),
pledge_amount,
owner,
layer,
block_height,
mix_node,
profit_margin_percent,
proxy,
}
}
pub fn profit_margin(&self) -> U128 {
U128::from_num(self.profit_margin_percent.unwrap_or(DEFAULT_PROFIT_MARGIN))
/ U128::from_num(100)
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
}
pub fn identity(&self) -> &String {
&self.mix_node.identity_key
}
pub fn bond_amount(&self) -> Coin {
self.bond_amount.clone()
pub fn pledge_amount(&self) -> Coin {
self.pledge_amount.clone()
}
pub fn owner(&self) -> &Addr {
@@ -280,10 +316,10 @@ impl MixNodeBond {
}
pub fn total_stake(&self) -> Option<u128> {
if self.bond_amount.denom != self.total_delegation.denom {
if self.pledge_amount.denom != self.total_delegation.denom {
None
} else {
Some(self.bond_amount.amount.u128() + self.total_delegation.amount.u128())
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
}
}
@@ -291,38 +327,37 @@ impl MixNodeBond {
self.total_delegation.clone()
}
pub fn bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.bond_amount().amount.u128()) / U128::from_num(circulating_supply)
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
}
pub fn total_stake_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.bond_amount().amount.u128() + self.total_delegation().amount.u128())
pub fn total_bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128() + self.total_delegation().amount.u128())
/ U128::from_num(circulating_supply)
}
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
let bond_to_circulating_supply_ratio =
self.bond_to_circulating_supply(params.circulating_supply());
bond_to_circulating_supply_ratio.min(params.one_over_k())
let pledge_to_circulating_supply_ratio =
self.pledge_to_circulating_supply(params.circulating_supply());
pledge_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
let total_stake_to_circulating_supply_ratio =
self.total_stake_to_circulating_supply(params.circulating_supply());
total_stake_to_circulating_supply_ratio.min(params.one_over_k())
let total_bond_to_circulating_supply_ratio =
self.total_bond_to_circulating_supply(params.circulating_supply());
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
// Assuming uniform work distribution across the network this is one_over_k * k
let omega_k = ONE;
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let reward = params.performance()
* params.period_reward_pool()
* (sigma * omega_k + params.alpha() * lambda * sigma * params.k())
* (sigma * params.omega()
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
/ (ONE + params.alpha());
NodeRewardResult {
@@ -367,9 +402,9 @@ impl MixNodeBond {
}
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
if self.total_stake_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
{
self.total_stake_to_circulating_supply(params.circulating_supply())
self.total_bond_to_circulating_supply(params.circulating_supply())
} else {
params.one_over_k()
}
@@ -384,33 +419,33 @@ impl MixNodeBond {
impl PartialOrd for MixNodeBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.bond_amount.denom != self.total_delegation.denom {
if self.pledge_amount.denom != self.total_delegation.denom {
return None;
}
if other.bond_amount.denom != other.total_delegation.denom {
if other.pledge_amount.denom != other.total_delegation.denom {
return None;
}
if self.bond_amount.denom != other.bond_amount.denom {
if self.pledge_amount.denom != other.pledge_amount.denom {
return None;
}
// try to order by total bond + delegation
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
let total_cmp = (self.pledge_amount.amount + self.total_delegation.amount)
.partial_cmp(&(self.pledge_amount.amount + self.total_delegation.amount))?;
if total_cmp != Ordering::Equal {
return Some(total_cmp);
}
// then if those are equal, prefer higher bond over delegation
let bond_cmp = self
.bond_amount
let pledge_cmp = self
.pledge_amount
.amount
.partial_cmp(&other.bond_amount.amount)?;
if bond_cmp != Ordering::Equal {
return Some(bond_cmp);
.partial_cmp(&other.pledge_amount.amount)?;
if pledge_cmp != Ordering::Equal {
return Some(pledge_cmp);
}
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
@@ -449,7 +484,10 @@ impl Display for MixNodeBond {
write!(
f,
"amount: {} {}, owner: {}, identity: {}",
self.bond_amount.amount, self.bond_amount.denom, self.owner, self.mix_node.identity_key
self.pledge_amount.amount,
self.pledge_amount.denom,
self.owner,
self.mix_node.identity_key
)
}
}
@@ -478,7 +516,7 @@ impl PagedMixnodeResponse {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixOwnershipResponse {
pub address: Addr,
pub has_node: bool,
pub mixnode: Option<MixNodeBond>,
}
#[cfg(test)]
@@ -494,6 +532,7 @@ mod tests {
sphinx_key: "sphinxkey".to_string(),
identity_key: "identitykey".to_string(),
version: "0.11.0".to_string(),
profit_margin_percent: 10,
}
}
@@ -504,53 +543,53 @@ mod tests {
let _0foos = Coin::new(0, "foo");
let mix1 = MixNodeBond {
bond_amount: _150foos.clone(),
pledge_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo1"),
layer: Layer::One,
block_height: 100,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
proxy: None,
};
let mix2 = MixNodeBond {
bond_amount: _150foos.clone(),
pledge_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo2"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
proxy: None,
};
let mix3 = MixNodeBond {
bond_amount: _50foos,
pledge_amount: _50foos,
total_delegation: _150foos.clone(),
owner: Addr::unchecked("foo3"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
proxy: None,
};
let mix4 = MixNodeBond {
bond_amount: _150foos.clone(),
pledge_amount: _150foos.clone(),
total_delegation: _0foos.clone(),
owner: Addr::unchecked("foo4"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
proxy: None,
};
let mix5 = MixNodeBond {
bond_amount: _0foos,
pledge_amount: _0foos,
total_delegation: _150foos,
owner: Addr::unchecked("foo5"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
proxy: None,
};
// summary:
+54 -18
View File
@@ -2,27 +2,30 @@
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardParams;
use crate::StateParams;
use crate::ContractStateParams;
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Addr;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {}
pub struct InstantiateMsg {
pub rewarding_validator_address: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
BondMixnode {
mix_node: MixNode,
owner_signature: String,
},
UnbondMixnode {},
BondGateway {
gateway: Gateway,
owner_signature: String,
},
UnbondGateway {},
UpdateStateParams(StateParams),
UpdateContractStateParams(ContractStateParams),
DelegateToMixnode {
mix_identity: IdentityKey,
@@ -42,7 +45,7 @@ pub enum ExecuteMsg {
rewarding_interval_nonce: u32,
},
RewardMixnodeV2 {
RewardMixnode {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
@@ -50,17 +53,41 @@ pub enum ExecuteMsg {
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
},
RewardNextMixDelegators {
mix_identity: IdentityKey,
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
},
DelegateToMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
},
UndelegateFromMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
},
BondMixnodeOnBehalf {
mix_node: MixNode,
owner: String,
owner_signature: String,
},
UnbondMixnodeOnBehalf {
owner: String,
},
BondGatewayOnBehalf {
gateway: Gateway,
owner: String,
owner_signature: String,
},
UnbondGatewayOnBehalf {
owner: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetContractVersion {},
GetMixNodes {
limit: Option<u32>,
start_after: Option<IdentityKey>,
@@ -70,30 +97,39 @@ pub enum QueryMsg {
limit: Option<u32>,
},
OwnsMixnode {
address: Addr,
address: String,
},
OwnsGateway {
address: Addr,
address: String,
},
StateParams {},
CurrentRewardingInterval {},
GetMixDelegations {
// gets all [paged] delegations in the entire network
// TODO: do we even want that?
GetAllNetworkDelegations {
start_after: Option<(IdentityKey, String)>,
limit: Option<u32>,
},
// gets all [paged] delegations associated with particular mixnode
GetMixnodeDelegations {
mix_identity: IdentityKey,
start_after: Option<Addr>,
// since `start_after` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
start_after: Option<String>,
limit: Option<u32>,
},
GetAllMixDelegations {
start_after: Option<Vec<u8>>,
limit: Option<u32>,
},
GetReverseMixDelegations {
delegation_owner: Addr,
// gets all [paged] delegations associated with particular delegator
GetDelegatorDelegations {
// 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>,
limit: Option<u32>,
},
GetMixDelegation {
// gets delegation associated with particular mixnode, delegator pair
GetDelegationDetails {
mix_identity: IdentityKey,
address: Addr,
delegator: String,
},
LayerDistribution {},
GetRewardPool {},
+43 -8
View File
@@ -3,7 +3,7 @@
use crate::mixnode::DelegatorRewardParams;
use crate::Layer;
use cosmwasm_std::Uint128;
use cosmwasm_std::{Addr, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
@@ -35,13 +35,13 @@ pub struct RewardingIntervalResponse {
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct StateParams {
pub struct ContractStateParams {
// so currently epoch_length is being unused and validator API performs rewarding
// based on its own epoch length config value. I guess that's fine for time being
// however, in the future, the contract constant should be controlling it instead.
// pub epoch_length: u32, // length of a rewarding epoch/interval, expressed in hours
pub minimum_mixnode_bond: Uint128, // minimum amount a mixnode must bond to get into the system
pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system
pub 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
// number of mixnode that are going to get rewarded during current rewarding interval (k_m)
// based on overall demand for private bandwidth-
@@ -50,13 +50,22 @@ pub struct StateParams {
// 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 active_set_work_factor: u8,
}
impl Display for StateParams {
impl Display for ContractStateParams {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Contract state parameters: [ ")?;
write!(f, "minimum mixnode bond: {}; ", self.minimum_mixnode_bond)?;
write!(f, "minimum gateway bond: {}; ", self.minimum_gateway_bond)?;
write!(
f,
"minimum mixnode pledge: {}; ",
self.minimum_mixnode_pledge
)?;
write!(
f,
"minimum gateway pledge: {}; ",
self.minimum_gateway_pledge
)?;
write!(
f,
"mixnode rewarded set size: {}",
@@ -66,6 +75,11 @@ impl Display for StateParams {
f,
"mixnode active set size: {}",
self.mixnode_active_set_size
)?;
write!(
f,
"mixnode active set work factor: {}",
self.active_set_work_factor
)
}
}
@@ -81,7 +95,7 @@ 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,
pub next_start: String,
pub next_start: Addr,
pub rewarding_params: DelegatorRewardParams,
}
@@ -97,6 +111,27 @@ 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;
+10 -12
View File
@@ -39,13 +39,10 @@ impl ValidatorDetails {
}
pub fn default_validators() -> Vec<ValidatorDetails> {
vec![
ValidatorDetails::new(
"https://testnet-milhon-validator1.nymtech.net",
Some("https://testnet-milhon-validator1.nymtech.net/api"),
),
ValidatorDetails::new("https://testnet-milhon-validator2.nymtech.net", None),
]
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
Some("https://sandbox-validator.nymtech.net/api"),
)]
}
pub fn default_nymd_endpoints() -> Vec<Url> {
@@ -62,8 +59,9 @@ pub fn default_api_endpoints() -> Vec<Url> {
.collect()
}
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3";
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "nymt14hj2tavq8fpesdwxxcu44rty3hh90vhuysqrsr";
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = "nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "nymt17zujduc46wvkwvp6f062mm5xhr7jc3fewvqu9e";
/// How much bandwidth (in bytes) one token can buy
const BYTES_PER_TOKEN: u64 = 1024 * 1024 * 1024;
@@ -76,7 +74,7 @@ pub const BANDWIDTH_VALUE: u64 = TOKENS_TO_BURN * BYTES_PER_TOKEN;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("9fEE3e28c17dbB87310A51F13C4fbf4331A6f102");
pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
pub const COSMOS_CONTRACT_ADDRESS: &str = "punk1jld76tqw4wnpfenmay2xkv86nr3j0w426eka82";
pub const COSMOS_CONTRACT_ADDRESS: &str = "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
// Name of the event triggered by the eth contract. If the event name is changed,
// this would also need to be changed; It is currently tested against the json abi
pub const ETH_EVENT_NAME: &str = "Burned";
@@ -84,8 +82,8 @@ pub const ETH_BURN_FUNCTION_NAME: &str = "burnTokenForAccessCode";
/// Defaults Cosmos Hub/ATOM path
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
pub const BECH32_PREFIX: &str = "punk";
pub const DENOM: &str = "upunk";
pub const BECH32_PREFIX: &str = "nymt";
pub const DENOM: &str = "unymt";
// as set by validators in their configs
// (note that the 'amount' postfix is relevant here as the full gas price also includes denom)
pub const GAS_PRICE_AMOUNT: f64 = 0.025;
+2 -2
View File
@@ -22,9 +22,9 @@ const CLIENT_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH;
#[derive(Debug)]
pub enum RecipientFormattingError {
MalformedRecipientError,
MalformedIdentityError(identity::KeyRecoveryError),
MalformedIdentityError(identity::Ed25519RecoveryError),
MalformedEncryptionKeyError(encryption::KeyRecoveryError),
MalformedGatewayError(identity::KeyRecoveryError),
MalformedGatewayError(identity::Ed25519RecoveryError),
}
impl fmt::Display for RecipientFormattingError {
+4 -4
View File
@@ -13,7 +13,7 @@ use std::net::SocketAddr;
#[derive(Debug)]
pub enum GatewayConversionError {
InvalidIdentityKey(identity::KeyRecoveryError),
InvalidIdentityKey(identity::Ed25519RecoveryError),
InvalidSphinxKey(encryption::KeyRecoveryError),
InvalidAddress(String, io::Error),
InvalidStake,
@@ -26,8 +26,8 @@ impl From<encryption::KeyRecoveryError> for GatewayConversionError {
}
}
impl From<identity::KeyRecoveryError> for GatewayConversionError {
fn from(err: identity::KeyRecoveryError) -> Self {
impl From<identity::Ed25519RecoveryError> for GatewayConversionError {
fn from(err: identity::Ed25519RecoveryError) -> Self {
GatewayConversionError::InvalidIdentityKey(err)
}
}
@@ -125,7 +125,7 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
Ok(Node {
owner: bond.owner.as_str().to_owned(),
stake: bond.bond_amount.amount.into(),
stake: bond.pledge_amount.amount.into(),
location: bond.gateway.location.clone(),
host,
mix_host,
+4 -4
View File
@@ -13,7 +13,7 @@ use std::net::SocketAddr;
#[derive(Debug)]
pub enum MixnodeConversionError {
InvalidIdentityKey(identity::KeyRecoveryError),
InvalidIdentityKey(identity::Ed25519RecoveryError),
InvalidSphinxKey(encryption::KeyRecoveryError),
InvalidAddress(String, io::Error),
InvalidStake,
@@ -26,8 +26,8 @@ impl From<encryption::KeyRecoveryError> for MixnodeConversionError {
}
}
impl From<identity::KeyRecoveryError> for MixnodeConversionError {
fn from(err: identity::KeyRecoveryError) -> Self {
impl From<identity::Ed25519RecoveryError> for MixnodeConversionError {
fn from(err: identity::Ed25519RecoveryError) -> Self {
MixnodeConversionError::InvalidIdentityKey(err)
}
}
@@ -117,7 +117,7 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node {
Ok(Node {
owner: bond.owner.as_str().to_owned(),
stake: bond.bond_amount.amount.into(),
stake: bond.pledge_amount.amount.into(),
delegation: bond.total_delegation.amount.into(),
host,
mix_host,
+1641
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
[workspace]
members = ["erc20-bridge", "mixnet", "vesting"]
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'abort'
incremental = false
overflow-checks = true
+12 -8
View File
@@ -89,8 +89,9 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
@@ -101,16 +102,18 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -124,8 +127,9 @@ dependencies = [
[[package]]
name = "cosmwasm-storage"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f"
dependencies = [
"cosmwasm-std",
"serde",
+2 -16
View File
@@ -5,22 +5,9 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace] # adding a blank workspace to keep it out of the global workspace.
[lib]
crate-type = ["cdylib", "rlib"]
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'abort'
incremental = false
overflow-checks = true
[features]
# for more explicit tests, cargo test --features=backtraces
backtraces = ["cosmwasm-std/backtraces"]
@@ -31,9 +18,8 @@ config = { path = "../../common/config"}
[dependencies]
erc20-bridge-contract = { path = "../../common/erc20-bridge-contract" }
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
cosmwasm-storage = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
cosmwasm-std = "1.0.0-beta2"
cosmwasm-storage = "1.0.0-beta2"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
+1 -1
View File
@@ -68,7 +68,7 @@ pub mod tests {
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies(&[]);
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {};
let info = mock_info("creator", &[]);
+1 -1
View File
@@ -11,7 +11,7 @@ pub mod helpers {
use erc20_bridge_contract::payment::Payment;
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies(&[]);
let mut deps = mock_dependencies();
let msg = InstantiateMsg {};
let env = mock_env();
let info = mock_info("creator", &[]);
+703 -21
View File
@@ -2,6 +2,43 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"ctr",
"opaque-debug 0.3.0",
]
[[package]]
name = "anyhow"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e"
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "az"
version = "1.1.2"
@@ -14,6 +51,39 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330"
dependencies = [
"byte-tools",
"crypto-mac 0.7.0",
"digest 0.8.1",
"opaque-debug 0.2.3",
]
[[package]]
name = "blake3"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "526c210b4520e416420759af363083471656e819a75e831b8d2c9d5a584f2413"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"crypto-mac 0.11.1",
"digest 0.9.0",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
@@ -44,6 +114,18 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "bs58"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
[[package]]
name = "bumpalo"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "byte-tools"
version = "0.3.1"
@@ -62,12 +144,53 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862"
dependencies = [
"byteorder",
"keystream",
]
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time 0.1.43",
"winapi",
]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -86,10 +209,17 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cosmwasm-crypto"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
@@ -100,17 +230,18 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-schema"
version = "0.14.1"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04159eec9b583671db7923ff2b979736dfb8f0152347cab9fd02373c22e1a870"
checksum = "fe52b19d45fe3f8359db6cc24df44dbe05e5ae32539afc0f5b7f790a21aa6fd0"
dependencies = [
"schemars",
"serde_json",
@@ -118,8 +249,9 @@ dependencies = [
[[package]]
name = "cosmwasm-std"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -133,8 +265,9 @@ dependencies = [
[[package]]
name = "cosmwasm-storage"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f"
dependencies = [
"cosmwasm-std",
"serde",
@@ -155,6 +288,26 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto"
version = "0.1.0"
dependencies = [
"aes",
"blake3",
"bs58",
"cipher",
"digest 0.9.0",
"ed25519-dalek",
"generic-array 0.14.4",
"hkdf",
"hmac",
"log",
"nymsphinx-types",
"pemstore",
"rand",
"x25519-dalek",
]
[[package]]
name = "crypto-bigint"
version = "0.2.11"
@@ -163,10 +316,20 @@ checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
dependencies = [
"generic-array 0.14.4",
"rand_core 0.6.3",
"subtle",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "crypto-mac"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
dependencies = [
"generic-array 0.12.4",
"subtle 1.0.0",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
@@ -174,7 +337,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array 0.14.4",
"subtle",
"subtle 2.4.1",
]
[[package]]
name = "ctr"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher",
]
[[package]]
@@ -186,10 +358,21 @@ dependencies = [
"byteorder",
"digest 0.9.0",
"rand_core 0.5.1",
"subtle",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "cw-storage-plus"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3b8b840947313c1a1cccf056836cd79a60b4526bdcd6582995be37dc97be4ae"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "der"
version = "0.4.4"
@@ -235,6 +418,29 @@ dependencies = [
"signature",
]
[[package]]
name = "ed25519"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816"
dependencies = [
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand",
"serde",
"sha2",
"zeroize",
]
[[package]]
name = "ed25519-zebra"
version = "2.2.0"
@@ -261,10 +467,30 @@ dependencies = [
"group",
"pkcs8",
"rand_core 0.6.3",
"subtle",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "enum-iterator"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@@ -278,7 +504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
dependencies = [
"rand_core 0.6.3",
"subtle",
"subtle 2.4.1",
]
[[package]]
@@ -330,8 +556,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -345,6 +573,31 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "getset"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b328c01a4d71d2d8173daa93562a73ab0fe85616876f02500f53d82948c504"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "git2"
version = "0.13.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "845e007a28f1fcac035715988a234e8ec5458fd825b20a20c7dec74237ef341f"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]]
name = "group"
version = "0.10.0"
@@ -353,7 +606,7 @@ checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
dependencies = [
"ff",
"rand_core 0.6.3",
"subtle",
"subtle 2.4.1",
]
[[package]]
@@ -388,13 +641,23 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
[[package]]
name = "hkdf"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
dependencies = [
"digest 0.9.0",
"hmac",
]
[[package]]
name = "hmac"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac",
"crypto-mac 0.11.1",
"digest 0.9.0",
]
@@ -431,6 +694,24 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "k256"
version = "0.9.6"
@@ -443,12 +724,66 @@ dependencies = [
"sha2",
]
[[package]]
name = "keystream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
[[package]]
name = "libgit2-sys"
version = "0.12.25+1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68169ef08d6519b2fe133ecc637408d933c0174b23b80bb2f79828966fbaab"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]]
name = "libm"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
[[package]]
name = "libz-sys"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "lioness"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9"
dependencies = [
"arrayref",
"blake2",
"chacha",
"keystream",
]
[[package]]
name = "log"
version = "0.4.14"
@@ -489,15 +824,21 @@ dependencies = [
name = "mixnet-contracts"
version = "0.1.0"
dependencies = [
"bs58",
"config",
"cosmwasm-schema",
"cosmwasm-std",
"cosmwasm-storage",
"crypto",
"cw-storage-plus",
"fixed",
"mixnet-contract",
"rand",
"rand_chacha",
"schemars",
"serde",
"thiserror",
"vergen",
]
[[package]]
@@ -506,10 +847,43 @@ version = "0.1.0"
dependencies = [
"hex-literal",
"serde",
"time",
"time 0.3.4",
"url",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "nymsphinx-types"
version = "0.1.0"
dependencies = [
"sphinx",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "opaque-debug"
version = "0.2.3"
@@ -522,6 +896,24 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "pem"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
dependencies = [
"base64",
"once_cell",
"regex",
]
[[package]]
name = "pemstore"
version = "0.1.0"
dependencies = [
"pem",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@@ -581,6 +973,42 @@ dependencies = [
"spki",
]
[[package]]
name = "pkg-config"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.32"
@@ -605,6 +1033,29 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha",
"rand_core 0.5.1",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -623,6 +1074,55 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "rand_distr"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56"
dependencies = [
"num-traits",
"rand",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "ryu"
version = "1.0.5"
@@ -653,6 +1153,12 @@ dependencies = [
"syn",
]
[[package]]
name = "semver"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
[[package]]
name = "serde"
version = "1.0.130"
@@ -750,6 +1256,29 @@ dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "sphinx"
version = "0.1.0"
source = "git+https://github.com/nymtech/sphinx?rev=c494250f2a78bed33a618d470792418eee932859#c494250f2a78bed33a618d470792418eee932859"
dependencies = [
"aes",
"arrayref",
"blake2",
"bs58",
"byteorder",
"chacha",
"curve25519-dalek",
"digest 0.9.0",
"hkdf",
"hmac",
"lioness",
"log",
"rand",
"rand_distr",
"sha2",
"subtle 2.4.1",
]
[[package]]
name = "spki"
version = "0.4.1"
@@ -765,6 +1294,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
[[package]]
name = "subtle"
version = "2.4.1"
@@ -782,6 +1317,18 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.30"
@@ -802,6 +1349,16 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "time"
version = "0.3.4"
@@ -899,6 +1456,29 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "5.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e"
dependencies = [
"anyhow",
"cfg-if",
"chrono",
"enum-iterator",
"getset",
"git2",
"rustc_version",
"rustversion",
"thiserror",
]
[[package]]
name = "version_check"
version = "0.9.3"
@@ -918,7 +1498,109 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "zeroize"
version = "1.4.2"
name = "wasm-bindgen"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "x25519-dalek"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
dependencies = [
"curve25519-dalek",
"rand_core 0.5.1",
"zeroize",
]
[[package]]
name = "zeroize"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65f1a51723ec88c66d5d1fe80c841f17f63587d6691901d66be9bec6c3b51f73"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
+12 -20
View File
@@ -12,22 +12,9 @@ exclude = [
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace] # adding a blank workspace to keep it out of the global workspace.
[lib]
crate-type = ["cdylib", "rlib"]
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'abort'
incremental = false
overflow-checks = true
[features]
# for more explicit tests, cargo test --features=backtraces
backtraces = ["cosmwasm-std/backtraces"]
@@ -35,18 +22,23 @@ backtraces = ["cosmwasm-std/backtraces"]
[dependencies]
mixnet-contract = { path = "../../common/mixnet-contract" }
config = { path = "../../common/config"}
vesting-contract = { path = "../vesting" }
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
cosmwasm-storage = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
#cosmwasm-std = { version = "0.14.1", features = ["iterator"] }
#cosmwasm-storage = { version = "0.14.1", features = ["iterator"] }
cosmwasm-std = "1.0.0-beta2"
cosmwasm-storage = "1.0.0-beta2"
cw-storage-plus = "0.10.3"
bs58 = "0.4.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
[dev-dependencies]
cosmwasm-schema = { version = "0.14.0" }
cosmwasm-schema = "1.0.0-beta2"
fixed = "1.1"
rand_chacha = "0.2"
rand = "0.7"
crypto = { path = "../../common/crypto" }
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] }
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
}
-2
View File
@@ -5,7 +5,6 @@ extern crate mixnet_contract;
use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
use mixnet_contract::{ExecuteMsg, InstantiateMsg, MixNodeBond, QueryMsg};
use mixnet_contracts::state::State;
use std::env::current_dir;
use std::fs::create_dir_all;
@@ -18,6 +17,5 @@ fn main() {
export_schema(&schema_for!(InstantiateMsg), &out_dir);
export_schema(&schema_for!(ExecuteMsg), &out_dir);
export_schema(&schema_for!(QueryMsg), &out_dir);
export_schema(&schema_for!(State), &out_dir);
export_schema(&schema_for!(MixNodeBond), &out_dir);
}
+3 -3
View File
@@ -69,10 +69,10 @@
{
"type": "object",
"required": [
"update_state_params"
"update_contract_settings"
],
"properties": {
"update_state_params": {
"update_contract_settings": {
"$ref": "#/definitions/StateParams"
}
},
@@ -346,4 +346,4 @@
"type": "string"
}
}
}
}
+3 -3
View File
@@ -101,10 +101,10 @@
{
"type": "object",
"required": [
"state_params"
"contract_settings_params"
],
"properties": {
"state_params": {
"contract_settings_params": {
"type": "object"
}
},
@@ -321,4 +321,4 @@
"type": "string"
}
}
}
}
+167 -70
View File
@@ -1,22 +1,35 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::u128;
use crate::state::State;
use crate::storage::{config, layer_distribution};
use crate::{error::ContractError, queries, transactions};
use config::defaults::REWARDING_VALIDATOR_ADDRESS;
use crate::delegations::queries::query_all_network_delegations_paged;
use crate::delegations::queries::query_delegator_delegations_paged;
use crate::delegations::queries::query_mixnode_delegation;
use crate::delegations::queries::query_mixnode_delegations_paged;
use crate::error::ContractError;
use crate::gateways::queries::query_gateways_paged;
use crate::gateways::queries::query_owns_gateway;
use crate::mixnet_contract_settings::models::ContractState;
use crate::mixnet_contract_settings::queries::query_rewarding_interval;
use crate::mixnet_contract_settings::queries::{
query_contract_settings_params, query_contract_version,
};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::bonding_queries as mixnode_queries;
use crate::mixnodes::bonding_queries::query_mixnodes_paged;
use crate::mixnodes::layer_queries::query_layer_distribution;
use crate::rewards::queries::query_reward_pool;
use crate::rewards::queries::{query_circulating_supply, query_rewarding_status};
use crate::rewards::storage as rewards_storage;
use cosmwasm_std::{
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
};
use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams};
use mixnet_contract::{ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
/// Constant specifying minimum of coin required to bond a gateway
pub const INITIAL_GATEWAY_BOND: Uint128 = Uint128(100_000000);
pub const INITIAL_GATEWAY_PLEDGE: Uint128 = Uint128::new(100_000_000);
/// Constant specifying minimum of coin required to bond a mixnode
pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128(100_000000);
pub const INITIAL_MIXNODE_PLEDGE: Uint128 = Uint128::new(100_000_000);
pub const INITIAL_MIXNODE_REWARDED_SET_SIZE: u32 = 200;
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
@@ -24,19 +37,22 @@ pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
pub const INITIAL_REWARD_POOL: u128 = 250_000_000_000_000;
pub const EPOCH_REWARD_PERCENT: u8 = 2; // Used to calculate epoch reward pool
pub const DEFAULT_SYBIL_RESISTANCE_PERCENT: u8 = 30;
pub const DEFAULT_ACTIVE_SET_WORK_FACTOR: u8 = 10;
// We'll be assuming a few more things, profit margin and cost function. Since we don't have reliable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate epoch costs to Nyms. We'll also assume a cost of 40$ per epoch(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
pub const DEFAULT_COST_PER_EPOCH: u32 = 40_000_000;
fn default_initial_state(owner: Addr, env: Env) -> State {
State {
fn default_initial_state(
owner: Addr,
rewarding_validator_address: Addr,
env: Env,
) -> ContractState {
ContractState {
owner,
rewarding_validator_address: Addr::unchecked(REWARDING_VALIDATOR_ADDRESS), // we trust our hardcoded value
params: StateParams {
minimum_mixnode_bond: INITIAL_MIXNODE_BOND,
minimum_gateway_bond: INITIAL_GATEWAY_BOND,
rewarding_validator_address,
params: ContractStateParams {
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
minimum_gateway_pledge: INITIAL_GATEWAY_PLEDGE,
mixnode_rewarded_set_size: INITIAL_MIXNODE_REWARDED_SET_SIZE,
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
active_set_work_factor: DEFAULT_ACTIVE_SET_WORK_FACTOR,
},
rewarding_interval_starting_block: env.block.height,
latest_rewarding_interval_nonce: 0,
@@ -54,12 +70,15 @@ pub fn instantiate(
deps: DepsMut,
env: Env,
info: MessageInfo,
_msg: InstantiateMsg,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let state = default_initial_state(info.sender, env);
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
let state = default_initial_state(info.sender, rewarding_validator_address, env);
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
mixnet_params_storage::LAYERS.save(deps.storage, &Default::default())?;
rewards_storage::REWARD_POOL.save(deps.storage, &Uint128::new(INITIAL_REWARD_POOL))?;
config(deps.storage).save(&state)?;
layer_distribution(deps.storage).save(&Default::default())?;
Ok(Response::default())
}
@@ -72,22 +91,42 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::BondMixnode { mix_node } => {
transactions::try_add_mixnode(deps, env, info, mix_node)
ExecuteMsg::BondMixnode {
mix_node,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode(
deps,
env,
info,
mix_node,
owner_signature,
),
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(deps, info)
}
ExecuteMsg::UnbondMixnode {} => transactions::try_remove_mixnode(deps, info),
ExecuteMsg::BondGateway { gateway } => {
transactions::try_add_gateway(deps, env, info, gateway)
ExecuteMsg::BondGateway {
gateway,
owner_signature,
} => crate::gateways::transactions::try_add_gateway(
deps,
env,
info,
gateway,
owner_signature,
),
ExecuteMsg::UnbondGateway {} => {
crate::gateways::transactions::try_remove_gateway(deps, info)
}
ExecuteMsg::UnbondGateway {} => transactions::try_remove_gateway(deps, info),
ExecuteMsg::UpdateStateParams(params) => {
transactions::try_update_state_params(deps, info, params)
ExecuteMsg::UpdateContractStateParams(params) => {
crate::mixnet_contract_settings::transactions::try_update_contract_settings(
deps, info, params,
)
}
ExecuteMsg::RewardMixnodeV2 {
ExecuteMsg::RewardMixnode {
identity,
params,
rewarding_interval_nonce,
} => transactions::try_reward_mixnode_v2(
} => crate::rewards::transactions::try_reward_mixnode(
deps,
env,
info,
@@ -96,88 +135,143 @@ pub fn execute(
rewarding_interval_nonce,
),
ExecuteMsg::DelegateToMixnode { mix_identity } => {
transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
}
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity)
crate::delegations::transactions::try_remove_delegation_from_mixnode(
deps,
info,
mix_identity,
)
}
ExecuteMsg::BeginMixnodeRewarding {
rewarding_interval_nonce,
} => transactions::try_begin_mixnode_rewarding(deps, env, info, rewarding_interval_nonce),
} => crate::rewards::transactions::try_begin_mixnode_rewarding(
deps,
env,
info,
rewarding_interval_nonce,
),
ExecuteMsg::FinishMixnodeRewarding {
rewarding_interval_nonce,
} => transactions::try_finish_mixnode_rewarding(deps, info, rewarding_interval_nonce),
} => crate::rewards::transactions::try_finish_mixnode_rewarding(
deps,
info,
rewarding_interval_nonce,
),
ExecuteMsg::RewardNextMixDelegators {
mix_identity,
rewarding_interval_nonce,
} => transactions::try_reward_next_mixnode_delegators_v2(
} => crate::rewards::transactions::try_reward_next_mixnode_delegators(
deps,
info,
mix_identity,
rewarding_interval_nonce,
),
ExecuteMsg::DelegateToMixnodeOnBehalf {
mix_identity,
delegate,
} => crate::delegations::transactions::try_delegate_to_mixnode_on_behalf(
deps,
env,
info,
mix_identity,
delegate,
),
ExecuteMsg::UndelegateFromMixnodeOnBehalf {
mix_identity,
delegate,
} => crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
deps,
info,
mix_identity,
delegate,
),
ExecuteMsg::BondMixnodeOnBehalf {
mix_node,
owner,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
deps,
env,
info,
mix_node,
owner,
owner_signature,
),
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, info, owner)
}
ExecuteMsg::BondGatewayOnBehalf {
gateway,
owner,
owner_signature,
} => crate::gateways::transactions::try_add_gateway_on_behalf(
deps,
env,
info,
gateway,
owner,
owner_signature,
),
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
}
}
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetContractVersion {} => to_binary(&query_contract_version()),
QueryMsg::GetMixNodes { start_after, limit } => {
to_binary(&queries::query_mixnodes_paged(deps, start_after, limit)?)
to_binary(&query_mixnodes_paged(deps, start_after, limit)?)
}
QueryMsg::GetGateways { limit, start_after } => {
to_binary(&queries::query_gateways_paged(deps, start_after, limit)?)
to_binary(&query_gateways_paged(deps, start_after, limit)?)
}
QueryMsg::OwnsMixnode { address } => {
to_binary(&queries::query_owns_mixnode(deps, address)?)
to_binary(&mixnode_queries::query_owns_mixnode(deps, address)?)
}
QueryMsg::OwnsGateway { address } => {
to_binary(&queries::query_owns_gateway(deps, address)?)
}
QueryMsg::StateParams {} => to_binary(&queries::query_state_params(deps)),
QueryMsg::CurrentRewardingInterval {} => {
to_binary(&queries::query_rewarding_interval(deps))
}
QueryMsg::LayerDistribution {} => to_binary(&queries::query_layer_distribution(deps)),
QueryMsg::GetMixDelegations {
QueryMsg::OwnsGateway { address } => to_binary(&query_owns_gateway(deps, address)?),
QueryMsg::StateParams {} => to_binary(&query_contract_settings_params(deps)?),
QueryMsg::CurrentRewardingInterval {} => to_binary(&query_rewarding_interval(deps)?),
QueryMsg::LayerDistribution {} => to_binary(&query_layer_distribution(deps)?),
QueryMsg::GetMixnodeDelegations {
mix_identity,
start_after,
limit,
} => to_binary(&queries::query_mixnode_delegations_paged(
} => to_binary(&query_mixnode_delegations_paged(
deps,
mix_identity,
start_after,
limit,
)?),
QueryMsg::GetAllMixDelegations { start_after, limit } => to_binary(
&queries::query_all_mixnode_delegations_paged(deps, start_after, limit)?,
QueryMsg::GetAllNetworkDelegations { start_after, limit } => to_binary(
&query_all_network_delegations_paged(deps, start_after, limit)?,
),
QueryMsg::GetReverseMixDelegations {
delegation_owner,
QueryMsg::GetDelegatorDelegations {
delegator: delegation_owner,
start_after,
limit,
} => to_binary(&queries::query_reverse_mixnode_delegations_paged(
} => to_binary(&query_delegator_delegations_paged(
deps,
delegation_owner,
start_after,
limit,
)?),
QueryMsg::GetMixDelegation {
QueryMsg::GetDelegationDetails {
mix_identity,
address,
} => to_binary(&queries::query_mixnode_delegation(
deps,
mix_identity,
address,
)?),
QueryMsg::GetRewardPool {} => to_binary(&queries::query_reward_pool(deps)),
QueryMsg::GetCirculatingSupply {} => to_binary(&queries::query_circulating_supply(deps)),
delegator,
} => to_binary(&query_mixnode_delegation(deps, mix_identity, delegator)?),
QueryMsg::GetRewardPool {} => to_binary(&query_reward_pool(deps)?),
QueryMsg::GetCirculatingSupply {} => to_binary(&query_circulating_supply(deps)?),
QueryMsg::GetEpochRewardPercent {} => to_binary(&EPOCH_REWARD_PERCENT),
QueryMsg::GetSybilResistancePercent {} => to_binary(&DEFAULT_SYBIL_RESISTANCE_PERCENT),
QueryMsg::GetRewardingStatus {
mix_identity,
rewarding_interval_nonce,
} => to_binary(&queries::query_rewarding_status(
} => to_binary(&query_rewarding_status(
deps,
mix_identity,
rewarding_interval_nonce,
@@ -188,13 +282,14 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
}
#[entry_point]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
todo!("ACTIVE_STATE_WORK_FACTOR to State");
// Ok(Default::default())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::support::tests::helpers::*;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
@@ -202,9 +297,11 @@ pub mod tests {
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies(&[]);
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {};
let msg = InstantiateMsg {
rewarding_validator_address: config::defaults::REWARDING_VALIDATOR_ADDRESS.to_string(),
};
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
@@ -226,7 +323,7 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, DENOM),
query_contract_balance(env.contract.address, deps)
test_helpers::query_contract_balance(env.contract.address, deps)
);
}
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod queries;
pub(crate) mod storage;
pub(crate) mod transactions;
+649
View File
@@ -0,0 +1,649 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use cosmwasm_std::Deps;
use cosmwasm_std::Order;
use cosmwasm_std::StdResult;
use cw_storage_plus::{Bound, PrimaryKey};
use mixnet_contract::PagedAllDelegationsResponse;
use mixnet_contract::PagedDelegatorDelegationsResponse;
use mixnet_contract::PagedMixDelegationsResponse;
use mixnet_contract::{Delegation, IdentityKey};
pub(crate) fn query_all_network_delegations_paged(
deps: Deps,
start_after: Option<(IdentityKey, String)>,
limit: Option<u32>,
) -> StdResult<PagedAllDelegationsResponse> {
let limit = limit
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = start_after
.map(|start| start.joined_key())
.map(Bound::exclusive);
let delegations = storage::delegations()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|record| record.map(|r| r.1))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = delegations
.last()
.map(|delegation| delegation.storage_key());
Ok(PagedAllDelegationsResponse::new(
delegations,
start_next_after,
))
}
pub(crate) fn query_delegator_delegations_paged(
deps: Deps,
delegation_owner: String,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> StdResult<PagedDelegatorDelegationsResponse> {
let validated_owner = deps.api.addr_validate(&delegation_owner)?;
let limit = limit
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = start_after
.map(|mix_identity| Bound::Exclusive((mix_identity, validated_owner.clone()).joined_key()));
let delegations = storage::delegations()
.idx
.owner
.prefix(validated_owner)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|record| record.map(|r| r.1))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = delegations
.last()
.map(|delegation| delegation.node_identity());
Ok(PagedDelegatorDelegationsResponse::new(
delegations,
start_next_after,
))
}
// queries for delegation value of given address for particular node
pub(crate) fn query_mixnode_delegation(
deps: Deps,
mix_identity: IdentityKey,
delegator: String,
) -> Result<Delegation, ContractError> {
let validated_delegator = deps.api.addr_validate(&delegator)?;
let storage_key = (mix_identity.clone(), validated_delegator.clone()).joined_key();
storage::delegations()
.may_load(deps.storage, storage_key)?
.ok_or(ContractError::NoMixnodeDelegationFound {
identity: mix_identity,
address: validated_delegator,
})
}
pub(crate) fn query_mixnode_delegations_paged(
deps: Deps,
mix_identity: IdentityKey,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedMixDelegationsResponse> {
let limit = limit
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = start_after
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?
.map(|addr| Bound::Exclusive((mix_identity.clone(), addr).joined_key()));
let delegations = storage::delegations()
.idx
.mixnode
.prefix(mix_identity)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|record| record.map(|r| r.1))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = delegations.last().map(|delegation| delegation.owner());
Ok(PagedMixDelegationsResponse::new(
delegations,
start_next_after,
))
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::{coin, Addr, Storage};
pub fn store_n_mix_delegations(n: u32, storage: &mut dyn Storage, node_identity: &str) {
for i in 0..n {
let address = format!("address{}", i);
test_helpers::save_dummy_delegation(storage, node_identity, address);
}
}
#[cfg(test)]
mod querying_for_mixnode_delegations_paged {
use super::*;
use mixnet_contract::IdentityKey;
#[test]
fn retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(100, &mut deps.storage, &node_identity);
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity,
None,
Option::from(limit),
)
.unwrap();
assert_eq!(limit, page1.delegations.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(
storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&node_identity,
);
// query without explicitly setting a limit
let page1 =
query_mixnode_delegations_paged(deps.as_ref(), node_identity, None, None).unwrap();
assert_eq!(
storage::DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegations.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(
storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&node_identity,
);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * storage::DELEGATION_PAGE_DEFAULT_LIMIT;
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity,
None,
Option::from(crazy_limit),
)
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = storage::DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegations.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "100");
let per_page = 2;
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
None,
Option::from(per_page),
)
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegations.len());
// save another
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "200");
// page1 should have 2 results on it
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "300");
// page1 still has 2 results
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
assert_eq!("200".to_string(), page1.start_next_after.unwrap());
// retrieving the next page should start after the last key on this page
let start_after = "200".to_string();
let page2 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400");
let start_after = "200".to_string();
let page2 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegations.len());
}
}
#[cfg(test)]
mod querying_for_all_mixnode_delegations_paged {
use super::*;
use crate::support::tests::test_helpers;
use mixnet_contract::IdentityKey;
#[test]
fn retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(100, &mut deps.storage, &node_identity);
let page1 =
query_all_network_delegations_paged(deps.as_ref(), None, Option::from(limit))
.unwrap();
assert_eq!(limit, page1.delegations.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(
storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&node_identity,
);
// query without explicitly setting a limit
let page1 = query_all_network_delegations_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(
storage::DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegations.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(
storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&node_identity,
);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * storage::DELEGATION_PAGE_DEFAULT_LIMIT;
let page1 =
query_all_network_delegations_paged(deps.as_ref(), None, Option::from(crazy_limit))
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = storage::DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegations.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "100");
let per_page = 2;
let page1 =
query_all_network_delegations_paged(deps.as_ref(), None, Option::from(per_page))
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegations.len());
// save another
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "200");
// page1 should have 2 results on it
let page1 =
query_all_network_delegations_paged(deps.as_ref(), None, Option::from(per_page))
.unwrap();
assert_eq!(2, page1.delegations.len());
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "300");
// page1 still has 2 results
let page1 =
query_all_network_delegations_paged(deps.as_ref(), None, Option::from(per_page))
.unwrap();
assert_eq!(2, page1.delegations.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_all_network_delegations_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400");
let page2 = query_all_network_delegations_paged(
deps.as_ref(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegations.len());
}
}
#[test]
fn mix_deletion_query_returns_current_delegation_value() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
let delegation = Delegation::new(
delegation_owner.clone(),
node_identity.clone(),
coin(1234, DENOM),
1234,
None,
);
storage::delegations()
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Ok(delegation),
query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner.to_string())
)
}
#[test]
fn mix_deletion_query_returns_error_if_delegation_doesnt_exist() {
let mut deps = test_helpers::init_contract();
let node_identity1: IdentityKey = "foo1".into();
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner1 = Addr::unchecked("bar");
let delegation_owner2 = Addr::unchecked("bar2");
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: delegation_owner1.clone(),
}),
query_mixnode_delegation(
deps.as_ref(),
node_identity1.clone(),
delegation_owner1.to_string()
)
);
// add delegation from a different address
let delegation = Delegation::new(
delegation_owner2.clone(),
node_identity1.clone(),
coin(1234, DENOM),
1234,
None,
);
storage::delegations()
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: delegation_owner1.clone(),
}),
query_mixnode_delegation(
deps.as_ref(),
node_identity1.clone(),
delegation_owner1.to_string()
)
);
// add delegation for a different node
let delegation = Delegation::new(
delegation_owner1.clone(),
node_identity2.clone(),
coin(1234, DENOM),
1234,
None,
);
storage::delegations()
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: Addr::unchecked(delegation_owner1.clone())
}),
query_mixnode_delegation(
deps.as_ref(),
node_identity1.clone(),
delegation_owner1.to_string()
)
)
}
#[cfg(test)]
mod querying_for_reverse_mixnode_delegations_paged {
use super::*;
fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &str) {
for i in 0..n {
let node_identity = format!("node{}", i);
test_helpers::save_dummy_delegation(storage, node_identity, delegation_owner);
}
}
#[test]
fn retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
let delegation_owner = "foo".to_string();
store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner);
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
Option::from(limit),
)
.unwrap();
assert_eq!(limit, page1.delegations.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
let delegation_owner = "foo".to_string();
store_n_reverse_delegations(
storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&delegation_owner,
);
// query without explicitly setting a limit
let page1 =
query_delegator_delegations_paged(deps.as_ref(), delegation_owner, None, None)
.unwrap();
assert_eq!(
storage::DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegations.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
let delegation_owner = "foo".to_string();
store_n_reverse_delegations(
storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&delegation_owner,
);
// query with a crazy high limit in an attempt to use too many resources
let crazy_limit = 1000 * storage::DELEGATION_PAGE_DEFAULT_LIMIT;
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
Option::from(crazy_limit),
)
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = storage::DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegations.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = test_helpers::init_contract();
let delegation_owner = "bar".to_string();
test_helpers::save_dummy_delegation(&mut deps.storage, "100", &delegation_owner);
let per_page = 2;
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegations.len());
// save another
test_helpers::save_dummy_delegation(&mut deps.storage, "200", &delegation_owner);
// page1 should have 2 results on it
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
test_helpers::save_dummy_delegation(&mut deps.storage, "300", &delegation_owner);
// page1 still has 2 results
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
// retrieving the next page should start after the last key on this page
let start_after: IdentityKey = page1.start_next_after.unwrap();
let page2 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, "400", &delegation_owner);
let start_after = String::from("2");
let page2 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegations.len());
}
}
}
+180
View File
@@ -0,0 +1,180 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
use mixnet_contract::{Addr, Delegation, IdentityKey};
// storage prefixes
const DELEGATION_PK_NAMESPACE: &str = "dl";
const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
// paged retrieval limits for all queries and transactions
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 500;
pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 250;
// It's a composite key on node's identity and delegator address
type PrimaryKey = Vec<u8>;
pub(crate) struct DelegationIndex<'a> {
pub(crate) owner: MultiIndex<'a, (Addr, PrimaryKey), Delegation>,
pub(crate) mixnode: MultiIndex<'a, (IdentityKey, PrimaryKey), Delegation>,
}
impl<'a> IndexList<Delegation> for DelegationIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<Delegation>> + '_> {
let v: Vec<&dyn Index<Delegation>> = vec![&self.owner, &self.mixnode];
Box::new(v.into_iter())
}
}
// I was really going back and forth about the data stored on the disk vs primary key duplication.
// It was basically between convenience and bloat, but in the end I decided the convenience wins.
//
// Basically I had 2 approaches. a) store delegator address and mixnode identity only as primary key of delegation or
// b) store it both as primary key AND inside delegation data.
// For the longest time I was in favour of a), since that removed any data duplication. However...,
// that also required that during index creation I recovered delegator address and mixnode identity
// from the Vec<u8>. That doesn't sound that terrible. However, even though I'm 99.99% certain that
// conversion would be impossible to fail, I'd still have to call an `unwrap` here due to required
// type signature and I didn't feel super comfortable doing that in our smart contract...
// So to get rid of this uncertainty I went with the b) approach. Even though each stored delegation
// takes over ~250B (since the key has to be duplicated), in the grand blockchain scheme of things
// it's not that terrible. Say we had 100_000_000 delegations -> that's still only 25GB of data
// and as a nice by-product it cleans up code a little bit by only having a single Delegation type.
pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> {
let indexes = DelegationIndex {
owner: MultiIndex::new(
|d, pk| (d.owner.clone(), pk),
DELEGATION_PK_NAMESPACE,
DELEGATION_OWNER_IDX_NAMESPACE,
),
mixnode: MultiIndex::new(
|d, pk| (d.node_identity.clone(), pk),
DELEGATION_PK_NAMESPACE,
DELEGATION_MIXNODE_IDX_NAMESPACE,
),
};
IndexedMap::new(DELEGATION_PK_NAMESPACE, indexes)
}
#[cfg(test)]
mod tests {
use crate::delegations::storage;
use cosmwasm_std::Addr;
use mixnet_contract::IdentityKey;
#[cfg(test)]
mod reverse_mix_delegations {
use super::*;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{coin, Order};
use cw_storage_plus::PrimaryKey;
use mixnet_contract::Delegation;
#[test]
fn reverse_mix_delegation_exists() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
let delegation = coin(12345, DENOM);
let dummy_data = Delegation::new(
delegation_owner.clone(),
node_identity.clone(),
delegation,
mock_env().block.height,
None,
);
storage::delegations()
.save(
&mut deps.storage,
(node_identity.clone(), delegation_owner.clone()).joined_key(),
&dummy_data,
)
.unwrap();
let read = storage::delegations()
.idx
.owner
.prefix(delegation_owner)
.range(&deps.storage, None, None, Order::Ascending)
.map(|record| record.unwrap().1)
.collect::<Vec<_>>();
assert_eq!(1, read.len());
assert_eq!(dummy_data, read[0]);
}
#[test]
fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() {
let mut deps = test_helpers::init_contract();
let node_identity1: IdentityKey = "foo1".into();
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner1 = Addr::unchecked("bar");
let delegation_owner2 = Addr::unchecked("bar2");
let delegation = coin(12345, DENOM);
assert!(test_helpers::read_delegation(
deps.as_ref().storage,
&node_identity1,
&delegation_owner1
)
.is_none());
// add delegation for a different node
let dummy_data = Delegation::new(
delegation_owner1.clone(),
node_identity2.clone(),
delegation.clone(),
mock_env().block.height,
None,
);
storage::delegations()
.save(
&mut deps.storage,
(node_identity1.clone(), delegation_owner1.clone()).joined_key(),
&dummy_data,
)
.unwrap();
storage::delegations()
.idx
.owner
.prefix(delegation_owner1.clone())
.range(&deps.storage, None, None, Order::Ascending)
.map(|record| record.unwrap().1)
.for_each(|delegation| assert_ne!(delegation.node_identity, node_identity1));
// add delegation from a different owner
let dummy_data = Delegation::new(
delegation_owner2.clone(),
node_identity1.clone(),
delegation.clone(),
mock_env().block.height,
None,
);
storage::delegations()
.save(
&mut deps.storage,
(node_identity1.clone(), delegation_owner2.clone()).joined_key(),
&dummy_data,
)
.unwrap();
storage::delegations()
.idx
.owner
.prefix(delegation_owner1.clone())
.range(&deps.storage, None, None, Order::Ascending)
.map(|record| record.unwrap().1)
.for_each(|delegation| assert_ne!(delegation.node_identity, node_identity1));
}
}
}
@@ -0,0 +1,855 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::helpers::generate_storage_key;
use config::defaults::DENOM;
use cosmwasm_std::{coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
use cw_storage_plus::PrimaryKey;
use mixnet_contract::Delegation;
use mixnet_contract::IdentityKey;
use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg;
fn validate_delegation_stake(mut delegation: Vec<Coin>) -> Result<Coin, ContractError> {
// check if anything was put as delegation
if delegation.is_empty() {
return Err(ContractError::EmptyDelegation);
}
if delegation.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
// check that the denomination is correct
if delegation[0].denom != DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have provided a non-zero amount in the delegation
if delegation[0].amount.is_zero() {
return Err(ContractError::EmptyDelegation);
}
Ok(delegation.pop().unwrap())
}
pub(crate) fn try_delegate_to_mixnode(
deps: DepsMut,
env: Env,
info: MessageInfo,
mix_identity: IdentityKey,
) -> Result<Response, ContractError> {
// check if the delegation contains any funds of the appropriate denomination
let amount = validate_delegation_stake(info.funds)?;
_try_delegate_to_mixnode(deps, env, mix_identity, info.sender.as_str(), amount, None)
}
pub(crate) fn try_delegate_to_mixnode_on_behalf(
deps: DepsMut,
env: Env,
info: MessageInfo,
mix_identity: IdentityKey,
delegate: String,
) -> Result<Response, ContractError> {
// check if the delegation contains any funds of the appropriate denomination
let amount = validate_delegation_stake(info.funds)?;
_try_delegate_to_mixnode(deps, env, mix_identity, &delegate, amount, None)
}
pub(crate) fn _try_delegate_to_mixnode(
deps: DepsMut,
env: Env,
mix_identity: IdentityKey,
delegate: &str,
amount: Coin,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let delegate = deps.api.addr_validate(delegate)?;
// check if the target node actually exists
if mixnodes_storage::mixnodes()
.may_load(deps.storage, &mix_identity)?
.is_none()
{
return Err(ContractError::MixNodeBondNotFound {
identity: mix_identity,
});
}
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
let storage_key = (mix_identity.clone(), maybe_proxy_storage).joined_key();
// update total_delegation of this node
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
deps.storage,
&mix_identity,
|total_delegation| {
// since we know that the target node exists and because the total_delegation bucket
// entry is created whenever the node itself is added, the unwrap here is fine
// as the entry MUST exist
Ok(total_delegation.unwrap() + amount.amount)
},
)?;
// update [or create new] delegation of this delegator
storage::delegations().update::<_, ContractError>(
deps.storage,
storage_key,
|existing_delegation| {
Ok(match existing_delegation {
Some(mut existing_delegation) => {
existing_delegation.increment_amount(amount.amount, Some(env.block.height));
existing_delegation
}
None => Delegation::new(
delegate.to_owned(),
mix_identity,
amount,
env.block.height,
proxy,
),
})
},
)?;
Ok(Response::default())
}
pub(crate) fn try_remove_delegation_from_mixnode(
deps: DepsMut,
info: MessageInfo,
mix_identity: IdentityKey,
) -> Result<Response, ContractError> {
_try_remove_delegation_from_mixnode(deps, mix_identity, info.sender.as_str(), None)
}
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
deps: DepsMut,
info: MessageInfo,
mix_identity: IdentityKey,
delegate: String,
) -> Result<Response, ContractError> {
_try_remove_delegation_from_mixnode(deps, mix_identity, &delegate, Some(info.sender))
}
pub(crate) fn _try_remove_delegation_from_mixnode(
deps: DepsMut,
mix_identity: IdentityKey,
delegate: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let delegate = deps.api.addr_validate(delegate)?;
let delegation_map = storage::delegations();
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
let storage_key = (mix_identity.clone(), maybe_proxy_storage).joined_key();
match delegation_map.may_load(deps.storage, storage_key.clone())? {
None => Err(ContractError::NoMixnodeDelegationFound {
identity: mix_identity,
address: delegate,
}),
Some(old_delegation) => {
// remove all delegation associated with this delegator
if proxy != old_delegation.proxy {
return Err(ContractError::ProxyMismatch {
existing: old_delegation
.proxy
.map_or_else(|| "None".to_string(), |a| a.to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.to_string()),
});
}
// remove old delegation data from the store
// note for reviewers: I'm using `replace` as `remove` is just `may_load` followed by `replace`
// and we've already performed `may_load` and have access to pre-existing data
delegation_map.replace(deps.storage, storage_key, None, Some(&old_delegation))?;
// send delegated funds back to the delegation owner
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&delegate).to_string(),
amount: coins(
old_delegation.amount.amount.u128(),
old_delegation.amount.denom.clone(),
),
};
// update total_delegation of this node
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
deps.storage,
&mix_identity,
|total_delegation| {
// the first unwrap is fine because the delegation information MUST exist, otherwise we would
// have never gotten here in the first place
// the second unwrap is also fine because we should NEVER underflow here,
// if we do, it means we have some serious error in our logic
Ok(total_delegation
.unwrap()
.checked_sub(old_delegation.amount.amount)
.unwrap())
},
)?;
let mut response = Response::new().add_message(return_tokens);
if let Some(proxy) = &proxy {
let msg = Some(VestingContractExecuteMsg::TrackUndelegation {
owner: delegate.as_str().to_string(),
mix_identity: mix_identity.clone(),
amount: old_delegation.amount,
});
let track_undelegation_msg = wasm_execute(proxy, &msg, coins(0, DENOM))?;
response = response.add_message(track_undelegation_msg);
}
Ok(response)
}
}
}
#[cfg(test)]
mod tests {
use cosmwasm_std::coins;
use crate::support::tests::test_helpers;
use super::storage;
use super::*;
#[cfg(test)]
mod delegation_stake_validation {
use cosmwasm_std::coin;
use super::*;
#[test]
fn stake_cant_be_empty() {
assert_eq!(
Err(ContractError::EmptyDelegation),
validate_delegation_stake(vec![])
)
}
#[test]
fn stake_must_have_single_coin_type() {
assert_eq!(
Err(ContractError::MultipleDenoms),
validate_delegation_stake(vec![
coin(123, DENOM),
coin(123, "BTC"),
coin(123, "DOGE")
])
)
}
#[test]
fn stake_coin_must_be_of_correct_type() {
assert_eq!(
Err(ContractError::WrongDenom {}),
validate_delegation_stake(coins(123, "DOGE"))
)
}
#[test]
fn stake_coin_must_have_value_greater_than_zero() {
assert_eq!(
Err(ContractError::EmptyDelegation),
validate_delegation_stake(coins(0, DENOM))
)
}
#[test]
fn stake_can_have_any_positive_value() {
// this might change in the future, but right now an arbitrary (positive) value can be delegated
assert!(validate_delegation_stake(coins(1, DENOM)).is_ok());
assert!(validate_delegation_stake(coins(123, DENOM)).is_ok());
assert!(validate_delegation_stake(coins(10000000000, DENOM)).is_ok());
}
}
#[cfg(test)]
mod mix_stake_delegation {
use super::*;
use crate::mixnodes::transactions::try_remove_mixnode;
use crate::support::tests::test_helpers::good_mixnode_bond;
use cosmwasm_std::coin;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Addr;
#[test]
fn fails_if_node_doesnt_exist() {
let mut deps = test_helpers::init_contract();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: "non-existent-mix-identity".into()
}),
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("sender", &coins(123, DENOM)),
"non-existent-mix-identity".into(),
)
);
}
#[test]
fn succeeds_for_existing_node() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
let delegation = coin(123, DENOM);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.is_ok());
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
delegation.clone(),
mock_env().block.height,
None,
);
assert_eq!(
expected,
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
);
// node's "total_delegation" is increased
assert_eq!(
delegation.amount,
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
#[test]
fn fails_if_node_unbonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: identity.clone()
}),
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(123, DENOM)),
identity,
)
);
}
#[test]
fn succeeds_if_node_rebonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation = coin(123, DENOM);
let delegation_owner = Addr::unchecked("sender");
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.is_ok());
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
delegation.clone(),
mock_env().block.height,
None,
);
assert_eq!(
expected,
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
);
// node's "total_delegation" is increased
assert_eq!(
delegation.amount,
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
#[test]
fn is_possible_for_an_already_delegated_node() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
let delegation1 = coin(100, DENOM);
let delegation2 = coin(50, DENOM);
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation1.clone()]),
identity.clone(),
)
.unwrap();
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation2.clone()]),
identity.clone(),
)
.unwrap();
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
coin(delegation1.amount.u128() + delegation2.amount.u128(), DENOM),
mock_env().block.height,
None,
);
assert_eq!(
expected,
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
);
// node's "total_delegation" is sum of both
assert_eq!(
delegation1.amount + delegation2.amount,
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
#[test]
fn block_height_is_updated_on_new_delegation() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
let delegation = coin(100, DENOM);
let env1 = mock_env();
let mut env2 = mock_env();
let initial_height = env1.block.height;
let updated_height = initial_height + 42;
// second env has grown in block height
env2.block.height = updated_height;
try_delegate_to_mixnode(
deps.as_mut(),
env1,
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner)
.unwrap()
.block_height
);
try_delegate_to_mixnode(
deps.as_mut(),
env2,
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.unwrap();
let updated =
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner).unwrap();
assert_eq!(delegation.amount + delegation.amount, updated.amount.amount);
assert_eq!(updated_height, updated.block_height);
}
#[test]
fn block_height_is_not_updated_on_different_delegator() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner1 = Addr::unchecked("sender1");
let delegation_owner2 = Addr::unchecked("sender2");
let delegation1 = coin(100, DENOM);
let delegation2 = coin(120, DENOM);
let env1 = mock_env();
let mut env2 = mock_env();
let initial_height = env1.block.height;
let second_height = initial_height + 42;
// second env has grown in block height
env2.block.height = second_height;
try_delegate_to_mixnode(
deps.as_mut(),
env1,
mock_info(delegation_owner1.as_str(), &[delegation1.clone()]),
identity.clone(),
)
.unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1)
.unwrap()
.block_height
);
try_delegate_to_mixnode(
deps.as_mut(),
env2,
mock_info(delegation_owner2.as_str(), &[delegation2.clone()]),
identity.clone(),
)
.unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1)
.unwrap()
.block_height
);
assert_eq!(
second_height,
test_helpers::read_delegation(&deps.storage, identity, &delegation_owner2)
.unwrap()
.block_height
);
}
#[test]
fn is_disallowed_for_already_delegated_node_if_it_unbonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(100, DENOM)),
identity.clone(),
)
.unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: identity.clone()
}),
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(50, DENOM)),
identity,
)
);
}
#[test]
fn is_allowed_for_multiple_nodes() {
let mut deps = test_helpers::init_contract();
let mixnode_owner1 = "bob";
let mixnode_owner2 = "fred";
let identity1 =
test_helpers::add_mixnode(mixnode_owner1, good_mixnode_bond(), deps.as_mut());
let identity2 =
test_helpers::add_mixnode(mixnode_owner2, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(123, DENOM)),
identity1.clone(),
)
.is_ok());
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(42, DENOM)),
identity2.clone(),
)
.is_ok());
let expected1 = Delegation::new(
delegation_owner.clone(),
identity1.clone(),
coin(123, DENOM),
mock_env().block.height,
None,
);
let expected2 = Delegation::new(
delegation_owner.clone(),
identity2.clone(),
coin(42, DENOM),
mock_env().block.height,
None,
);
assert_eq!(
expected1,
test_helpers::read_delegation(&deps.storage, identity1, &delegation_owner).unwrap()
);
assert_eq!(
expected2,
test_helpers::read_delegation(&deps.storage, identity2, &delegation_owner).unwrap()
);
}
#[test]
fn is_allowed_by_multiple_users() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation1 = coin(123, DENOM);
let delegation2 = coin(234, DENOM);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("sender1", &[delegation1.clone()]),
identity.clone(),
)
.is_ok());
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("sender2", &[delegation2.clone()]),
identity.clone(),
)
.is_ok());
// node's "total_delegation" is sum of both
assert_eq!(
delegation1.amount + delegation2.amount,
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
#[test]
fn delegation_is_not_removed_if_node_unbonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
let delegation_amount = coin(100, DENOM);
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &vec![delegation_amount.clone()]),
identity.clone(),
)
.unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
delegation_amount,
mock_env().block.height,
None,
);
assert_eq!(
expected,
test_helpers::read_delegation(&deps.storage, identity, delegation_owner).unwrap()
)
}
}
#[cfg(test)]
mod removing_mix_stake_delegation {
use cosmwasm_std::coin;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Addr;
use cosmwasm_std::Uint128;
use crate::mixnodes::transactions::try_remove_mixnode;
use crate::support::tests::test_helpers::good_mixnode_bond;
use super::storage;
use super::*;
#[test]
fn fails_if_delegation_never_existed() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: identity.clone(),
address: delegation_owner.clone(),
}),
try_remove_delegation_from_mixnode(
deps.as_mut(),
mock_info(delegation_owner.as_str(), &[]),
identity,
)
);
}
#[test]
fn succeeds_if_delegation_existed() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(100, DENOM)),
identity.clone(),
)
.unwrap();
assert_eq!(
Ok(Response::new().add_message(BankMsg::Send {
to_address: delegation_owner.clone().into(),
amount: coins(100, DENOM),
})),
try_remove_delegation_from_mixnode(
deps.as_mut(),
mock_info(delegation_owner.as_str(), &[]),
identity.clone(),
)
);
assert!(storage::delegations()
.may_load(
&deps.storage,
(identity.clone(), delegation_owner).joined_key(),
)
.unwrap()
.is_none());
// and total delegation is cleared
assert_eq!(
Uint128::zero(),
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
#[test]
fn succeeds_if_delegation_existed_even_if_node_unbonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(100, DENOM)),
identity.clone(),
)
.unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Ok(Response::new().add_message(BankMsg::Send {
to_address: delegation_owner.clone().into(),
amount: coins(100, DENOM),
})),
try_remove_delegation_from_mixnode(
deps.as_mut(),
mock_info(delegation_owner.as_str(), &[]),
identity.clone(),
)
);
assert!(
test_helpers::read_delegation(&deps.storage, identity, delegation_owner).is_none()
);
}
#[test]
fn total_delegation_is_preserved_if_only_some_undelegate() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner1 = Addr::unchecked("sender1");
let delegation_owner2 = Addr::unchecked("sender2");
let delegation1 = coin(123, DENOM);
let delegation2 = coin(234, DENOM);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner1.as_str(), &[delegation1.clone()]),
identity.clone(),
)
.is_ok());
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner2.as_str(), &[delegation2.clone()]),
identity.clone(),
)
.is_ok());
// sender1 undelegates
try_remove_delegation_from_mixnode(
deps.as_mut(),
mock_info(delegation_owner1.as_str(), &[]),
identity.clone(),
)
.unwrap();
// but total delegation should still equal to what sender2 sent
// node's "total_delegation" is sum of both
assert_eq!(
delegation2.amount,
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
}
// #[cfg(test)]
// mod multi_delegations {
// use super::*;
// use crate::delegations::helpers;
// use crate::delegations::queries::tests::store_n_mix_delegations;
// use crate::support::tests::test_helpers;
// use mixnet_contract::IdentityKey;
// use mixnet_contract::RawDelegationData;
//
// #[test]
// fn multiple_page_delegations() {
// let mut deps = test_helpers::init_contract();
// let node_identity: IdentityKey = "foo".into();
// store_n_mix_delegations(
// storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
// &mut deps.storage,
// &node_identity,
// );
// let mix_bucket = storage::all_mix_delegations_read::<RawDelegationData>(&deps.storage);
// let mix_delegations = helpers::Delegations::new(mix_bucket);
// assert_eq!(
// storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
// mix_delegations.count() as u32
// );
// }
// }
}
+15 -9
View File
@@ -24,9 +24,6 @@ pub enum ContractError {
#[error("Not enough funds sent for gateway bond. (received {received}, minimum {minimum})")]
InsufficientGatewayBond { received: u128, minimum: u128 },
#[error("Gateway ({identity}) does not exist")]
GatewayBondNotFound { identity: IdentityKey },
#[error("{owner} does not seem to own any mixnodes")]
NoAssociatedMixNodeBond { owner: Addr },
@@ -54,9 +51,6 @@ pub enum ContractError {
#[error("Provided rewarded set size is zero")]
ZeroRewardedSet,
#[error("The node had uptime larger than 100%")]
UnexpectedUptime,
#[error("This address has already bonded a mixnode")]
AlreadyOwnsMixnode,
@@ -72,9 +66,6 @@ pub enum ContractError {
#[error("No funds were provided for the delegation")]
EmptyDelegation,
#[error("Request did not come from the node owner ({owner})")]
InvalidSender { owner: Addr },
#[error("Could not find any delegation information associated with mixnode {identity} for {address}")]
NoMixnodeDelegationFound {
identity: IdentityKey,
@@ -101,4 +92,19 @@ pub enum ContractError {
#[error("Mixnode's {identity} operator has not been rewarded yet - cannot perform delegator rewarding until that happens")]
MixnodeOperatorNotRewarded { identity: IdentityKey },
#[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("Profit margin percent needs to be an integer in range [0, 100], recieved {0}")]
InvalidProfitMarginPercent(u8),
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod storage;
pub mod transactions;
+212
View File
@@ -0,0 +1,212 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::mixnodes::storage::{BOND_PAGE_DEFAULT_LIMIT, BOND_PAGE_MAX_LIMIT}; // Keeps gateway and mixnode retrieval in sync by re-using the constant. Could be split into its own constant.
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use mixnet_contract::{GatewayBond, GatewayOwnershipResponse, IdentityKey, PagedGatewayResponse};
pub(crate) fn query_gateways_paged(
deps: Deps,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> StdResult<PagedGatewayResponse> {
let limit = limit
.unwrap_or(BOND_PAGE_DEFAULT_LIMIT)
.min(BOND_PAGE_MAX_LIMIT) as usize;
let start = start_after.map(Bound::exclusive);
let nodes = storage::gateways()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<GatewayBond>>>()?;
let start_next_after = nodes.last().map(|node| node.identity().clone());
Ok(PagedGatewayResponse::new(nodes, limit, start_next_after))
}
pub(crate) fn query_owns_gateway(
deps: Deps,
address: String,
) -> StdResult<GatewayOwnershipResponse> {
let validated_addr = deps.api.addr_validate(&address)?;
let gateway = storage::gateways()
.idx
.owner
.item(deps.storage, validated_addr.clone())?
.map(|record| record.1);
Ok(GatewayOwnershipResponse {
address: validated_addr,
gateway,
})
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::contract::execute;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn gateways_empty_on_init() {
let deps = test_helpers::init_contract();
let response = query_gateways_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.nodes.len());
}
#[test]
fn gateways_paged_retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_gateway(&key, test_helpers::good_gateway_bond(), deps.as_mut());
}
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32);
}
#[test]
fn gateways_paged_retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_gateway(&key, test_helpers::good_gateway_bond(), deps.as_mut());
}
// query without explicitly setting a limit
let page1 = query_gateways_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(BOND_PAGE_DEFAULT_LIMIT, page1.nodes.len() as u32);
}
#[test]
fn gateways_paged_retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_gateway(&key, test_helpers::good_gateway_bond(), deps.as_mut());
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * BOND_PAGE_DEFAULT_LIMIT;
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
let expected_limit = BOND_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.nodes.len() as u32);
}
#[test]
fn gateway_pagination_works() {
// prepare 4 messages and identities that are sorted by the generated identities
// (because we query them in an ascended manner)
let mut exec_data = (0..4)
.map(|i| {
let sender = format!("nym-addr{}", i);
let (msg, identity) = test_helpers::valid_bond_gateway_msg(&sender);
(msg, (sender, identity))
})
.collect::<Vec<_>>();
exec_data.sort_by(|(_, (_, id1)), (_, (_, id2))| id1.cmp(id2));
let (messages, sender_identities): (Vec<_>, Vec<_>) = exec_data.into_iter().unzip();
let mut deps = test_helpers::init_contract();
let info = mock_info(
&sender_identities[0].0.clone(),
&test_helpers::good_gateway_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[0].clone()).unwrap();
let per_page = 2;
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.nodes.len());
// save another
let info = mock_info(
&sender_identities[1].0.clone(),
&test_helpers::good_gateway_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[1].clone()).unwrap();
// page1 should have 2 results on it
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
let info = mock_info(
&sender_identities[2].0.clone(),
&test_helpers::good_gateway_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[2].clone()).unwrap();
// page1 still has 2 results
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_gateways_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.nodes.len());
// save another one
let info = mock_info(
&sender_identities[3].0.clone(),
&test_helpers::good_gateway_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[3].clone()).unwrap();
let page2 = query_gateways_paged(
deps.as_ref(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.nodes.len());
}
#[test]
fn query_for_gateway_owner_works() {
let mut deps = test_helpers::init_contract();
// "fred" does not own a mixnode if there are no mixnodes
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
// mixnode was added to "bob", "fred" still does not own one
test_helpers::add_gateway("bob", test_helpers::good_gateway_bond(), deps.as_mut());
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
// "fred" now owns a gateway!
test_helpers::add_gateway("fred", test_helpers::good_gateway_bond(), deps.as_mut());
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.gateway.is_some());
// but after unbonding it, he doesn't own one anymore
crate::gateways::transactions::try_remove_gateway(deps.as_mut(), mock_info("fred", &[]))
.unwrap();
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
}
}
+108
View File
@@ -0,0 +1,108 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex};
use mixnet_contract::{GatewayBond, IdentityKeyRef};
// storage prefixes
const GATEWAYS_PK_NAMESPACE: &str = "gt";
const GATEWAYS_OWNER_IDX_NAMESPACE: &str = "gto";
pub(crate) struct GatewayBondIndex<'a> {
pub(crate) owner: UniqueIndex<'a, Addr, GatewayBond>,
}
// IndexList is just boilerplate code for fetching a struct's indexes
// note that from my understanding this will be converted into a macro at some point in the future
impl<'a> IndexList<GatewayBond> for GatewayBondIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<GatewayBond>> + '_> {
let v: Vec<&dyn Index<GatewayBond>> = vec![&self.owner];
Box::new(v.into_iter())
}
}
// gateways() is the storage access function.
pub(crate) fn gateways<'a>() -> IndexedMap<'a, IdentityKeyRef<'a>, GatewayBond, GatewayBondIndex<'a>>
{
let indexes = GatewayBondIndex {
owner: UniqueIndex::new(|d| d.owner.clone(), GATEWAYS_OWNER_IDX_NAMESPACE),
};
IndexedMap::new(GATEWAYS_PK_NAMESPACE, indexes)
}
// currently not used outside tests
#[cfg(test)]
mod tests {
use super::super::storage;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::StdResult;
use cosmwasm_std::Storage;
use cosmwasm_std::{coin, Addr, Uint128};
use mixnet_contract::GatewayBond;
use mixnet_contract::IdentityKey;
use mixnet_contract::{Gateway, IdentityKeyRef};
// currently this is only used in tests but may become useful later on
pub(crate) fn read_gateway_pledge_amount(
storage: &dyn Storage,
identity: IdentityKeyRef,
) -> StdResult<cosmwasm_std::Uint128> {
let node = storage::gateways().load(storage, identity)?;
Ok(node.pledge_amount.amount)
}
#[test]
fn gateway_single_read_retrieval() {
let mut storage = MockStorage::new();
let bond1 = test_helpers::gateway_bond_fixture("owner1");
let bond2 = test_helpers::gateway_bond_fixture("owner2");
storage::gateways()
.save(&mut storage, "bond1", &bond1)
.unwrap();
storage::gateways()
.save(&mut storage, "bond2", &bond2)
.unwrap();
let res1 = storage::gateways().load(&storage, "bond1").unwrap();
let res2 = storage::gateways().load(&storage, "bond2").unwrap();
assert_eq!(bond1, res1);
assert_eq!(bond2, res2);
}
#[test]
fn reading_gateway_bond() {
let mut mock_storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces an error if target gateway doesn't exist
let res = read_gateway_pledge_amount(&mock_storage, &node_identity);
assert!(res.is_err());
// returns appropriate value otherwise
let pledge_amount = 1000;
let gateway_bond = GatewayBond {
pledge_amount: coin(pledge_amount, DENOM),
owner: node_owner.clone(),
block_height: 12_345,
gateway: Gateway {
identity_key: node_identity.clone(),
..test_helpers::gateway_fixture()
},
proxy: None,
};
storage::gateways()
.save(&mut mock_storage, &node_identity, &gateway_bond)
.unwrap();
assert_eq!(
Uint128::new(pledge_amount),
read_gateway_pledge_amount(&mock_storage, &node_identity).unwrap()
);
}
}
@@ -0,0 +1,561 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
use config::defaults::DENOM;
use cosmwasm_std::{
coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128,
};
use mixnet_contract::{Gateway, GatewayBond, Layer};
use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg;
pub fn try_add_gateway(
deps: DepsMut,
env: Env,
info: MessageInfo,
gateway: Gateway,
owner_signature: String,
) -> Result<Response, ContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
_try_add_gateway(
deps,
env,
gateway,
pledge,
info.sender.as_str(),
owner_signature,
None,
)
}
pub fn try_add_gateway_on_behalf(
deps: DepsMut,
env: Env,
info: MessageInfo,
gateway: Gateway,
owner: String,
owner_signature: String,
) -> Result<Response, ContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
let _proxy = info.sender;
_try_add_gateway(deps, env, gateway, pledge, &owner, owner_signature, None)
}
pub(crate) fn _try_add_gateway(
deps: DepsMut,
env: Env,
gateway: Gateway,
pledge: Coin,
owner: &str,
owner_signature: String,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = deps.api.addr_validate(owner)?;
// if the client has an active bonded mixnode or gateway, don't allow bonding
ensure_no_existing_bond(deps.storage, &owner)?;
// check if somebody else has already bonded a gateway with this identity
if let Some(existing_bond) =
storage::gateways().may_load(deps.storage, &gateway.identity_key)?
{
if existing_bond.owner != owner {
return Err(ContractError::DuplicateGateway {
owner: existing_bond.owner,
});
}
}
// check if this sender actually owns the gateway by checking the signature
validate_node_identity_signature(
deps.as_ref(),
&owner,
owner_signature,
&gateway.identity_key,
)?;
let bond = GatewayBond::new(pledge, owner, env.block.height, gateway, proxy);
storage::gateways().save(deps.storage, bond.identity(), &bond)?;
mixnet_params_storage::increment_layer_count(deps.storage, Layer::Gateway)?;
Ok(Response::new())
}
pub fn try_remove_gateway_on_behalf(
deps: DepsMut,
info: MessageInfo,
owner: String,
) -> Result<Response, ContractError> {
let proxy = info.sender;
_try_remove_gateway(deps, &owner, Some(proxy))
}
pub fn try_remove_gateway(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
_try_remove_gateway(deps, info.sender.as_ref(), None)
}
pub(crate) fn _try_remove_gateway(
deps: DepsMut,
owner: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = deps.api.addr_validate(owner)?;
// try to find the node of the sender
let gateway_bond = match storage::gateways()
.idx
.owner
.item(deps.storage, owner.clone())?
{
Some(record) => record.1,
None => return Err(ContractError::NoAssociatedGatewayBond { owner }),
};
if proxy != gateway_bond.proxy {
return Err(ContractError::ProxyMismatch {
existing: gateway_bond
.proxy
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
});
}
// send bonded funds back to the bond owner
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
amount: vec![gateway_bond.pledge_amount()],
};
// remove the bond
storage::gateways().remove(deps.storage, gateway_bond.identity())?;
// decrement layer count
mixnet_params_storage::decrement_layer_count(deps.storage, Layer::Gateway)?;
let mut response = Response::new()
.add_message(return_tokens)
.add_attribute("action", "unbond")
.add_attribute("address", owner.clone())
.add_attribute("gateway_bond", gateway_bond.to_string());
if let Some(proxy) = &proxy {
let msg = VestingContractExecuteMsg::TrackUnbondGateway {
owner: owner.as_str().to_string(),
amount: gateway_bond.pledge_amount,
};
let track_unbond_message = wasm_execute(proxy, &msg, coins(0, DENOM))?;
response = response.add_message(track_unbond_message);
}
Ok(response)
}
fn validate_gateway_pledge(
mut pledge: Vec<Coin>,
minimum_pledge: Uint128,
) -> Result<Coin, ContractError> {
// check if anything was put as bond
if pledge.is_empty() {
return Err(ContractError::NoBondFound);
}
if pledge.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
// check that the denomination is correct
if pledge[0].denom != DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have at least 100 coins in our pledge
if pledge[0].amount < minimum_pledge {
return Err(ContractError::InsufficientGatewayBond {
received: pledge[0].amount.into(),
minimum: minimum_pledge.into(),
});
}
Ok(pledge.pop().unwrap())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::contract::{execute, query, INITIAL_GATEWAY_PLEDGE};
use crate::error::ContractError;
use crate::gateways::transactions::validate_gateway_pledge;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::attr;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coins, BankMsg, Response};
use cosmwasm_std::{from_binary, Addr, Uint128};
use mixnet_contract::Gateway;
use mixnet_contract::{ExecuteMsg, PagedGatewayResponse, QueryMsg};
#[test]
fn gateway_add() {
let mut deps = test_helpers::init_contract();
// if we fail validation (by say not sending enough funds
let insufficient_bond = Into::<u128>::into(INITIAL_GATEWAY_PLEDGE) - 1;
let info = mock_info("anyone", &coins(insufficient_bond, DENOM));
let (msg, _) = test_helpers::valid_bond_gateway_msg("anyone");
// we are informed that we didn't send enough funds
let result = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
result,
Err(ContractError::InsufficientGatewayBond {
received: insufficient_bond,
minimum: INITIAL_GATEWAY_PLEDGE.into(),
})
);
// make sure no gateway was inserted into the topology
let res = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetGateways {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedGatewayResponse = from_binary(&res).unwrap();
assert_eq!(0, page.nodes.len());
// if we send enough funds
let info = mock_info("anyone", &test_helpers::good_gateway_bond());
let (msg, identity) = test_helpers::valid_bond_gateway_msg("anyone");
// we get back a message telling us everything was OK
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
// we can query topology and the new node is there
let query_response = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetGateways {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedGatewayResponse = from_binary(&query_response).unwrap();
assert_eq!(1, page.nodes.len());
assert_eq!(
&Gateway {
identity_key: identity,
..test_helpers::gateway_fixture()
},
page.nodes[0].gateway()
);
// if there was already a gateway bonded by particular user
let info = mock_info("foomper", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("foomper");
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let info = mock_info("foomper", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("foomper");
// it fails
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(Err(ContractError::AlreadyOwnsGateway), execute_response);
// bonding fails if the user already owns a mixnode
test_helpers::add_mixnode(
"mixnode-owner",
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
let info = mock_info("mixnode-owner", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("mixnode-owner");
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(execute_response, Err(ContractError::AlreadyOwnsMixnode));
// but after he unbonds it, it's all fine again
let info = mock_info("mixnode-owner", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let info = mock_info("mixnode-owner", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("mixnode-owner");
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
// adding another node from another account, but with the same IP, should fail (or we would have a weird state).
// Is that right? Think about this, not sure yet.
}
#[test]
fn adding_gateway_without_existing_owner() {
let mut deps = test_helpers::init_contract();
let info = mock_info("gateway-owner", &test_helpers::good_gateway_bond());
// before the execution the node had no associated owner
assert!(storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.is_none());
let (msg, identity) = test_helpers::valid_bond_gateway_msg("gateway-owner");
// it's all fine, owner is saved
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
assert_eq!(
&identity,
storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
}
#[test]
fn adding_gateway_with_existing_owner() {
let mut deps = test_helpers::init_contract();
let identity = test_helpers::add_gateway(
"gateway-owner",
test_helpers::good_gateway_bond(),
deps.as_mut(),
);
// request fails giving the existing owner address in the message
let info = mock_info(
"gateway-owner-pretender",
&test_helpers::good_gateway_bond(),
);
let msg = ExecuteMsg::BondGateway {
gateway: Gateway {
identity_key: identity,
..test_helpers::gateway_fixture()
},
owner_signature: "foomp".to_string(),
};
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
Err(ContractError::DuplicateGateway {
owner: Addr::unchecked("gateway-owner")
}),
execute_response
);
}
#[test]
fn adding_gateway_with_existing_unchanged_owner() {
let mut deps = test_helpers::init_contract();
test_helpers::add_gateway(
"gateway-owner",
test_helpers::good_gateway_bond(),
deps.as_mut(),
);
let info = mock_info("gateway-owner", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("gateway-owner");
let res = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(Err(ContractError::AlreadyOwnsGateway), res);
}
#[test]
fn gateway_remove() {
let mut deps = test_helpers::init_contract();
// try unbond when no nodes exist yet
let info = mock_info("anyone", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
// we're told that there is no node for our address
assert_eq!(
result,
Err(ContractError::NoAssociatedGatewayBond {
owner: Addr::unchecked("anyone")
})
);
// let's add a node owned by bob
test_helpers::add_gateway("bob", test_helpers::good_gateway_bond(), deps.as_mut());
// attempt to unbond fred's node, which doesn't exist
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
result,
Err(ContractError::NoAssociatedGatewayBond {
owner: Addr::unchecked("fred")
})
);
// bob's node is still there
let nodes = test_helpers::get_gateways(&mut deps);
assert_eq!(1, nodes.len());
let first_node = &nodes[0];
assert_eq!(&Addr::unchecked("bob"), first_node.owner());
// add a node owned by fred
let fred_identity =
test_helpers::add_gateway("fred", test_helpers::good_gateway_bond(), deps.as_mut());
// let's make sure we now have 2 nodes:
assert_eq!(2, test_helpers::get_gateways(&mut deps).len());
// unbond fred's node
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let remove_fred = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
// we should see log messages come back showing an unbond message
let expected_attributes = vec![
attr("action", "unbond"),
attr("address", "fred"),
attr(
"gateway_bond",
format!(
"amount: {} {}, owner: fred, identity: {}",
INITIAL_GATEWAY_PLEDGE, DENOM, fred_identity
),
),
];
// we should see a funds transfer from the contract back to fred
let expected_message = BankMsg::Send {
to_address: String::from(info.sender),
amount: test_helpers::good_gateway_bond(),
};
// run the executor and check that we got back the correct results
let expected = Response::new()
.add_attributes(expected_attributes)
.add_message(expected_message);
assert_eq!(remove_fred, expected);
// only 1 node now exists, owned by bob:
let gateway_bonds = test_helpers::get_gateways(&mut deps);
assert_eq!(1, gateway_bonds.len());
assert_eq!(&Addr::unchecked("bob"), gateway_bonds[0].owner());
}
#[test]
fn removing_gateway_clears_ownership() {
let mut deps = test_helpers::init_contract();
let info = mock_info("gateway-owner", &test_helpers::good_gateway_bond());
let (bond_msg, identity) = test_helpers::valid_bond_gateway_msg("gateway-owner");
execute(deps.as_mut(), mock_env(), info, bond_msg.clone()).unwrap();
assert_eq!(
&identity,
storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
let info = mock_info("gateway-owner", &[]);
let msg = ExecuteMsg::UnbondGateway {};
assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok());
assert!(storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.is_none());
// and since it's removed, it can be reclaimed
let info = mock_info("gateway-owner", &test_helpers::good_gateway_bond());
assert!(execute(deps.as_mut(), mock_env(), info, bond_msg).is_ok());
assert_eq!(
&identity,
storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
}
#[test]
fn validating_gateway_bond() {
// you must send SOME funds
let result = validate_gateway_pledge(Vec::new(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::NoBondFound));
// you must send at least 100 coins...
let mut bond = test_helpers::good_gateway_bond();
bond[0].amount = INITIAL_GATEWAY_PLEDGE.checked_sub(Uint128::new(1)).unwrap();
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(
result,
Err(ContractError::InsufficientGatewayBond {
received: Into::<u128>::into(INITIAL_GATEWAY_PLEDGE) - 1,
minimum: INITIAL_GATEWAY_PLEDGE.into(),
})
);
// more than that is still fine
let mut bond = test_helpers::good_gateway_bond();
bond[0].amount = INITIAL_GATEWAY_PLEDGE + Uint128::new(1);
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert!(result.is_ok());
// it must be sent in the defined denom!
let mut bond = test_helpers::good_gateway_bond();
bond[0].denom = "baddenom".to_string();
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
let mut bond = test_helpers::good_gateway_bond();
bond[0].denom = "foomp".to_string();
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
}
}
-272
View File
@@ -1,272 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::transactions::OLD_DELEGATIONS_CHUNK_SIZE;
use cosmwasm_std::{Order, StdError, StdResult};
use cosmwasm_storage::ReadonlyBucket;
use mixnet_contract::{Addr, IdentityKey, PagedAllDelegationsResponse, UnpackedDelegation};
use serde::de::DeserializeOwned;
use serde::Serialize;
// cosmwasm bucket internal value
const NAMESPACE_LENGTH: usize = 2;
// Extracts the node identity and owner of a delegation from the bytes used as
// key in the delegation buckets.
fn extract_identity_and_owner(bytes: Vec<u8>) -> StdResult<(Addr, IdentityKey)> {
if bytes.len() < NAMESPACE_LENGTH {
return Err(StdError::parse_err(
"mixnet_contract::types::IdentityKey",
"Invalid type",
));
}
let identity_size = u16::from_be_bytes([bytes[0], bytes[1]]) as usize;
let identity_bytes: Vec<u8> = bytes
.iter()
.skip(NAMESPACE_LENGTH)
.take(identity_size)
.copied()
.collect();
let identity = IdentityKey::from_utf8(identity_bytes)
.map_err(|_| StdError::parse_err("mixnet_contract::types::IdentityKey", "Invalid type"))?;
let owner_bytes: Vec<u8> = bytes
.iter()
.skip(NAMESPACE_LENGTH + identity_size)
.copied()
.collect();
let owner = Addr::unchecked(
String::from_utf8(owner_bytes)
.map_err(|_| StdError::parse_err("cosmwasm_std::addresses::Addr", "Invalid type"))?,
);
Ok((owner, identity))
}
// currently not used outside tests
#[cfg(test)]
// Converts the node identity and owner of a delegation into the bytes used as
// key in the delegation buckets.
pub(crate) fn identity_and_owner_to_bytes(identity: &IdentityKey, owner: &Addr) -> Vec<u8> {
let mut bytes = u16::to_be_bytes(identity.len() as u16).to_vec();
bytes.append(&mut identity.as_bytes().to_vec());
bytes.append(&mut owner.as_bytes().to_vec());
bytes
}
pub(crate) fn get_all_delegations_paged<T>(
bucket: &ReadonlyBucket<T>,
start_after: &Option<Vec<u8>>,
limit: usize,
) -> StdResult<PagedAllDelegationsResponse<T>>
where
T: Serialize + DeserializeOwned,
{
let delegations = bucket
.range(start_after.as_deref(), None, Order::Ascending)
.filter(|res| res.is_ok())
.take(limit)
.map(|res| {
res.map(|entry| {
let (owner, identity) = extract_identity_and_owner(entry.0).expect("Invalid node identity or address used as key in bucket. The storage is corrupted!");
UnpackedDelegation::new(owner, identity, entry.1)
})
})
.collect::<StdResult<Vec<UnpackedDelegation<T>>>>()?;
let start_next_after = if let Some(Ok(last)) = bucket
.range(start_after.as_deref(), None, Order::Ascending)
.filter(|res| res.is_ok())
.take(limit)
.last()
{
Some(last.0)
} else {
None
};
Ok(PagedAllDelegationsResponse::new(
delegations,
start_next_after,
))
}
pub struct Delegations<'a, T: Clone + Serialize + DeserializeOwned> {
delegations_bucket: ReadonlyBucket<'a, T>,
curr_delegations: Vec<UnpackedDelegation<T>>,
curr_index: usize,
start_after: Option<Vec<u8>>,
last_page: bool,
}
#[cfg(test)]
impl<'a, T: Clone + Serialize + DeserializeOwned> Delegations<'a, T> {
pub fn new(delegations_bucket: ReadonlyBucket<'a, T>) -> Self {
Delegations {
delegations_bucket,
curr_delegations: vec![],
curr_index: OLD_DELEGATIONS_CHUNK_SIZE,
start_after: None,
last_page: false,
}
}
}
impl<'a, T: Clone + Serialize + DeserializeOwned> Iterator for Delegations<'a, T> {
type Item = UnpackedDelegation<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.curr_index == OLD_DELEGATIONS_CHUNK_SIZE && !self.last_page {
self.start_after = self.start_after.clone().map(|mut v: Vec<u8>| {
v.push(0);
v
});
let delegations_paged = get_all_delegations_paged(
&self.delegations_bucket,
&self.start_after,
OLD_DELEGATIONS_CHUNK_SIZE,
)
.ok()?;
self.curr_delegations = delegations_paged.delegations;
self.curr_index = 0;
self.start_after = delegations_paged.start_next_after;
if self.start_after.is_none() {
self.last_page = true;
}
}
if self.curr_index < self.curr_delegations.len() {
let ret = self.curr_delegations[self.curr_index].clone();
self.curr_index += 1;
Some(ret)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::queries::tests::store_n_mix_delegations;
use crate::storage::{all_mix_delegations_read, mix_delegations};
use crate::support::tests::helpers;
use cosmwasm_std::testing::mock_dependencies;
use mixnet_contract::RawDelegationData;
#[test]
fn delegations_iterator() {
let mut deps = helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(
2 * OLD_DELEGATIONS_CHUNK_SIZE as u32,
&mut deps.storage,
&node_identity,
);
let mix_bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
let mut delegations = Delegations::new(mix_bucket);
assert!(delegations.curr_delegations.is_empty());
assert_eq!(delegations.curr_index, OLD_DELEGATIONS_CHUNK_SIZE);
delegations.next().unwrap();
assert_eq!(
delegations.curr_delegations.len(),
OLD_DELEGATIONS_CHUNK_SIZE
);
assert_eq!(delegations.curr_index, 1);
for _ in 0..OLD_DELEGATIONS_CHUNK_SIZE {
delegations.next().unwrap();
}
assert_eq!(
delegations.curr_delegations.len(),
OLD_DELEGATIONS_CHUNK_SIZE
);
assert_eq!(delegations.curr_index, 1);
for _ in 0..OLD_DELEGATIONS_CHUNK_SIZE - 1 {
delegations.next().unwrap();
}
assert!(delegations.next().is_none());
}
#[test]
fn identity_and_owner_deserialization() {
assert!(extract_identity_and_owner(vec![]).is_err());
assert!(extract_identity_and_owner(vec![0]).is_err());
let (owner, identity) = extract_identity_and_owner(vec![
0, 7, 109, 105, 120, 110, 111, 100, 101, 97, 108, 105, 99, 101,
])
.unwrap();
assert_eq!(owner, "alice");
assert_eq!(identity, "mixnode");
}
#[test]
fn identity_and_owner_serialization() {
let identity: IdentityKey = "gateway".into();
let owner = Addr::unchecked("bob");
assert_eq!(
vec![0, 7, 103, 97, 116, 101, 119, 97, 121, 98, 111, 98],
identity_and_owner_to_bytes(&identity, &owner)
);
}
#[test]
fn all_mix_delegations() {
let mut deps = mock_dependencies(&[]);
let node_identity1: IdentityKey = "foo1".into();
let delegation_owner1 = Addr::unchecked("bar1");
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner2 = Addr::unchecked("bar2");
let raw_delegation = RawDelegationData::new(1000u128.into(), 42);
let mut start_after = None;
mix_delegations(&mut deps.storage, &node_identity1)
.save(delegation_owner1.as_bytes(), &raw_delegation)
.unwrap();
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
let response =
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
start_after = response.start_next_after;
let delegations = response.delegations;
assert_eq!(delegations.len(), 1);
assert_eq!(
delegations[0],
UnpackedDelegation::new(
delegation_owner1.clone(),
node_identity1.clone(),
raw_delegation.clone()
)
);
mix_delegations(&mut deps.storage, &node_identity2)
.save(delegation_owner2.as_bytes(), &raw_delegation)
.unwrap();
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
let response =
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
start_after = response.start_next_after;
let delegations = response.delegations;
assert_eq!(delegations.len(), 2);
assert_eq!(
delegations[1],
UnpackedDelegation::new(
delegation_owner2.clone(),
node_identity2.clone(),
raw_delegation.clone()
)
);
mix_delegations(&mut deps.storage, &node_identity1).remove(delegation_owner1.as_bytes());
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
let response =
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
let delegations = response.delegations;
assert_eq!(delegations.len(), 1);
assert_eq!(
delegations[0],
UnpackedDelegation::new(delegation_owner2, node_identity2, raw_delegation.clone()),
);
}
}
+7 -7
View File
@@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
pub mod contract;
pub mod error;
pub(crate) mod helpers;
pub mod queries;
pub mod state;
pub(crate) mod storage;
pub mod support;
pub mod transactions;
mod delegations;
mod error;
mod gateways;
mod mixnet_contract_settings;
mod mixnodes;
mod rewards;
mod support;
@@ -0,0 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod models;
pub mod queries;
pub mod storage;
pub mod transactions;
@@ -2,15 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use mixnet_contract::StateParams;
use mixnet_contract::ContractStateParams;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
pub struct ContractState {
pub owner: Addr, // only the owner account can update state
pub rewarding_validator_address: Addr,
pub params: StateParams,
pub params: ContractStateParams,
// keep track of the changes to the current rewarding interval,
// i.e. at which block has the latest rewarding occurred
@@ -0,0 +1,86 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use cosmwasm_std::{Deps, StdResult};
use mixnet_contract::{ContractStateParams, MixnetContractVersion, RewardingIntervalResponse};
pub(crate) fn query_contract_settings_params(deps: Deps) -> StdResult<ContractStateParams> {
storage::CONTRACT_STATE
.load(deps.storage)
.map(|settings| settings.params)
}
pub(crate) fn query_rewarding_interval(deps: Deps) -> StdResult<RewardingIntervalResponse> {
let state = storage::CONTRACT_STATE.load(deps.storage)?;
Ok(RewardingIntervalResponse {
current_rewarding_interval_starting_block: state.rewarding_interval_starting_block,
current_rewarding_interval_nonce: state.latest_rewarding_interval_nonce,
rewarding_in_progress: state.rewarding_in_progress,
})
}
pub(crate) fn query_contract_version() -> MixnetContractVersion {
// as per docs
// env! macro will expand to the value of the named environment variable at
// compile time, yielding an expression of type `&'static str`
MixnetContractVersion {
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(),
build_version: env!("VERGEN_BUILD_SEMVER").to_string(),
commit_sha: env!("VERGEN_GIT_SHA").to_string(),
commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP").to_string(),
commit_branch: env!("VERGEN_GIT_BRANCH").to_string(),
rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(),
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::mixnet_contract_settings::models::ContractState;
use crate::support::tests::test_helpers;
use cosmwasm_std::Addr;
#[test]
fn query_for_contract_settings_works() {
let mut deps = test_helpers::init_contract();
let dummy_state = ContractState {
owner: Addr::unchecked("someowner"),
rewarding_validator_address: Addr::unchecked("monitor"),
params: ContractStateParams {
minimum_mixnode_pledge: 123u128.into(),
minimum_gateway_pledge: 456u128.into(),
mixnode_rewarded_set_size: 1000,
mixnode_active_set_size: 500,
active_set_work_factor: 10,
},
rewarding_interval_starting_block: 123,
latest_rewarding_interval_nonce: 0,
rewarding_in_progress: false,
};
storage::CONTRACT_STATE
.save(deps.as_mut().storage, &dummy_state)
.unwrap();
assert_eq!(
dummy_state.params,
query_contract_settings_params(deps.as_ref()).unwrap()
)
}
#[test]
fn query_for_contract_version_works() {
// this basically means _something_ was grabbed from the environment at compilation time
let version = query_contract_version();
assert!(!version.build_timestamp.is_empty());
assert!(!version.build_version.is_empty());
assert!(!version.commit_sha.is_empty());
assert!(!version.commit_timestamp.is_empty());
assert!(!version.commit_branch.is_empty());
assert!(!version.rustc_version.is_empty());
}
}
@@ -0,0 +1,60 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnet_contract_settings::models::ContractState;
use cosmwasm_std::StdResult;
use cosmwasm_std::Storage;
use cw_storage_plus::Item;
use mixnet_contract::Layer;
use mixnet_contract::LayerDistribution;
pub(crate) const CONTRACT_STATE: Item<ContractState> = Item::new("config");
pub(crate) const LAYERS: Item<LayerDistribution> = Item::new("layers");
pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
LAYERS
.update(storage, |mut distribution| {
match layer {
Layer::Gateway => distribution.gateways += 1,
Layer::One => distribution.layer1 += 1,
Layer::Two => distribution.layer2 += 1,
Layer::Three => distribution.layer3 += 1,
}
Ok(distribution)
})
.map(|_| ())
}
pub fn decrement_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
LAYERS
.update(storage, |mut distribution| {
match layer {
Layer::Gateway => {
distribution.gateways = distribution
.gateways
.checked_sub(1)
.expect("tried to subtract from unsigned zero!")
}
Layer::One => {
distribution.layer1 = distribution
.layer1
.checked_sub(1)
.expect("tried to subtract from unsigned zero!")
}
Layer::Two => {
distribution.layer2 = distribution
.layer2
.checked_sub(1)
.expect("tried to subtract from unsigned zero!")
}
Layer::Three => {
distribution.layer3 = distribution
.layer3
.checked_sub(1)
.expect("tried to subtract from unsigned zero!")
}
}
Ok(distribution)
})
.map(|_| ())
}
@@ -0,0 +1,110 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use cosmwasm_std::DepsMut;
use cosmwasm_std::MessageInfo;
use cosmwasm_std::Response;
use mixnet_contract::ContractStateParams;
pub(crate) fn try_update_contract_settings(
deps: DepsMut,
info: MessageInfo,
params: ContractStateParams,
) -> Result<Response, ContractError> {
let mut state = storage::CONTRACT_STATE.load(deps.storage)?;
// check if this is executed by the owner, if not reject the transaction
if info.sender != state.owner {
return Err(ContractError::Unauthorized);
}
if params.mixnode_rewarded_set_size == 0 {
return Err(ContractError::ZeroRewardedSet);
}
if params.mixnode_active_set_size == 0 {
return Err(ContractError::ZeroActiveSet);
}
// note: rewarded_set = active_set + idle_set
// hence rewarded set must always be bigger than (or equal to) the active set
if params.mixnode_rewarded_set_size < params.mixnode_active_set_size {
return Err(ContractError::InvalidActiveSetSize);
}
state.params = params;
storage::CONTRACT_STATE.save(deps.storage, &state)?;
Ok(Response::default())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::contract::{INITIAL_GATEWAY_PLEDGE, INITIAL_MIXNODE_PLEDGE};
use crate::error::ContractError;
use crate::mixnet_contract_settings::transactions::try_update_contract_settings;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Response;
use mixnet_contract::ContractStateParams;
#[test]
fn updating_contract_settings() {
let mut deps = test_helpers::init_contract();
let new_params = ContractStateParams {
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
minimum_gateway_pledge: INITIAL_GATEWAY_PLEDGE,
mixnode_rewarded_set_size: 100,
mixnode_active_set_size: 50,
active_set_work_factor: 10,
};
// sanity check to ensure new_params are different than the default ones
assert_ne!(
new_params,
storage::CONTRACT_STATE
.load(deps.as_ref().storage)
.unwrap()
.params
);
// cannot be updated from non-owner account
let info = mock_info("not-the-creator", &[]);
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(res, Err(ContractError::Unauthorized));
// but works fine from the creator account
let info = mock_info("creator", &[]);
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(res, Ok(Response::default()));
// and the state is actually updated
let current_state = storage::CONTRACT_STATE.load(deps.as_ref().storage).unwrap();
assert_eq!(current_state.params, new_params);
// error is thrown if rewarded set is smaller than the active set
let info = mock_info("creator", &[]);
let mut new_params = current_state.params.clone();
new_params.mixnode_rewarded_set_size = new_params.mixnode_active_set_size - 1;
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(Err(ContractError::InvalidActiveSetSize), res);
// error is thrown for 0 size rewarded set
let info = mock_info("creator", &[]);
let mut new_params = current_state.params.clone();
new_params.mixnode_rewarded_set_size = 0;
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(Err(ContractError::ZeroRewardedSet), res);
// error is thrown for 0 size active set
let info = mock_info("creator", &[]);
let mut new_params = current_state.params.clone();
new_params.mixnode_active_set_size = 0;
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(Err(ContractError::ZeroActiveSet), res);
}
}
@@ -0,0 +1,228 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use mixnet_contract::{IdentityKey, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
pub fn query_mixnodes_paged(
deps: Deps,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> StdResult<PagedMixnodeResponse> {
let limit = limit
.unwrap_or(storage::BOND_PAGE_DEFAULT_LIMIT)
.min(storage::BOND_PAGE_MAX_LIMIT) as usize;
let start = start_after.map(Bound::exclusive);
let nodes = storage::mixnodes()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.map(|stored_bond| {
// I really don't like this additional read per entry, but I don't see an obvious way to remove it
stored_bond.map(|stored_bond| {
let total_delegation =
storage::TOTAL_DELEGATION.load(deps.storage, stored_bond.identity());
total_delegation
.map(|total_delegation| stored_bond.attach_delegation(total_delegation))
})
})
.collect::<StdResult<StdResult<Vec<MixNodeBond>>>>()??;
let start_next_after = nodes.last().map(|node| node.identity().clone());
Ok(PagedMixnodeResponse::new(nodes, limit, start_next_after))
}
pub fn query_owns_mixnode(deps: Deps, address: String) -> StdResult<MixOwnershipResponse> {
let validated_addr = deps.api.addr_validate(&address)?;
let stored_bond = storage::mixnodes()
.idx
.owner
.item(deps.storage, validated_addr.clone())?
.map(|record| record.1);
let mixnode = match stored_bond {
None => None,
Some(bond) => {
let total_delegation =
storage::TOTAL_DELEGATION.may_load(deps.storage, bond.identity())?;
Some(bond.attach_delegation(total_delegation.unwrap_or_default()))
}
};
Ok(MixOwnershipResponse {
address: validated_addr,
mixnode,
})
}
#[cfg(test)]
pub(crate) mod tests {
use super::storage;
use super::*;
use crate::contract::execute;
use crate::mixnodes::storage::BOND_PAGE_DEFAULT_LIMIT;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn mixnodes_empty_on_init() {
let deps = test_helpers::init_contract();
let response = query_mixnodes_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.nodes.len());
}
#[test]
fn mixnodes_paged_retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_mixnode(&key, test_helpers::good_mixnode_bond(), deps.as_mut());
}
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32);
}
#[test]
fn mixnodes_paged_retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_mixnode(&key, test_helpers::good_mixnode_bond(), deps.as_mut());
}
// query without explicitly setting a limit
let page1 = query_mixnodes_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(BOND_PAGE_DEFAULT_LIMIT, page1.nodes.len() as u32);
}
#[test]
fn mixnodes_paged_retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_mixnode(&key, test_helpers::good_mixnode_bond(), deps.as_mut());
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000;
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
let expected_limit = storage::BOND_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.nodes.len() as u32);
}
#[test]
fn pagination_works() {
// prepare 4 messages and identities that are sorted by the generated identities
// (because we query them in an ascended manner)
let mut exec_data = (0..4)
.map(|i| {
let sender = format!("nym-addr{}", i);
let (msg, identity) = test_helpers::valid_bond_mixnode_msg(&sender);
(msg, (sender, identity))
})
.collect::<Vec<_>>();
exec_data.sort_by(|(_, (_, id1)), (_, (_, id2))| id1.cmp(id2));
let (messages, sender_identities): (Vec<_>, Vec<_>) = exec_data.into_iter().unzip();
let mut deps = test_helpers::init_contract();
let info = mock_info(
&sender_identities[0].0.clone(),
&test_helpers::good_mixnode_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[0].clone()).unwrap();
let per_page = 2;
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.nodes.len());
// save another
let info = mock_info(
&sender_identities[1].0.clone(),
&test_helpers::good_mixnode_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[1].clone()).unwrap();
// page1 should have 2 results on it
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
let info = mock_info(
&sender_identities[2].0.clone(),
&test_helpers::good_mixnode_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[2].clone()).unwrap();
// page1 still has 2 results
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_mixnodes_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.nodes.len());
// save another one
let info = mock_info(
&sender_identities[3].0.clone(),
&test_helpers::good_mixnode_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[3].clone()).unwrap();
let page2 = query_mixnodes_paged(
deps.as_ref(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.nodes.len());
}
#[test]
fn query_for_mixnode_owner_works() {
let mut deps = test_helpers::init_contract();
// "fred" does not own a mixnode if there are no mixnodes
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_none());
// mixnode was added to "bob", "fred" still does not own one
test_helpers::add_mixnode("bob", test_helpers::good_mixnode_bond(), deps.as_mut());
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_none());
// "fred" now owns a mixnode!
test_helpers::add_mixnode("fred", test_helpers::good_mixnode_bond(), deps.as_mut());
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_some());
// but after unbonding it, he doesn't own one anymore
crate::mixnodes::transactions::try_remove_mixnode(deps.as_mut(), mock_info("fred", &[]))
.unwrap();
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_none());
}
}
@@ -0,0 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use cosmwasm_std::{Deps, StdResult};
use mixnet_contract::LayerDistribution;
pub(crate) fn query_layer_distribution(deps: Deps) -> StdResult<LayerDistribution> {
mixnet_params_storage::LAYERS.load(deps.storage)
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bonding_queries;
pub mod layer_queries;
pub mod storage;
pub mod transactions;
+200
View File
@@ -0,0 +1,200 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::DENOM;
use cosmwasm_std::{StdResult, Storage, Uint128};
use cw_storage_plus::{Index, IndexList, IndexedMap, Map, UniqueIndex};
use mixnet_contract::{Addr, Coin, IdentityKeyRef, Layer, MixNode, MixNodeBond};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
// storage prefixes
const TOTAL_DELEGATION_NAMESPACE: &str = "td";
const MIXNODES_PK_NAMESPACE: &str = "mn";
const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
// paged retrieval limits for all queries and transactions
pub(crate) const BOND_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const BOND_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) const TOTAL_DELEGATION: Map<IdentityKeyRef, Uint128> =
Map::new(TOTAL_DELEGATION_NAMESPACE);
pub(crate) struct MixnodeBondIndex<'a> {
pub(crate) owner: UniqueIndex<'a, Addr, StoredMixnodeBond>,
}
// IndexList is just boilerplate code for fetching a struct's indexes
// note that from my understanding this will be converted into a macro at some point in the future
impl<'a> IndexList<StoredMixnodeBond> for MixnodeBondIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<StoredMixnodeBond>> + '_> {
let v: Vec<&dyn Index<StoredMixnodeBond>> = vec![&self.owner];
Box::new(v.into_iter())
}
}
// mixnodes() is the storage access function.
pub(crate) fn mixnodes<'a>(
) -> IndexedMap<'a, IdentityKeyRef<'a>, StoredMixnodeBond, MixnodeBondIndex<'a>> {
let indexes = MixnodeBondIndex {
owner: UniqueIndex::new(|d| d.owner.clone(), MIXNODES_OWNER_IDX_NAMESPACE),
};
IndexedMap::new(MIXNODES_PK_NAMESPACE, indexes)
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub(crate) struct StoredMixnodeBond {
pub pledge_amount: Coin,
pub owner: Addr,
pub layer: Layer,
pub block_height: u64,
pub mix_node: MixNode,
pub profit_margin_percent: Option<u8>,
pub proxy: Option<Addr>,
}
impl StoredMixnodeBond {
pub(crate) fn new(
pledge_amount: Coin,
owner: Addr,
layer: Layer,
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
proxy: Option<Addr>,
) -> Self {
StoredMixnodeBond {
pledge_amount,
owner,
layer,
block_height,
mix_node,
profit_margin_percent,
proxy,
}
}
pub(crate) fn attach_delegation(self, total_delegation: Uint128) -> MixNodeBond {
MixNodeBond {
total_delegation: Coin {
denom: self.pledge_amount.denom.clone(),
amount: total_delegation,
},
pledge_amount: self.pledge_amount,
owner: self.owner,
layer: self.layer,
block_height: self.block_height,
mix_node: self.mix_node,
proxy: self.proxy,
}
}
pub(crate) fn identity(&self) -> &String {
&self.mix_node.identity_key
}
pub(crate) fn pledge_amount(&self) -> Coin {
self.pledge_amount.clone()
}
}
impl Display for StoredMixnodeBond {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"amount: {}, owner: {}, identity: {}",
self.pledge_amount, self.owner, self.mix_node.identity_key
)
}
}
pub(crate) fn read_full_mixnode_bond(
storage: &dyn Storage,
mix_identity: IdentityKeyRef,
) -> StdResult<Option<MixNodeBond>> {
let stored_bond = mixnodes().may_load(storage, mix_identity)?;
match stored_bond {
None => Ok(None),
Some(stored_bond) => {
let total_delegation = TOTAL_DELEGATION.may_load(storage, mix_identity)?;
Ok(Some(MixNodeBond {
pledge_amount: stored_bond.pledge_amount,
total_delegation: Coin {
denom: DENOM.to_owned(),
amount: total_delegation.unwrap_or_default(),
},
owner: stored_bond.owner,
layer: stored_bond.layer,
block_height: stored_bond.block_height,
mix_node: stored_bond.mix_node,
proxy: stored_bond.proxy,
}))
}
}
}
#[cfg(test)]
mod tests {
use super::super::storage;
use super::*;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::{coin, Addr, Uint128};
use mixnet_contract::IdentityKey;
use mixnet_contract::MixNode;
#[test]
fn mixnode_single_read_retrieval() {
let mut storage = MockStorage::new();
let bond1 = test_helpers::stored_mixnode_bond_fixture("owner1");
let bond2 = test_helpers::stored_mixnode_bond_fixture("owner2");
mixnodes().save(&mut storage, "bond1", &bond1).unwrap();
mixnodes().save(&mut storage, "bond2", &bond2).unwrap();
let res1 = mixnodes().load(&storage, "bond1").unwrap();
let res2 = mixnodes().load(&storage, "bond2").unwrap();
assert_eq!(bond1, res1);
assert_eq!(bond2, res2);
}
#[test]
fn reading_mixnode_bond() {
let mut mock_storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces a None if target mixnode doesn't exist
let res = storage::read_full_mixnode_bond(&mock_storage, &node_identity).unwrap();
assert!(res.is_none());
// returns appropriate value otherwise
let pledge_value = 1000000000;
let mixnode_bond = StoredMixnodeBond {
pledge_amount: coin(pledge_value, DENOM),
owner: node_owner.clone(),
layer: Layer::One,
block_height: 12_345,
mix_node: MixNode {
identity_key: node_identity.clone(),
..test_helpers::mix_node_fixture()
},
profit_margin_percent: None,
proxy: None,
};
storage::mixnodes()
.save(&mut mock_storage, &node_identity, &mixnode_bond)
.unwrap();
assert_eq!(
Uint128::new(pledge_value),
storage::read_full_mixnode_bond(&mock_storage, node_identity.as_str())
.unwrap()
.unwrap()
.pledge_amount
.amount
);
}
}
@@ -0,0 +1,632 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::layer_queries::query_layer_distribution;
use crate::mixnodes::storage::StoredMixnodeBond;
use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
use config::defaults::DENOM;
use cosmwasm_std::{
coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128,
};
use mixnet_contract::MixNode;
use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg;
pub fn try_add_mixnode(
deps: DepsMut,
env: Env,
info: MessageInfo,
mix_node: MixNode,
owner_signature: String,
) -> Result<Response, ContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?;
_try_add_mixnode(
deps,
env,
mix_node,
pledge,
info.sender.as_str(),
owner_signature,
None,
)
}
pub fn try_add_mixnode_on_behalf(
deps: DepsMut,
env: Env,
info: MessageInfo,
mix_node: MixNode,
owner: String,
owner_signature: String,
) -> Result<Response, ContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?;
let _proxy = info.sender;
_try_add_mixnode(deps, env, mix_node, pledge, &owner, owner_signature, None)
}
fn _try_add_mixnode(
deps: DepsMut,
env: Env,
mix_node: MixNode,
pledge_amount: Coin,
owner: &str,
owner_signature: String,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = deps.api.addr_validate(owner)?;
// if the client has an active bonded mixnode or gateway, don't allow bonding
ensure_no_existing_bond(deps.storage, &owner)?;
// We don't have to check lower bound as its an u8
if mix_node.profit_margin_percent > 100 {
return Err(ContractError::InvalidProfitMarginPercent(
mix_node.profit_margin_percent,
));
}
// check if somebody else has already bonded a mixnode with this identity
if let Some(existing_bond) =
storage::mixnodes().may_load(deps.storage, &mix_node.identity_key)?
{
if existing_bond.owner != owner {
return Err(ContractError::DuplicateMixnode {
owner: existing_bond.owner,
});
}
}
// check if this sender actually owns the mixnode by checking the signature
validate_node_identity_signature(
deps.as_ref(),
&owner,
owner_signature,
&mix_node.identity_key,
)?;
let layer_distribution = query_layer_distribution(deps.as_ref())?;
let layer = layer_distribution.choose_with_fewest();
let stored_bond = StoredMixnodeBond::new(
pledge_amount,
owner,
layer,
env.block.height,
mix_node,
None,
proxy,
);
// technically we don't have to set the total_delegation bucket, but it makes things easier
// in different places that we can guarantee that if node exists, so does the data behind the total delegation
let identity = stored_bond.identity();
storage::mixnodes().save(deps.storage, identity, &stored_bond)?;
// if this is a fresh mixnode - write 0 total delegation, otherwise, don't touch it since the node has just rebonded
if storage::TOTAL_DELEGATION
.may_load(deps.storage, identity)?
.is_none()
{
storage::TOTAL_DELEGATION.save(deps.storage, identity, &Uint128::zero())?;
}
mixnet_params_storage::increment_layer_count(deps.storage, stored_bond.layer)?;
Ok(Response::new())
}
pub fn try_remove_mixnode_on_behalf(
deps: DepsMut,
info: MessageInfo,
owner: String,
) -> Result<Response, ContractError> {
let proxy = info.sender;
_try_remove_mixnode(deps, &owner, Some(proxy))
}
pub fn try_remove_mixnode(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
_try_remove_mixnode(deps, info.sender.as_ref(), None)
}
pub(crate) fn _try_remove_mixnode(
deps: DepsMut,
owner: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = deps.api.addr_validate(owner)?;
// try to find the node of the sender
let mixnode_bond = match storage::mixnodes()
.idx
.owner
.item(deps.storage, owner.clone())?
{
Some(record) => record.1,
None => return Err(ContractError::NoAssociatedMixNodeBond { owner }),
};
if proxy != mixnode_bond.proxy {
return Err(ContractError::ProxyMismatch {
existing: mixnode_bond
.proxy
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
});
}
// send bonded funds back to the bond owner
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
amount: vec![mixnode_bond.pledge_amount()],
};
// remove the bond
storage::mixnodes().remove(deps.storage, mixnode_bond.identity())?;
// decrement layer count
mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?;
let mut response = Response::new()
.add_message(return_tokens)
.add_attribute("action", "unbond")
.add_attribute("mixnode_bond", mixnode_bond.to_string());
if let Some(proxy) = &proxy {
let msg = VestingContractExecuteMsg::TrackUnbondMixnode {
owner: owner.as_str().to_string(),
amount: mixnode_bond.pledge_amount,
};
let track_unbond_message = wasm_execute(proxy, &msg, coins(0, DENOM))?;
response = response.add_message(track_unbond_message);
}
Ok(response)
}
fn validate_mixnode_pledge(
mut pledge: Vec<Coin>,
minimum_pledge: Uint128,
) -> Result<Coin, ContractError> {
// check if anything was put as bond
if pledge.is_empty() {
return Err(ContractError::NoBondFound);
}
if pledge.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
// check that the denomination is correct
if pledge[0].denom != DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have at least MIXNODE_BOND coins in our pledge
if pledge[0].amount < minimum_pledge {
return Err(ContractError::InsufficientMixNodeBond {
received: pledge[0].amount.into(),
minimum: minimum_pledge.into(),
});
}
Ok(pledge.pop().unwrap())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::contract::{execute, query, INITIAL_MIXNODE_PLEDGE};
use crate::error::ContractError;
use crate::mixnodes::transactions::validate_mixnode_pledge;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::attr;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coins, BankMsg, Response};
use cosmwasm_std::{from_binary, Addr, Uint128};
use mixnet_contract::Layer;
use mixnet_contract::MixNode;
use mixnet_contract::{ExecuteMsg, LayerDistribution, PagedMixnodeResponse, QueryMsg};
#[test]
fn mixnode_add() {
let mut deps = test_helpers::init_contract();
// if we don't send enough funds
let insufficient_bond = Into::<u128>::into(INITIAL_MIXNODE_PLEDGE) - 1;
let info = mock_info("anyone", &coins(insufficient_bond, DENOM));
let (msg, _) = test_helpers::valid_bond_mixnode_msg("anyone");
// we are informed that we didn't send enough funds
let result = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
result,
Err(ContractError::InsufficientMixNodeBond {
received: insufficient_bond,
minimum: INITIAL_MIXNODE_PLEDGE.into(),
})
);
// no mixnode was inserted into the topology
let res = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetMixNodes {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedMixnodeResponse = from_binary(&res).unwrap();
assert_eq!(0, page.nodes.len());
// if we send enough funds
let info = mock_info("anyone", &test_helpers::good_mixnode_bond());
let (msg, identity) = test_helpers::valid_bond_mixnode_msg("anyone");
// we get back a message telling us everything was OK
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
// we can query topology and the new node is there
let query_response = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetMixNodes {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedMixnodeResponse = from_binary(&query_response).unwrap();
assert_eq!(1, page.nodes.len());
assert_eq!(
&MixNode {
identity_key: identity,
..test_helpers::mix_node_fixture()
},
page.nodes[0].mix_node()
);
// if there was already a mixnode bonded by particular user
let info = mock_info("foomper", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("foomper");
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let info = mock_info("foomper", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("foomper");
// it fails
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(Err(ContractError::AlreadyOwnsMixnode), execute_response);
// bonding fails if the user already owns a gateway
test_helpers::add_gateway(
"gateway-owner",
test_helpers::good_gateway_bond(),
deps.as_mut(),
);
let info = mock_info("gateway-owner", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("gateway-owner");
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(execute_response, Err(ContractError::AlreadyOwnsGateway));
// but after he unbonds it, it's all fine again
let info = mock_info("gateway-owner", &[]);
let msg = ExecuteMsg::UnbondGateway {};
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let info = mock_info("gateway-owner", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("gateway-owner");
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
// adding another node from another account, but with the same IP, should fail (or we would have a weird state). Is that right? Think about this, not sure yet.
// if we attempt to register a second node from the same address, should we get an error? It would probably be polite.
}
#[test]
fn adding_mixnode_without_existing_owner() {
let mut deps = test_helpers::init_contract();
let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond());
// before the execution the node had no associated owner
assert!(storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.is_none());
let (msg, identity) = test_helpers::valid_bond_mixnode_msg("mix-owner");
// it's all fine, owner is saved
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
assert_eq!(
&identity,
storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
}
#[test]
fn adding_mixnode_with_existing_owner() {
let mut deps = test_helpers::init_contract();
let identity = test_helpers::add_mixnode(
"mix-owner",
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
// request fails giving the existing owner address in the message
let info = mock_info("mix-owner-pretender", &test_helpers::good_mixnode_bond());
let msg = ExecuteMsg::BondMixnode {
mix_node: MixNode {
identity_key: identity,
..test_helpers::mix_node_fixture()
},
owner_signature: "foomp".to_string(),
};
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
Err(ContractError::DuplicateMixnode {
owner: Addr::unchecked("mix-owner")
}),
execute_response
);
}
#[test]
fn adding_mixnode_with_existing_unchanged_owner() {
let mut deps = test_helpers::init_contract();
test_helpers::add_mixnode(
"mix-owner",
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("mix-owner");
let res = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(Err(ContractError::AlreadyOwnsMixnode), res);
}
#[test]
fn adding_mixnode_updates_layer_distribution() {
let mut deps = test_helpers::init_contract();
assert_eq!(
LayerDistribution::default(),
mixnet_params_storage::LAYERS.load(&deps.storage).unwrap(),
);
test_helpers::add_mixnode("mix1", test_helpers::good_mixnode_bond(), deps.as_mut());
assert_eq!(
LayerDistribution {
layer1: 1,
..Default::default()
},
mixnet_params_storage::LAYERS.load(&deps.storage).unwrap()
);
}
#[test]
fn mixnode_remove() {
let mut deps = test_helpers::init_contract();
// try un-registering when no nodes exist yet
let info = mock_info("anyone", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
// we're told that there is no node for our address
assert_eq!(
result,
Err(ContractError::NoAssociatedMixNodeBond {
owner: Addr::unchecked("anyone")
})
);
// let's add a node owned by bob
test_helpers::add_mixnode("bob", test_helpers::good_mixnode_bond(), deps.as_mut());
// attempt to un-register fred's node, which doesn't exist
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
result,
Err(ContractError::NoAssociatedMixNodeBond {
owner: Addr::unchecked("fred")
})
);
// bob's node is still there
let nodes = test_helpers::get_mix_nodes(&mut deps);
assert_eq!(1, nodes.len());
assert_eq!("bob", nodes[0].owner().clone());
// add a node owned by fred
let fred_identity =
test_helpers::add_mixnode("fred", test_helpers::good_mixnode_bond(), deps.as_mut());
// let's make sure we now have 2 nodes:
assert_eq!(2, test_helpers::get_mix_nodes(&mut deps).len());
// un-register fred's node
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
let remove_fred = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
// we should see log messages come back showing an unbond message
let expected_attributes = vec![
attr("action", "unbond"),
attr(
"mixnode_bond",
format!(
"amount: {}{}, owner: fred, identity: {}",
INITIAL_MIXNODE_PLEDGE, DENOM, fred_identity
),
),
];
// we should see a funds transfer from the contract back to fred
let expected_message = BankMsg::Send {
to_address: String::from(info.sender),
amount: test_helpers::good_mixnode_bond(),
};
// run the executor and check that we got back the correct results
let expected = Response::new()
.add_attributes(expected_attributes)
.add_message(expected_message);
assert_eq!(remove_fred, expected);
// only 1 node now exists, owned by bob:
let mix_node_bonds = test_helpers::get_mix_nodes(&mut deps);
assert_eq!(1, mix_node_bonds.len());
assert_eq!(&Addr::unchecked("bob"), mix_node_bonds[0].owner());
}
#[test]
fn removing_mixnode_clears_ownership() {
let mut deps = test_helpers::init_contract();
let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond());
let (bond_msg, identity) = test_helpers::valid_bond_mixnode_msg("mix-owner");
execute(deps.as_mut(), mock_env(), info, bond_msg.clone()).unwrap();
assert_eq!(
&identity,
storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
let info = mock_info("mix-owner", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok());
assert!(storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.is_none());
// and since it's removed, it can be reclaimed
let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond());
assert!(execute(deps.as_mut(), mock_env(), info, bond_msg).is_ok());
assert_eq!(
&identity,
storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
}
#[test]
fn validating_mixnode_bond() {
// you must send SOME funds
let result = validate_mixnode_pledge(Vec::new(), INITIAL_MIXNODE_PLEDGE);
assert_eq!(result, Err(ContractError::NoBondFound));
// you must send at least 100 coins...
let mut bond = test_helpers::good_mixnode_bond();
bond[0].amount = INITIAL_MIXNODE_PLEDGE.checked_sub(Uint128::new(1)).unwrap();
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
assert_eq!(
result,
Err(ContractError::InsufficientMixNodeBond {
received: Into::<u128>::into(INITIAL_MIXNODE_PLEDGE) - 1,
minimum: INITIAL_MIXNODE_PLEDGE.into(),
})
);
// more than that is still fine
let mut bond = test_helpers::good_mixnode_bond();
bond[0].amount = INITIAL_MIXNODE_PLEDGE + Uint128::new(1);
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
assert!(result.is_ok());
// it must be sent in the defined denom!
let mut bond = test_helpers::good_mixnode_bond();
bond[0].denom = "baddenom".to_string();
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
let mut bond = test_helpers::good_mixnode_bond();
bond[0].denom = "foomp".to_string();
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
}
#[test]
fn choose_layer_mix_node() {
let mut deps = test_helpers::init_contract();
let alice_identity =
test_helpers::add_mixnode("alice", test_helpers::good_mixnode_bond(), deps.as_mut());
let bob_identity =
test_helpers::add_mixnode("bob", test_helpers::good_mixnode_bond(), deps.as_mut());
let bonded_mix_nodes = test_helpers::get_mix_nodes(&mut deps);
let alice_node = bonded_mix_nodes
.iter()
.find(|m| m.owner == "alice")
.cloned()
.unwrap();
let bob_node = bonded_mix_nodes
.iter()
.find(|m| m.owner == "bob")
.cloned()
.unwrap();
assert_eq!(alice_node.mix_node.identity_key, alice_identity);
assert_eq!(alice_node.layer, Layer::One);
assert_eq!(bob_node.mix_node.identity_key, bob_identity);
assert_eq!(bob_node.layer, mixnet_contract::Layer::Two);
}
}
File diff suppressed because it is too large Load Diff
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{Addr, Storage, Uint128};
use mixnet_contract::mixnode::DelegatorRewardParams;
use mixnet_contract::{
IdentityKey, IdentityKeyRef, PendingDelegatorRewarding, RewardingResult, RewardingStatus,
};
pub(crate) fn update_post_rewarding_storage(
storage: &mut dyn Storage,
mix_identity: IdentityKeyRef,
operator_reward: Uint128,
delegators_reward: Uint128,
) -> Result<(), ContractError> {
if operator_reward == Uint128::zero() && delegators_reward == Uint128::zero() {
return Ok(());
}
// update pledge
if operator_reward > Uint128::zero() {
mixnodes_storage::mixnodes().update(storage, mix_identity, |current_bond| {
match current_bond {
None => Err(ContractError::MixNodeBondNotFound {
identity: mix_identity.to_string(),
}),
Some(mut mixnode_bond) => {
mixnode_bond.pledge_amount.amount += operator_reward;
Ok(mixnode_bond)
}
}
})?;
}
// update total_delegation
if delegators_reward > Uint128::zero() {
mixnodes_storage::TOTAL_DELEGATION.update(storage, mix_identity, |current_total| {
match current_total {
None => Err(ContractError::MixNodeBondNotFound {
identity: mix_identity.to_string(),
}),
Some(current_total) => Ok(current_total + delegators_reward),
}
})?;
}
// update reward pool
storage::decr_reward_pool(storage, operator_reward + delegators_reward)?;
Ok(())
}
pub(crate) fn update_rewarding_status(
storage: &mut dyn Storage,
rewarding_interval_nonce: u32,
mix_identity: IdentityKey,
rewarding_results: RewardingResult,
next_start: Option<Addr>,
delegators_rewarding_params: DelegatorRewardParams,
) -> Result<(), ContractError> {
if let Some(next_start) = next_start {
storage::REWARDING_STATUS.save(
storage,
(rewarding_interval_nonce.into(), mix_identity),
&RewardingStatus::PendingNextDelegatorPage(PendingDelegatorRewarding {
running_results: rewarding_results,
next_start,
rewarding_params: delegators_rewarding_params,
}),
)?;
} else {
storage::REWARDING_STATUS.save(
storage,
(rewarding_interval_nonce.into(), mix_identity),
&RewardingStatus::Complete(rewarding_results),
)?;
}
Ok(())
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod helpers;
pub mod queries;
pub mod storage;
pub mod transactions;
+270
View File
@@ -0,0 +1,270 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use cosmwasm_std::Uint128;
use cosmwasm_std::{Deps, StdResult};
use mixnet_contract::{IdentityKey, MixnodeRewardingStatusResponse};
pub(crate) fn query_reward_pool(deps: Deps) -> StdResult<Uint128> {
storage::REWARD_POOL.load(deps.storage)
}
pub(crate) fn query_circulating_supply(deps: Deps) -> StdResult<Uint128> {
storage::circulating_supply(deps.storage)
}
pub(crate) fn query_rewarding_status(
deps: Deps,
mix_identity: IdentityKey,
rewarding_interval_nonce: u32,
) -> StdResult<MixnodeRewardingStatusResponse> {
let status = storage::REWARDING_STATUS.may_load(
deps.storage,
(rewarding_interval_nonce.into(), mix_identity),
)?;
Ok(MixnodeRewardingStatusResponse { status })
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
#[cfg(test)]
mod querying_for_rewarding_status {
use super::storage;
use super::*;
use crate::delegations::transactions::try_delegate_to_mixnode;
use crate::rewards::transactions::{
try_begin_mixnode_rewarding, try_finish_mixnode_rewarding, try_reward_mixnode,
try_reward_next_mixnode_delegators,
};
use config::defaults::DENOM;
use cosmwasm_std::{coin, Addr};
use mixnet_contract::{RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT};
#[test]
fn returns_empty_status_for_unrewarded_nodes() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let current_state = mixnet_params_storage::CONTRACT_STATE
.load(deps.as_mut().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_identity =
test_helpers::add_mixnode("bob", test_helpers::good_mixnode_bond(), deps.as_mut());
assert!(
query_rewarding_status(deps.as_ref(), node_identity.clone(), 1)
.unwrap()
.status
.is_none()
);
// node was rewarded but for different epoch
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
test_helpers::node_rewarding_params_fixture(100),
1,
)
.unwrap();
try_finish_mixnode_rewarding(deps.as_mut(), info.clone(), 1).unwrap();
assert!(query_rewarding_status(deps.as_ref(), node_identity, 2)
.unwrap()
.status
.is_none());
}
#[test]
fn returns_complete_status_for_fully_rewarded_node() {
// with single page
let mut deps = test_helpers::init_contract();
let mut env = mock_env();
let current_state = mixnet_params_storage::CONTRACT_STATE
.load(deps.as_mut().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_owner: Addr = Addr::unchecked("bob");
let node_identity = test_helpers::add_mixnode(
node_owner.as_str(),
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
env.block.height += storage::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
test_helpers::node_rewarding_params_fixture(100),
1,
)
.unwrap();
try_finish_mixnode_rewarding(deps.as_mut(), info.clone(), 1).unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap();
assert!(matches!(res.status, Some(RewardingStatus::Complete(..))));
match res.status.unwrap() {
RewardingStatus::Complete(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.operator_reward
);
assert_eq!(
RewardingResult::default().total_delegator_reward,
result.total_delegator_reward
);
}
_ => unreachable!(),
}
// with multiple pages
let node_owner: Addr = Addr::unchecked("alice");
let node_identity = test_helpers::add_mixnode(
node_owner.as_str(),
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT + 123 {
try_delegate_to_mixnode(
deps.as_mut(),
env.clone(),
mock_info(
&*format!("delegator{:04}", i),
&vec![coin(200_000000, DENOM)],
),
node_identity.clone(),
)
.unwrap();
}
env.block.height += storage::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 2).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
test_helpers::node_rewarding_params_fixture(100),
2,
)
.unwrap();
// rewards all pending
try_reward_next_mixnode_delegators(
deps.as_mut(),
info.clone(),
node_identity.to_string(),
2,
)
.unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 2).unwrap();
assert!(matches!(res.status, Some(RewardingStatus::Complete(..))));
match res.status.unwrap() {
RewardingStatus::Complete(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.operator_reward
);
assert_ne!(
RewardingResult::default().total_delegator_reward,
result.total_delegator_reward
);
}
_ => unreachable!(),
}
}
#[test]
fn returns_pending_next_delegator_page_status_when_there_are_more_delegators_to_reward() {
let mut deps = test_helpers::init_contract();
let mut env = mock_env();
let current_state = mixnet_params_storage::CONTRACT_STATE
.load(deps.as_mut().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_owner: Addr = Addr::unchecked("bob");
let node_identity = test_helpers::add_mixnode(
node_owner.as_str(),
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT + 123 {
try_delegate_to_mixnode(
deps.as_mut(),
env.clone(),
mock_info(
&*format!("delegator{:04}", i),
&vec![coin(200_000000, DENOM)],
),
node_identity.clone(),
)
.unwrap();
}
env.block.height += storage::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info,
node_identity.clone(),
test_helpers::node_rewarding_params_fixture(100),
1,
)
.unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap();
assert!(matches!(
res.status,
Some(RewardingStatus::PendingNextDelegatorPage(..))
));
match res.status.unwrap() {
RewardingStatus::PendingNextDelegatorPage(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.running_results.operator_reward
);
assert_ne!(
RewardingResult::default().total_delegator_reward,
result.running_results.total_delegator_reward
);
assert_eq!(
&*format!("delegator{:04}", MIXNODE_DELEGATORS_PAGE_LIMIT),
result.next_start
);
}
_ => unreachable!(),
}
}
}
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::ContractError;
use config::defaults::TOTAL_SUPPLY;
use cosmwasm_std::{StdResult, Storage, Uint128};
use cw_storage_plus::{Item, Map, U32Key};
use mixnet_contract::{IdentityKey, RewardingStatus};
pub(crate) const REWARD_POOL: Item<Uint128> = Item::new("pool");
pub(crate) const REWARDING_STATUS: Map<(U32Key, IdentityKey), RewardingStatus> = Map::new("rm");
// approximately 1 day (assuming 5s per block)
pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280;
// approximately 30min (assuming 5s per block)
pub(crate) const MAX_REWARDING_DURATION_IN_BLOCKS: u64 = 360;
#[allow(dead_code)]
pub fn incr_reward_pool(
amount: Uint128,
storage: &mut dyn Storage,
) -> Result<Uint128, ContractError> {
REWARD_POOL.update(storage, |mut current_pool| {
current_pool += amount;
Ok(current_pool)
})
}
pub fn decr_reward_pool(
storage: &mut dyn Storage,
amount: Uint128,
) -> Result<Uint128, ContractError> {
REWARD_POOL.update(storage, |current_pool| {
let stake = current_pool
.checked_sub(amount)
.map_err(|_| ContractError::OutOfFunds {
to_remove: amount.u128(),
reward_pool: current_pool.u128(),
})?;
Ok(stake)
})
}
pub fn circulating_supply(storage: &dyn Storage) -> StdResult<Uint128> {
let reward_pool = REWARD_POOL.load(storage)?;
Ok(Uint128::new(TOTAL_SUPPLY) - reward_pool)
}
-593
View File
@@ -1,593 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract::INITIAL_REWARD_POOL;
use crate::error::ContractError;
use crate::state::State;
use config::defaults::{DENOM, TOTAL_SUPPLY};
use cosmwasm_std::{Coin, StdResult, Storage, Uint128};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
};
use mixnet_contract::{
Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNode, MixNodeBond,
RawDelegationData, RewardingStatus, StateParams,
};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
// storage prefixes
// all of them must be unique and presumably not be a prefix of a different one
// keeping them as short as possible is also desirable as they are part of each stored key
// it's not as important for singletons, but is a nice optimisation for buckets
// singletons
const CONFIG_KEY: &[u8] = b"config";
const LAYER_DISTRIBUTION_KEY: &[u8] = b"layers";
const REWARD_POOL_PREFIX: &[u8] = b"pool";
// buckets
const PREFIX_MIXNODES: &[u8] = b"mn";
const PREFIX_MIXNODES_OWNERS: &[u8] = b"mo";
const PREFIX_GATEWAYS: &[u8] = b"gt";
const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go";
const PREFIX_MIX_DELEGATION: &[u8] = b"md";
const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm";
const PREFIX_REWARDED_MIXNODES: &[u8] = b"rm";
const PREFIX_TOTAL_DELEGATION: &[u8] = b"td";
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub(crate) struct StoredMixnodeBond {
pub bond_amount: Coin,
pub owner: Addr,
pub layer: Layer,
pub block_height: u64,
pub mix_node: MixNode,
pub profit_margin_percent: Option<u8>,
}
impl StoredMixnodeBond {
pub(crate) fn new(
bond_amount: Coin,
owner: Addr,
layer: Layer,
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
) -> Self {
StoredMixnodeBond {
bond_amount,
owner,
layer,
block_height,
mix_node,
profit_margin_percent,
}
}
pub(crate) fn attach_delegation(self, total_delegation: Uint128) -> MixNodeBond {
MixNodeBond {
total_delegation: Coin {
denom: self.bond_amount.denom.clone(),
amount: total_delegation,
},
bond_amount: self.bond_amount,
owner: self.owner,
layer: self.layer,
block_height: self.block_height,
mix_node: self.mix_node,
profit_margin_percent: self.profit_margin_percent,
}
}
pub(crate) fn identity(&self) -> &String {
&self.mix_node.identity_key
}
pub(crate) fn bond_amount(&self) -> Coin {
self.bond_amount.clone()
}
}
impl Display for StoredMixnodeBond {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"amount: {}, owner: {}, identity: {}",
self.bond_amount, self.owner, self.mix_node.identity_key
)
}
}
// Contract-level stuff
// TODO Unify bucket and mixnode storage functions
pub fn config(storage: &mut dyn Storage) -> Singleton<State> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<State> {
singleton_read(storage, CONFIG_KEY)
}
fn reward_pool(storage: &dyn Storage) -> ReadonlySingleton<Uint128> {
singleton_read(storage, REWARD_POOL_PREFIX)
}
pub fn mut_reward_pool(storage: &mut dyn Storage) -> Singleton<Uint128> {
singleton(storage, REWARD_POOL_PREFIX)
}
pub fn reward_pool_value(storage: &dyn Storage) -> Uint128 {
match reward_pool(storage).load() {
Ok(value) => value,
Err(_e) => Uint128(INITIAL_REWARD_POOL),
}
}
#[allow(dead_code)]
pub fn incr_reward_pool(
amount: Uint128,
storage: &mut dyn Storage,
) -> Result<Uint128, ContractError> {
let stake = reward_pool_value(storage).saturating_add(amount);
mut_reward_pool(storage).save(&stake)?;
Ok(stake)
}
pub fn decr_reward_pool(
amount: Uint128,
storage: &mut dyn Storage,
) -> Result<Uint128, ContractError> {
let stake = match reward_pool_value(storage).checked_sub(amount) {
Ok(stake) => stake,
Err(_e) => {
return Err(ContractError::OutOfFunds {
to_remove: amount.u128(),
reward_pool: reward_pool_value(storage).u128(),
})
}
};
mut_reward_pool(storage).save(&stake)?;
Ok(stake)
}
pub fn circulating_supply(storage: &dyn Storage) -> Uint128 {
let reward_pool = reward_pool_value(storage).u128();
Uint128(TOTAL_SUPPLY - reward_pool)
}
pub(crate) fn read_state_params(storage: &dyn Storage) -> StateParams {
// note: In any other case, I wouldn't have attempted to unwrap this result, but in here
// if we fail to load the stored state we would already be in the undefined behaviour land,
// so we better just blow up immediately.
config_read(storage).load().unwrap().params
}
pub fn layer_distribution(storage: &mut dyn Storage) -> Singleton<LayerDistribution> {
singleton(storage, LAYER_DISTRIBUTION_KEY)
}
pub fn layer_distribution_read(storage: &dyn Storage) -> ReadonlySingleton<LayerDistribution> {
singleton_read(storage, LAYER_DISTRIBUTION_KEY)
}
pub(crate) fn read_layer_distribution(storage: &dyn Storage) -> LayerDistribution {
// note: In any other case, I wouldn't have attempted to unwrap this result, but in here
// if we fail to load the stored state we would already be in the undefined behaviour land,
// so we better just blow up immediately.
layer_distribution_read(storage).load().unwrap()
}
pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
let mut distribution = layer_distribution(storage).load()?;
match layer {
Layer::Gateway => distribution.gateways += 1,
Layer::One => distribution.layer1 += 1,
Layer::Two => distribution.layer2 += 1,
Layer::Three => distribution.layer3 += 1,
}
layer_distribution(storage).save(&distribution)
}
pub fn decrement_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
let mut distribution = layer_distribution(storage).load()?;
// It can't possibly go below zero, if it does, it means there's a serious error in the contract logic
match layer {
Layer::Gateway => {
distribution.gateways = distribution
.gateways
.checked_sub(1)
.expect("tried to subtract from unsigned zero!")
}
Layer::One => {
distribution.layer1 = distribution
.layer1
.checked_sub(1)
.expect("tried to subtract from unsigned zero!")
}
Layer::Two => {
distribution.layer2 = distribution
.layer2
.checked_sub(1)
.expect("tried to subtract from unsigned zero!")
}
Layer::Three => {
distribution.layer3 = distribution
.layer3
.checked_sub(1)
.expect("tried to subtract from unsigned zero!")
}
};
layer_distribution(storage).save(&distribution)
}
// Mixnode-related stuff
pub(crate) fn mixnodes(storage: &mut dyn Storage) -> Bucket<StoredMixnodeBond> {
bucket(storage, PREFIX_MIXNODES)
}
pub(crate) fn mixnodes_read(storage: &dyn Storage) -> ReadonlyBucket<StoredMixnodeBond> {
bucket_read(storage, PREFIX_MIXNODES)
}
// owner address -> node identity
pub fn mixnodes_owners(storage: &mut dyn Storage) -> Bucket<IdentityKey> {
bucket(storage, PREFIX_MIXNODES_OWNERS)
}
pub fn mixnodes_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey> {
bucket_read(storage, PREFIX_MIXNODES_OWNERS)
}
pub fn total_delegation(storage: &mut dyn Storage) -> Bucket<Uint128> {
bucket(storage, PREFIX_TOTAL_DELEGATION)
}
pub fn total_delegation_read(storage: &dyn Storage) -> ReadonlyBucket<Uint128> {
bucket_read(storage, PREFIX_TOTAL_DELEGATION)
}
// we want to treat this bucket as a set so we don't really care about what type of data is being stored.
// I went with u8 as after serialization it takes only a single byte of space, while if a `()` was used,
// it would have taken 4 bytes (representation of 'null')
pub(crate) fn rewarded_mixnodes(
storage: &mut dyn Storage,
rewarding_interval_nonce: u32,
) -> Bucket<RewardingStatus> {
Bucket::multilevel(
storage,
&[
rewarding_interval_nonce.to_be_bytes().as_ref(),
PREFIX_REWARDED_MIXNODES,
],
)
}
pub(crate) fn rewarded_mixnodes_read(
storage: &dyn Storage,
rewarding_interval_nonce: u32,
) -> ReadonlyBucket<RewardingStatus> {
ReadonlyBucket::multilevel(
storage,
&[
rewarding_interval_nonce.to_be_bytes().as_ref(),
PREFIX_REWARDED_MIXNODES,
],
)
}
// helpers
pub(crate) fn read_mixnode_bond(
storage: &dyn Storage,
mix_identity: IdentityKeyRef,
) -> StdResult<Option<MixNodeBond>> {
let stored_bond = mixnodes_read(storage).may_load(mix_identity.as_bytes())?;
match stored_bond {
None => Ok(None),
Some(stored_bond) => {
let total_delegation =
total_delegation_read(storage).may_load(mix_identity.as_bytes())?;
Ok(Some(MixNodeBond {
bond_amount: stored_bond.bond_amount,
total_delegation: Coin {
denom: DENOM.to_owned(),
amount: total_delegation.unwrap_or_default(),
},
owner: stored_bond.owner,
layer: stored_bond.layer,
block_height: stored_bond.block_height,
mix_node: stored_bond.mix_node,
profit_margin_percent: stored_bond.profit_margin_percent,
}))
}
}
}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_mixnode_bond_amount(
storage: &dyn Storage,
identity: &[u8],
) -> StdResult<cosmwasm_std::Uint128> {
let bucket = mixnodes_read(storage);
let node = bucket.load(identity)?;
Ok(node.bond_amount.amount)
}
// Gateway-related stuff
pub fn gateways(storage: &mut dyn Storage) -> Bucket<GatewayBond> {
bucket(storage, PREFIX_GATEWAYS)
}
pub fn gateways_read(storage: &dyn Storage) -> ReadonlyBucket<GatewayBond> {
bucket_read(storage, PREFIX_GATEWAYS)
}
// owner address -> node identity
pub fn gateways_owners(storage: &mut dyn Storage) -> Bucket<IdentityKey> {
bucket(storage, PREFIX_GATEWAYS_OWNERS)
}
pub fn gateways_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey> {
bucket_read(storage, PREFIX_GATEWAYS_OWNERS)
}
// delegation related
pub fn all_mix_delegations_read<T>(storage: &dyn Storage) -> ReadonlyBucket<T>
where
T: Serialize + DeserializeOwned,
{
bucket_read(storage, PREFIX_MIX_DELEGATION)
}
pub fn mix_delegations<'a>(
storage: &'a mut dyn Storage,
mix_identity: IdentityKeyRef,
) -> Bucket<'a, RawDelegationData> {
Bucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()])
}
pub fn mix_delegations_read<'a>(
storage: &'a dyn Storage,
mix_identity: IdentityKeyRef,
) -> ReadonlyBucket<'a, RawDelegationData> {
ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()])
}
pub fn reverse_mix_delegations<'a>(storage: &'a mut dyn Storage, owner: &Addr) -> Bucket<'a, ()> {
Bucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()])
}
pub fn reverse_mix_delegations_read<'a>(
storage: &'a dyn Storage,
owner: &Addr,
) -> ReadonlyBucket<'a, ()> {
ReadonlyBucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()])
}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_gateway_bond(
storage: &dyn Storage,
identity: &[u8],
) -> StdResult<cosmwasm_std::Uint128> {
let bucket = gateways_read(storage);
let node = bucket.load(identity)?;
Ok(node.bond_amount.amount)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::identity_and_owner_to_bytes;
use crate::support::tests::helpers::{
gateway_bond_fixture, gateway_fixture, mix_node_fixture, stored_mixnode_bond_fixture,
};
use config::defaults::DENOM;
use cosmwasm_std::testing::{mock_dependencies, MockStorage};
use cosmwasm_std::{coin, Addr, Uint128};
use mixnet_contract::{Gateway, MixNode};
#[test]
fn mixnode_single_read_retrieval() {
let mut storage = MockStorage::new();
let bond1 = stored_mixnode_bond_fixture();
let bond2 = stored_mixnode_bond_fixture();
mixnodes(&mut storage).save(b"bond1", &bond1).unwrap();
mixnodes(&mut storage).save(b"bond2", &bond2).unwrap();
let res1 = mixnodes_read(&storage).load(b"bond1").unwrap();
let res2 = mixnodes_read(&storage).load(b"bond2").unwrap();
assert_eq!(bond1, res1);
assert_eq!(bond2, res2);
}
#[test]
fn gateway_single_read_retrieval() {
let mut storage = MockStorage::new();
let bond1 = gateway_bond_fixture();
let bond2 = gateway_bond_fixture();
gateways(&mut storage).save(b"bond1", &bond1).unwrap();
gateways(&mut storage).save(b"bond2", &bond2).unwrap();
let res1 = gateways_read(&storage).load(b"bond1").unwrap();
let res2 = gateways_read(&storage).load(b"bond2").unwrap();
assert_eq!(bond1, res1);
assert_eq!(bond2, res2);
}
#[test]
fn reading_mixnode_bond() {
let mut storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces an error if target mixnode doesn't exist
let res = read_mixnode_bond_amount(&storage, node_owner.as_bytes());
assert!(res.is_err());
// returns appropriate value otherwise
let bond_value = 1000;
let mixnode_bond = StoredMixnodeBond {
bond_amount: coin(bond_value, DENOM),
owner: node_owner.clone(),
layer: Layer::One,
block_height: 12_345,
mix_node: MixNode {
identity_key: node_identity.clone(),
..mix_node_fixture()
},
profit_margin_percent: Some(10),
};
mixnodes(&mut storage)
.save(node_identity.as_bytes(), &mixnode_bond)
.unwrap();
assert_eq!(
Uint128(bond_value),
read_mixnode_bond_amount(&storage, node_identity.as_bytes()).unwrap()
);
}
#[test]
#[test]
fn reading_gateway_bond() {
let mut storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces an error if target gateway doesn't exist
let res = read_gateway_bond(&storage, node_owner.as_bytes());
assert!(res.is_err());
// returns appropriate value otherwise
let bond_value = 1000;
let gateway_bond = GatewayBond {
bond_amount: coin(bond_value, DENOM),
owner: node_owner.clone(),
block_height: 12_345,
gateway: Gateway {
identity_key: node_identity.clone(),
..gateway_fixture()
},
};
gateways(&mut storage)
.save(node_identity.as_bytes(), &gateway_bond)
.unwrap();
assert_eq!(
Uint128(bond_value),
read_gateway_bond(&storage, node_identity.as_bytes()).unwrap()
);
}
#[test]
fn all_mixnode_delegations_read_retrieval() {
let mut deps = mock_dependencies(&[]);
let node_identity1: IdentityKey = "foo1".into();
let delegation_owner1 = Addr::unchecked("bar1");
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner2 = Addr::unchecked("bar2");
let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000);
let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000);
mix_delegations(&mut deps.storage, &node_identity1)
.save(delegation_owner1.as_bytes(), &raw_delegation1)
.unwrap();
mix_delegations(&mut deps.storage, &node_identity2)
.save(delegation_owner2.as_bytes(), &raw_delegation2)
.unwrap();
let res1 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
.load(&*identity_and_owner_to_bytes(
&node_identity1,
&delegation_owner1,
))
.unwrap();
let res2 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
.load(&*identity_and_owner_to_bytes(
&node_identity2,
&delegation_owner2,
))
.unwrap();
assert_eq!(raw_delegation1, res1);
assert_eq!(raw_delegation2, res2);
}
#[cfg(test)]
mod reverse_mix_delegations {
use super::*;
use crate::support::tests::helpers;
#[test]
fn reverse_mix_delegation_exists() {
let mut deps = helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
reverse_mix_delegations(&mut deps.storage, &delegation_owner)
.save(node_identity.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner)
.may_load(node_identity.as_bytes())
.unwrap()
.is_some(),
);
}
#[test]
fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() {
let mut deps = helpers::init_contract();
let node_identity1: IdentityKey = "foo1".into();
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner1 = Addr::unchecked("bar");
let delegation_owner2 = Addr::unchecked("bar2");
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
// add delegation for a different node
reverse_mix_delegations(&mut deps.storage, &delegation_owner1)
.save(node_identity2.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
// add delegation from a different owner
reverse_mix_delegations(&mut deps.storage, &delegation_owner2)
.save(node_identity1.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
}
}
}
+228
View File
@@ -0,0 +1,228 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::ContractError;
use crate::gateways::storage as gateways_storage;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{Addr, Deps, Storage};
use mixnet_contract::IdentityKeyRef;
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
if let Some(proxy) = &proxy {
address
.as_bytes()
.iter()
.zip(proxy.as_bytes())
.map(|(x, y)| x ^ y)
.collect()
} else {
address.as_bytes().to_vec()
}
}
// check if the target address has already bonded a mixnode or gateway,
// in either case, return an appropriate error
pub(crate) fn ensure_no_existing_bond(
storage: &dyn Storage,
sender: &Addr,
) -> Result<(), ContractError> {
if mixnodes_storage::mixnodes()
.idx
.owner
.item(storage, sender.clone())?
.is_some()
{
return Err(ContractError::AlreadyOwnsMixnode);
}
if gateways_storage::gateways()
.idx
.owner
.item(storage, sender.clone())?
.is_some()
{
return Err(ContractError::AlreadyOwnsGateway);
}
Ok(())
}
#[allow(unreachable_code)]
#[allow(unused_variables)]
pub(crate) fn validate_node_identity_signature(
deps: Deps,
owner: &Addr,
signature: String,
identity: IdentityKeyRef,
) -> Result<(), ContractError> {
return Ok(());
let owner_bytes = owner.as_bytes();
let mut identity_bytes = [0u8; 32];
let mut signature_bytes = [0u8; 64];
let identity_used_bytes = bs58::decode(identity)
.into(&mut identity_bytes)
.map_err(|err| ContractError::MalformedEd25519IdentityKey(err.to_string()))?;
let signature_used_bytes = bs58::decode(signature)
.into(&mut signature_bytes)
.map_err(|err| ContractError::MalformedEd25519Signature(err.to_string()))?;
if identity_used_bytes != 32 {
return Err(ContractError::MalformedEd25519IdentityKey(
"Too few bytes provided".into(),
));
}
if signature_used_bytes != 64 {
return Err(ContractError::MalformedEd25519Signature(
"Too few bytes provided".into(),
));
}
let res = deps
.api
.ed25519_verify(owner_bytes, &signature_bytes, &identity_bytes)
.map_err(cosmwasm_std::StdError::verification_err)?;
if !res {
Err(ContractError::InvalidEd25519Signature)
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::mock_dependencies;
use crypto::asymmetric::identity;
use rand_chacha::rand_core::SeedableRng;
#[test]
#[ignore]
fn validating_node_signature() {
let deps = mock_dependencies();
// since those tests are NOT compiled to wasm, we can use rng-related dependency
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let short_bs58 = "2SfEgZ4aQUr3HSwqE";
let long_bs58 = "g34PyULki9fc3FqKobj5wdVNCaWAt1M9oZowyyMFfWSCejxg7wt574piZVjqjFEN2UXsgZ56KTkKf3jnWD4DJ2Gsf7KXQAvptFfcYRrZHTjMVo3NXcBSNm3wDBKZWZURzp4Fixv";
let address1 = Addr::unchecked("some-dummy-address1");
let address2 = Addr::unchecked("some-dummy-address2");
let keypair1 = identity::KeyPair::new(&mut rng);
let keypair2 = identity::KeyPair::new(&mut rng);
let sig_addr1_key1 = keypair1
.private_key()
.sign(address1.as_bytes())
.to_base58_string();
let sig_addr2_key1 = keypair1
.private_key()
.sign(address2.as_bytes())
.to_base58_string();
let sig_addr1_key2 = keypair2
.private_key()
.sign(address1.as_bytes())
.to_base58_string();
assert_eq!(
Err(ContractError::MalformedEd25519IdentityKey(
"buffer provided to decode base58 encoded string into was too small".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key1.clone(),
long_bs58,
)
);
assert_eq!(
Err(ContractError::MalformedEd25519Signature(
"buffer provided to decode base58 encoded string into was too small".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
long_bs58.into(),
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::MalformedEd25519IdentityKey(
"Too few bytes provided".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key1.clone(),
short_bs58,
)
);
assert_eq!(
Err(ContractError::MalformedEd25519Signature(
"Too few bytes provided".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
short_bs58.into(),
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key1.clone(),
&keypair2.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr2_key1,
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address2,
sig_addr1_key1.clone(),
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key2,
&keypair1.public_key().to_base58_string(),
)
);
assert!(validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key1,
&keypair1.public_key().to_base58_string(),
)
.is_ok());
}
}
+5 -1
View File
@@ -1 +1,5 @@
pub mod tests;
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod helpers;
pub(crate) mod tests;
+142 -60
View File
@@ -1,42 +1,78 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod helpers {
pub mod test_helpers {
use super::*;
use crate::contract::{instantiate, INITIAL_MIXNODE_BOND};
use crate::contract::{instantiate, INITIAL_MIXNODE_PLEDGE};
use crate::contract::{
query, DEFAULT_SYBIL_RESISTANCE_PERCENT, EPOCH_REWARD_PERCENT, INITIAL_REWARD_POOL,
};
use crate::storage::StoredMixnodeBond;
use crate::transactions::{try_add_gateway, try_add_mixnode};
use crate::delegations::storage as delegations_storage;
use crate::gateways::transactions::try_add_gateway;
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::storage::StoredMixnodeBond;
use crate::mixnodes::transactions::try_add_mixnode;
use config::defaults::{DENOM, TOTAL_SUPPLY};
use cosmwasm_std::coin;
use cosmwasm_std::testing::mock_dependencies;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::testing::MockApi;
use cosmwasm_std::testing::MockQuerier;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
use cosmwasm_std::OwnedDeps;
use cosmwasm_std::{coin, Uint128};
use cosmwasm_std::{from_binary, DepsMut};
use cosmwasm_std::{Addr, StdResult, Storage};
use cosmwasm_std::{Empty, MemoryStorage};
use cw_storage_plus::PrimaryKey;
use mixnet_contract::mixnode::NodeRewardParams;
use mixnet_contract::{
Gateway, GatewayBond, InstantiateMsg, Layer, MixNode, MixNodeBond, PagedGatewayResponse,
PagedMixnodeResponse, QueryMsg, RawDelegationData,
Delegation, ExecuteMsg, Gateway, GatewayBond, IdentityKey, IdentityKeyRef, InstantiateMsg,
Layer, MixNode, MixNodeBond, PagedGatewayResponse, PagedMixnodeResponse, QueryMsg,
};
use rand::thread_rng;
pub(crate) fn valid_bond_mixnode_msg(sender: &str) -> (ExecuteMsg, IdentityKey) {
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let identity_key = keypair.public_key().to_base58_string();
(
ExecuteMsg::BondMixnode {
mix_node: MixNode {
identity_key: identity_key.clone(),
..test_helpers::mix_node_fixture()
},
owner_signature,
},
identity_key,
)
}
pub fn add_mixnode(sender: &str, stake: Vec<Coin>, deps: DepsMut) -> String {
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let info = mock_info(sender, &stake);
let key = format!("{}mixnode", sender);
let key = keypair.public_key().to_base58_string();
try_add_mixnode(
deps,
mock_env(),
info,
MixNode {
identity_key: key.clone(),
..helpers::mix_node_fixture()
..test_helpers::mix_node_fixture()
},
owner_signature,
)
.unwrap();
key
@@ -59,21 +95,44 @@ pub mod helpers {
page.nodes
}
pub fn add_gateway(
sender: &str,
stake: Vec<Coin>,
deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>,
) -> String {
pub(crate) fn valid_bond_gateway_msg(sender: &str) -> (ExecuteMsg, IdentityKey) {
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let identity_key = keypair.public_key().to_base58_string();
(
ExecuteMsg::BondGateway {
gateway: Gateway {
identity_key: identity_key.clone(),
..test_helpers::gateway_fixture()
},
owner_signature,
},
identity_key,
)
}
pub fn add_gateway(sender: &str, stake: Vec<Coin>, deps: DepsMut) -> String {
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let info = mock_info(sender, &stake);
let key = format!("{}gateway", sender);
let key = keypair.public_key().to_base58_string();
try_add_gateway(
deps.as_mut(),
deps,
mock_env(),
info,
Gateway {
identity_key: key.clone(),
..helpers::gateway_fixture()
..test_helpers::gateway_fixture()
},
owner_signature,
)
.unwrap();
key
@@ -97,12 +156,14 @@ pub mod helpers {
}
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies(&[]);
let msg = InstantiateMsg {};
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
rewarding_validator_address: config::defaults::REWARDING_VALIDATOR_ADDRESS.to_string(),
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
return deps;
deps
}
pub fn mix_node_fixture() -> MixNode {
@@ -114,36 +175,21 @@ pub mod helpers {
sphinx_key: "sphinx".to_string(),
identity_key: "identity".to_string(),
version: "0.10.0".to_string(),
profit_margin_percent: 10,
}
}
pub fn mixnode_bond_fixture() -> MixNodeBond {
let mix_node = MixNode {
host: "1.1.1.1".to_string(),
mix_port: 1789,
verloc_port: 1790,
http_api_port: 8000,
sphinx_key: "1234".to_string(),
identity_key: "aaaa".to_string(),
version: "0.10.0".to_string(),
};
MixNodeBond::new(
coin(50, DENOM),
Addr::unchecked("foo"),
Layer::One,
12_345,
mix_node,
None,
)
}
pub(crate) fn stored_mixnode_bond_fixture() -> StoredMixnodeBond {
pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::StoredMixnodeBond {
StoredMixnodeBond::new(
coin(50, DENOM),
Addr::unchecked("foo"),
Addr::unchecked(owner),
Layer::One,
12_345,
mix_node_fixture(),
MixNode {
identity_key: format!("id-{}", owner),
..mix_node_fixture()
},
None,
None,
)
}
@@ -154,28 +200,24 @@ pub mod helpers {
mix_port: 1789,
clients_port: 9000,
location: "Sweden".to_string(),
sphinx_key: "sphinx".to_string(),
identity_key: "identity".to_string(),
version: "0.10.0".to_string(),
}
}
pub fn gateway_bond_fixture() -> GatewayBond {
pub fn gateway_bond_fixture(owner: &str) -> GatewayBond {
let gateway = Gateway {
host: "1.1.1.1".to_string(),
mix_port: 1789,
clients_port: 9000,
location: "London".to_string(),
sphinx_key: "sphinx".to_string(),
identity_key: "identity".to_string(),
version: "0.10.0".to_string(),
identity_key: format!("id-{}", owner),
..gateway_fixture()
};
GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), 12_345, gateway)
}
pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData {
RawDelegationData::new(Uint128(amount), 42)
GatewayBond::new(
coin(50, DENOM),
Addr::unchecked(owner),
12_345,
gateway,
None,
)
}
pub fn query_contract_balance(
@@ -189,14 +231,14 @@ pub mod helpers {
pub fn good_mixnode_bond() -> Vec<Coin> {
vec![Coin {
denom: DENOM.to_string(),
amount: INITIAL_MIXNODE_BOND,
amount: INITIAL_MIXNODE_PLEDGE,
}]
}
pub fn good_gateway_bond() -> Vec<Coin> {
vec![Coin {
denom: DENOM.to_string(),
amount: INITIAL_MIXNODE_BOND,
amount: INITIAL_MIXNODE_PLEDGE,
}]
}
@@ -205,10 +247,50 @@ pub mod helpers {
NodeRewardParams::new(
(INITIAL_REWARD_POOL / 100) * EPOCH_REWARD_PERCENT as u128,
50 as u128,
25 as u128,
0,
TOTAL_SUPPLY - INITIAL_REWARD_POOL,
uptime,
DEFAULT_SYBIL_RESISTANCE_PERCENT,
true,
10,
)
}
// currently not used outside tests
pub(crate) fn read_mixnode_pledge_amount(
storage: &dyn Storage,
identity: IdentityKeyRef,
) -> StdResult<cosmwasm_std::Uint128> {
let node = mixnodes_storage::mixnodes().load(storage, identity)?;
Ok(node.pledge_amount.amount)
}
pub(crate) fn save_dummy_delegation(
storage: &mut dyn Storage,
mix: impl Into<String>,
owner: impl Into<String>,
) {
let delegation = Delegation {
owner: Addr::unchecked(owner.into()),
node_identity: mix.into(),
amount: coin(12345, DENOM),
block_height: 12345,
proxy: None,
};
delegations_storage::delegations()
.save(storage, delegation.storage_key().joined_key(), &delegation)
.unwrap();
}
pub(crate) fn read_delegation(
storage: &dyn Storage,
mix: impl Into<String>,
owner: impl Into<String>,
) -> Option<Delegation> {
delegations_storage::delegations()
.may_load(storage, (mix.into(), owner.into()).joined_key())
.unwrap()
}
}
File diff suppressed because it is too large Load Diff
+908
View File
@@ -0,0 +1,908 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "az"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
]
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "bytemuck"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "config"
version = "0.1.0"
dependencies = [
"handlebars",
"humantime-serde",
"network-defaults",
"serde",
"toml",
"url",
]
[[package]]
name = "const-oid"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
[[package]]
name = "cosmwasm-crypto"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
"k256",
"rand_core 0.5.1",
"thiserror",
]
[[package]]
name = "cosmwasm-derive"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"base64",
"cosmwasm-crypto",
"cosmwasm-derive",
"schemars",
"serde",
"serde-json-wasm",
"thiserror",
"uint",
]
[[package]]
name = "cosmwasm-storage"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"cosmwasm-std",
"serde",
]
[[package]]
name = "cpufeatures"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-bigint"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
dependencies = [
"generic-array 0.14.4",
"rand_core 0.6.3",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array 0.14.4",
"subtle",
]
[[package]]
name = "curve25519-dalek"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest 0.9.0",
"rand_core 0.5.1",
"subtle",
"zeroize",
]
[[package]]
name = "der"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
dependencies = [
"const-oid",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "dyn-clone"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
[[package]]
name = "ecdsa"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
dependencies = [
"der",
"elliptic-curve",
"hmac",
"signature",
]
[[package]]
name = "ed25519-zebra"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
dependencies = [
"curve25519-dalek",
"hex",
"rand_core 0.5.1",
"serde",
"sha2",
"thiserror",
]
[[package]]
name = "elliptic-curve"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
dependencies = [
"crypto-bigint",
"ff",
"generic-array 0.14.4",
"group",
"pkcs8",
"rand_core 0.6.3",
"subtle",
"zeroize",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "ff"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
dependencies = [
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "fixed"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
dependencies = [
"az",
"bytemuck",
"half",
"typenum",
]
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "group"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
dependencies = [
"ff",
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "handlebars"
version = "3.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3"
dependencies = [
"log",
"pest",
"pest_derive",
"quick-error",
"serde",
"serde_json",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-literal"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
[[package]]
name = "hmac"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac",
"digest 0.9.0",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "humantime-serde"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
dependencies = [
"humantime",
"serde",
]
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "k256"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
dependencies = [
"cfg-if",
"ecdsa",
"elliptic-curve",
"sha2",
]
[[package]]
name = "libc"
version = "0.2.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mixnet-contract"
version = "0.1.0"
dependencies = [
"az",
"cosmwasm-std",
"fixed",
"log",
"network-defaults",
"schemars",
"serde",
"serde_repr",
"thiserror",
]
[[package]]
name = "network-defaults"
version = "0.1.0"
dependencies = [
"hex-literal",
"serde",
"time",
"url",
]
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
"maplit",
"pest",
"sha-1",
]
[[package]]
name = "pkcs8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
dependencies = [
"der",
"spki",
]
[[package]]
name = "proc-macro2"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "schemars"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271ac0c667b8229adf70f0f957697c96fafd7486ab7481e15dc5e45e3e6a4368"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ebda811090b257411540779860bc09bf321bc587f58d2c5864309d1566214e7"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
]
[[package]]
name = "serde"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-json-wasm"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_derive_internals"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "signature"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4"
dependencies = [
"digest 0.9.0",
"rand_core 0.6.3",
]
[[package]]
name = "spki"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
dependencies = [
"der",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
dependencies = [
"libc",
"time-macros",
]
[[package]]
name = "time-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
[[package]]
name = "tinyvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "typenum"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uint"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
dependencies = [
"byteorder",
"crunchy",
"hex",
"static_assertions",
]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]]
name = "unicode-normalization"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "url"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "vesting-contracts"
version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cosmwasm-storage",
"mixnet-contract",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "zeroize"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "vesting-contract"
version = "0.1.0"
authors = ["Drazen Urch <durch@users.noreply.github.com>"]
edition = "2018"
exclude = [
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
"contract.wasm",
"hash.txt",
]
[lib]
crate-type = ["cdylib", "rlib"]
[features]
# for more explicit tests, cargo test --features=backtraces
backtraces = ["cosmwasm-std/backtraces"]
[dependencies]
mixnet-contract = { path = "../../common/mixnet-contract" }
config = { path = "../../common/config" }
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
cosmwasm-std = { version = "1.0.0-beta2", features = ["iterator"]}
cw-storage-plus = { version = "0.10.3", features = ["iterator"] }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
+2
View File
@@ -0,0 +1,2 @@
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
+11
View File
@@ -0,0 +1,11 @@
## Nym vesting contract
1. Initial vesting tokens are deposited and assigned to existing addresses via `CreatePeriodicVestingAccount`
2. Admin account can then delegate vested and unvested tokens to mixnodes on behalf of vesting accounts
3. Vesting accounts can withdraw vested and undelegated (spendable) coins to their addresses
### Vesting coin delegation flow
![vesting-coin-delegation](images/vesting-coin-delegation.png)
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

+403
View File
@@ -0,0 +1,403 @@
use crate::errors::ContractError;
use crate::messages::{ExecuteMsg, InitMsg, QueryMsg};
use crate::storage::account_from_address;
use crate::traits::{
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
};
use crate::vesting::{populate_vesting_periods, Account};
use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM};
use cosmwasm_std::{
entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse,
Response, Timestamp, Uint128,
};
use mixnet_contract::{Gateway, IdentityKey, MixNode};
// We're using a 24 month vesting period with 3 months sub-periods.
// There are 8 three month periods in two years
// and duration of a single period is 30 days.
pub const NUM_VESTING_PERIODS: usize = 8;
pub const VESTING_PERIOD: u64 = 3 * 30 * 86400;
// Address of the account set to be contract admin
pub const ADMIN_ADDRESS: &str = "admin";
#[entry_point]
pub fn instantiate(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: InitMsg,
) -> Result<Response, ContractError> {
Ok(Response::default())
}
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::DelegateToMixnode { mix_identity } => {
try_delegate_to_mixnode(mix_identity, info, env, deps)
}
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
try_undelegate_from_mixnode(mix_identity, info, deps)
}
ExecuteMsg::CreateAccount {
address,
start_time,
} => try_create_periodic_vesting_account(&address, start_time, info, env, deps),
ExecuteMsg::WithdrawVestedCoins { amount } => {
try_withdraw_vested_coins(amount, env, info, deps)
}
ExecuteMsg::TrackUndelegation {
owner,
mix_identity,
amount,
} => try_track_undelegation(&owner, mix_identity, amount, info, deps),
ExecuteMsg::BondMixnode {
mix_node,
owner_signature,
} => try_bond_mixnode(mix_node, owner_signature, info, env, deps),
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
try_track_unbond_mixnode(&owner, amount, info, deps)
}
ExecuteMsg::BondGateway {
gateway,
owner_signature,
} => try_bond_gateway(gateway, owner_signature, info, env, deps),
ExecuteMsg::UnbondGateway {} => try_unbond_gateway(info, deps),
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
try_track_unbond_gateway(&owner, amount, info, deps)
}
}
}
pub fn try_bond_gateway(
gateway: Gateway,
owner_signature: String,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
let pledge = validate_funds(&info.funds)?;
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage)
}
pub fn try_unbond_gateway(info: MessageInfo, deps: DepsMut) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_unbond_gateway(deps.storage)
}
pub fn try_track_unbond_gateway(
owner: &str,
amount: Coin,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS {
return Err(ContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(owner, deps.storage, deps.api)?;
account.try_track_unbond_gateway(amount, deps.storage)?;
Ok(Response::default())
}
pub fn try_bond_mixnode(
mix_node: MixNode,
owner_signature: String,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
let pledge = validate_funds(&info.funds)?;
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_bond_mixnode(mix_node, owner_signature, pledge, &env, deps.storage)
}
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_unbond_mixnode(deps.storage)
}
pub fn try_track_unbond_mixnode(
owner: &str,
amount: Coin,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS {
return Err(ContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(owner, deps.storage, deps.api)?;
account.try_track_unbond_mixnode(amount, deps.storage)?;
Ok(Response::default())
}
pub fn try_withdraw_vested_coins(
amount: Coin,
env: Env,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
let address = info.sender.clone();
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
if amount.amount <= spendable_coins.amount {
let new_balance = account
.load_balance(deps.storage)?
.u128()
.saturating_sub(amount.amount.u128());
account.save_balance(Uint128::new(new_balance), deps.storage)?;
let send_tokens = BankMsg::Send {
to_address: address.as_str().to_string(),
amount: vec![amount],
};
Ok(Response::new()
.add_attribute("action", "whitdraw")
.add_message(send_tokens))
} else {
Err(ContractError::InsufficientSpendable(
address.as_str().to_string(),
spendable_coins.amount.u128(),
))
}
}
fn try_track_undelegation(
address: &str,
mix_identity: IdentityKey,
amount: Coin,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS {
return Err(ContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(address, deps.storage, deps.api)?;
account.track_undelegation(mix_identity, amount, deps.storage)?;
Ok(Response::default())
}
fn try_delegate_to_mixnode(
mix_identity: IdentityKey,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
let amount = validate_funds(&info.funds)?;
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage)
}
fn try_undelegate_from_mixnode(
mix_identity: IdentityKey,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_undelegate_from_mixnode(mix_identity, deps.storage)
}
fn try_create_periodic_vesting_account(
address: &str,
start_time: Option<u64>,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
if info.sender != ADMIN_ADDRESS {
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
}
let coin = validate_funds(&info.funds)?;
let address = deps.api.addr_validate(address)?;
let start_time = start_time.unwrap_or_else(|| env.block.time.seconds());
let periods = populate_vesting_periods(start_time, NUM_VESTING_PERIODS);
Account::new(
address,
coin,
Timestamp::from_seconds(start_time),
periods,
deps.storage,
)?;
Ok(Response::default())
}
#[entry_point]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::LockedCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_locked_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::SpendableCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_spendable_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetVestedCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_vested_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetVestingCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_vesting_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetStartTime {
vesting_account_address,
} => to_binary(&try_get_start_time(&vesting_account_address, deps)?),
QueryMsg::GetEndTime {
vesting_account_address,
} => to_binary(&try_get_end_time(&vesting_account_address, deps)?),
QueryMsg::GetOriginalVesting {
vesting_account_address,
} => to_binary(&try_get_original_vesting(&vesting_account_address, deps)?),
QueryMsg::GetDelegatedFree {
block_time,
vesting_account_address,
} => to_binary(&try_get_delegated_free(
block_time,
&vesting_account_address,
env,
deps,
)?),
QueryMsg::GetDelegatedVesting {
block_time,
vesting_account_address,
} => to_binary(&try_get_delegated_vesting(
block_time,
&vesting_account_address,
env,
deps,
)?),
};
Ok(query_res?)
}
pub fn try_get_locked_coins(
vesting_account_address: &str,
block_time: Option<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.locked_coins(block_time, &env, deps.storage)
}
pub fn try_get_spendable_coins(
vesting_account_address: &str,
block_time: Option<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.spendable_coins(block_time, &env, deps.storage)
}
pub fn try_get_vested_coins(
vesting_account_address: &str,
block_time: Option<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_vested_coins(block_time, &env)
}
pub fn try_get_vesting_coins(
vesting_account_address: &str,
block_time: Option<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_vesting_coins(block_time, &env)
}
pub fn try_get_start_time(
vesting_account_address: &str,
deps: Deps,
) -> Result<Timestamp, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
Ok(account.get_start_time())
}
pub fn try_get_end_time(
vesting_account_address: &str,
deps: Deps,
) -> Result<Timestamp, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
Ok(account.get_end_time())
}
pub fn try_get_original_vesting(
vesting_account_address: &str,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
Ok(account.get_original_vesting())
}
pub fn try_get_delegated_free(
block_time: Option<Timestamp>,
vesting_account_address: &str,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_delegated_free(block_time, &env, deps.storage)
}
pub fn try_get_delegated_vesting(
block_time: Option<Timestamp>,
vesting_account_address: &str,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_delegated_vesting(block_time, &env, deps.storage)
}
fn validate_funds(funds: &[Coin]) -> Result<Coin, ContractError> {
if funds.is_empty() || funds[0].amount.is_zero() {
return Err(ContractError::EmptyFunds);
}
if funds.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
if funds[0].denom != DENOM {
return Err(ContractError::WrongDenom(
funds[0].denom.clone(),
DENOM.to_string(),
));
}
Ok(funds[0].clone())
}
+41
View File
@@ -0,0 +1,41 @@
use cosmwasm_std::{Addr, StdError};
use mixnet_contract::IdentityKey;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Account does not exist - {0}")]
NoAccountForAddress(String),
#[error("Only admin can perform this action, {0} is not admin")]
NotAdmin(String),
#[error("Balance not found for existing account ({0}), this is a bug")]
NoBalanceForAddress(String),
#[error("Insufficient balance for address {0} -> {1}")]
InsufficientBalance(String, u128),
#[error("Insufficient spendable balance for address {0} -> {1}")]
InsufficientSpendable(String, u128),
#[error(
"Only delegation owner can perform delegation actions, {0} is not the delegation owner"
)]
NotDelegate(String),
#[error("Total vesting amount is inprobably low -> {0}, this is likely an error")]
ImprobableVestingAmount(u128),
#[error("Address {0} has already bonded a node")]
AlreadyBonded(String),
#[error("Recieved empty funds vector")]
EmptyFunds,
#[error("Recieved wrong denom: {0}, expected {1}")]
WrongDenom(String, String),
#[error("Recieved multiple denoms, expected 1")]
MultipleDenoms,
#[error("No delegations found for account {0}, mix_identity {1}")]
NoSuchDelegation(Addr, IdentityKey),
#[error("Only mixnet contract can perform this operation, got {0}")]
NotMixnetContract(Addr),
#[error("Calculation underflowed")]
Underflow,
#[error("No bond found for account {0}")]
NoBondFound(String),
}
+7
View File
@@ -0,0 +1,7 @@
pub mod contract;
mod errors;
pub mod messages;
mod storage;
mod support;
mod traits;
mod vesting;
+88
View File
@@ -0,0 +1,88 @@
use cosmwasm_std::{Coin, Timestamp};
use mixnet_contract::IdentityKey;
use mixnet_contract::{Gateway, MixNode};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InitMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
DelegateToMixnode {
mix_identity: IdentityKey,
},
UndelegateFromMixnode {
mix_identity: IdentityKey,
},
CreateAccount {
address: String,
start_time: Option<u64>,
},
WithdrawVestedCoins {
amount: Coin,
},
TrackUndelegation {
owner: String,
mix_identity: IdentityKey,
amount: Coin,
},
BondMixnode {
mix_node: MixNode,
owner_signature: String,
},
UnbondMixnode {},
TrackUnbondMixnode {
owner: String,
amount: Coin,
},
BondGateway {
gateway: Gateway,
owner_signature: String,
},
UnbondGateway {},
TrackUnbondGateway {
owner: String,
amount: Coin,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
LockedCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
SpendableCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetVestedCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetVestingCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetStartTime {
vesting_account_address: String,
},
GetEndTime {
vesting_account_address: String,
},
GetOriginalVesting {
vesting_account_address: String,
},
GetDelegatedFree {
block_time: Option<Timestamp>,
vesting_account_address: String,
},
GetDelegatedVesting {
block_time: Option<Timestamp>,
vesting_account_address: String,
},
}
+30
View File
@@ -0,0 +1,30 @@
use crate::errors::ContractError;
use crate::vesting::Account;
use cosmwasm_std::{Addr, Api, Storage};
use cw_storage_plus::Map;
const ACCOUNTS: Map<Addr, Account> = Map::new("acc");
pub fn save_account(account: &Account, storage: &mut dyn Storage) -> Result<(), ContractError> {
Ok(ACCOUNTS.save(storage, account.address(), account)?)
}
pub fn load_account(
address: &Addr,
storage: &dyn Storage,
) -> Result<Option<Account>, ContractError> {
Ok(ACCOUNTS.may_load(storage, address.to_owned())?)
}
fn validate_account(address: &Addr, storage: &dyn Storage) -> Result<Account, ContractError> {
load_account(address, storage)?
.ok_or_else(|| ContractError::NoAccountForAddress(address.as_str().to_string()))
}
pub fn account_from_address(
address: &str,
storage: &dyn Storage,
api: &dyn Api,
) -> Result<Account, ContractError> {
validate_account(&api.addr_validate(address)?, storage)
}
+1
View File
@@ -0,0 +1 @@
pub mod tests;

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