Compare commits

...

39 Commits

Author SHA1 Message Date
tommy 8f5dd00027 minor changes 2022-08-04 13:41:53 +02:00
tommy 84c43ebf54 Draft - Validator CLI
- validator  binary - to enable easy to use commands on the network
- contains all operations (vesting / normal)
2022-08-04 13:38:59 +02:00
Dave Hrycyszyn b957b939cf Typo fix 2022-08-04 11:01:36 +01:00
Bogdan-Ștefan Neacşu 9c19ae322d Bump regex version to fix dependabot (#1488) 2022-08-03 12:45:21 +03:00
Bogdan-Ștefan Neacşu 07893828d8 Fix NC filter for domains suffix-only domains (#1487)
* Fix NC filter for domains suffix-only domains

* Update CHANGELOG

* Fix unit test for filter

Some domains might be composed of the suffix only.

There are no nonsense domains, as they can be defined even on the local
machine. The underlying library doesn't resolve them, but rather uses a
fixed list of public suffixes to assess the domains.

* Fix clippy
2022-08-03 11:58:20 +03:00
Pierre Dommerc 1167f50543 [Wallet] move Receive page in modal (#1484)
* feat(wallet): move receive page in modal

* feat(wallet-receive): some ui work

* feat(wallet): simple modal component

show or not the Ok button based on onOk props

* feat(wallet): fix sx props type imports
2022-08-02 12:04:47 +02:00
Drazen Urch ba1818a903 Chitchat example (#1464)
* Chitchat example

* Cleanup
2022-08-01 14:19:04 +02:00
Bogdan-Ștefan Neacşu e631219a73 explorer-api: handle SIGTERM (#1482)
* explorer-api: handle SIGTERM

* Update CHANGELOG
2022-08-01 13:30:31 +03:00
rachyandco 207c6cf2c7 Correcting a typo (#1475)
Co-authored-by: Rachyandco <alexis@nymtech.net>
2022-07-29 09:13:07 +01:00
Drazen Urch c5ece97872 Stake inflation mitigations (#1480)
* Return Err from compound transactions

* Remove malicious nodes migration

* Reduce total delegation, before adding to it

* Blacklist malicious nodes, prevent future bonding

* Blacklisted gets no reward, enable compound

* Add GetBlacklistedNodes message

* Rebase on develop

* Remove TODO
2022-07-28 15:19:31 +02:00
Bogdan-Ștefan Neacșu 8a2c95d044 Mixnode option doc fix 2022-07-26 15:00:27 +03:00
Bogdan-Ștefan Neacşu ba5e3d4efa Remove service entries instead of zeroing them (#1479) 2022-07-26 11:53:10 +03:00
Bogdan-Ștefan Neacşu c81623a61a Add gateway id in the gateway stats (#1478)
* Add gateway id in the gateway stats

* Update CHANGELOG
2022-07-25 16:59:45 +03:00
Bogdan-Ștefan Neacşu 8bb42c2b1b Add migration code for mixnet and vesting contracts (#1477) 2022-07-25 14:05:22 +03:00
Mark Sinclair 33e161bd59 GitHub Actions: make explorer deployment only a manual step 2022-07-22 12:19:58 +01:00
Mark Sinclair 0233499036 GitHub Actions: add prod deploy step for Network Explorer UI 2022-07-22 10:34:20 +01:00
Mark Sinclair a059a29173 nym-connect: update CHANGELOG and bump version to 1.0.1 2022-07-22 10:15:18 +01:00
Mark Sinclair 83c3398570 nym-connect: copy changes 2022-07-22 10:09:13 +01:00
Fouad 93f931459a fix type update issues with actions (bonding, delegating etc.) (#1469) 2022-07-22 09:40:06 +01:00
Bogdan-Ștefan Neacşu 5a7b19aeb6 Nym connect use config from env (mainnet defaulted (#1471) 2022-07-21 17:03:14 +03:00
Bogdan-Ștefan Neacşu b901655591 Fix message deserialization in socks5 client (#1470) 2022-07-21 15:36:38 +03:00
Drazen Urch a9fdbccb82 Universal compound rewards message that anyone can call (#1387) 2022-07-21 10:51:33 +02:00
Bogdan-Ștefan Neacşu 9ca3f69aa8 Feature/config from env (#1463)
* Clients use env

* Explorer api uses env

* Mainnet and qa env files

* Set CONFIGURED on the mainnet defaulting

* Gateway uses env

* Mixnode uses env

* Wallet error simplification

* Network requester takes only mainnet client address

* Validator api uses env

* Mixnet contract uses denom from instantiate

* Vesting contract uses denom from instantiate

* More contract test refactoring

* Coconut bandwidth contract uses denom from instantiate

* Bandwidth claim contract uses denom from instantiate and remove from Cargos

* More remove from Cargos and one missed DEFAULT_NETWORK

* Refactor some other missed places

* Minor fixes

* Test and clippy fixes

* Update CHANGELOG
2022-07-20 13:16:37 +03:00
Fouad c485934b06 Network Explorer: Mixnode filters (#1460)
* add filters UI

* use filter schema

* filter mixnode based on selected filters

* only show filters on the mixnode page

* use base api to get all mixnodes to avoid setting mixnodes in state

* prevent additional request when status changes

* create isMobile hook
2022-07-18 15:18:39 +01:00
Bogdan-Ștefan Neacşu d62e13c932 Feature/coconut double spend prev (#1457)
* Add spend credential endpoint to coconut bandwidth contract

* Store spent credentials support

* Add query endpoint for spent credentials

* Proposals allowed only from special (contract) address

* Include check for admin in tests

* Create proposal from CBC

* Refactor into coconut integration tests

* Create proposal with spend credential integration test

* Resolve mixnet warnings

* Refactor to re-enable build

* Call CBC from gateway and remove validator-api workaround

* Include migration for the first deployment of multisig

* Fix bug in proposal id parsing

* Remove more validator-api create proposal code

* Check for InProgress status of credential

* Check the proposed voucher value

* Unwrapping cosmos msg from gateway

* Improve error message

* More nit fixing

* Test getting validator api cosmos address endpoint

* Refactor to prepare for distributed comm channel

* Refactor coconut e2e test for reuse

* Verification of cred endpoint test

* Update CHANGELOG
2022-07-18 12:37:07 +03:00
Fouad 47f7a5f795 Feature/changing wallet currency types frontend work (#1455)
* Introduced concept of denom details

No longer exposing plain 'DENOM'

Denom registration + conversion

Generating typescript type for DecCoin

'New' API on 'send'

Further WIP work on transforming usages of MajorCurrencyAmount into DecCoin

Further replacements of MajorCurrencyAmount into DecCoin

Attempt at dec-coinifying get_all_mix_delegations

Finished purge of MajorCurrencyAmount

Display for Fee

More unification for conversion methods

Fixed up tests and made clippy happier

Minor post-merge fixes

Removed explicit Arc and RwLock from all tauri commands

Fixed conversion to display coin

More type-restrictive exported denom type

Regenerated rust => ts types

* post-rebase fixes

* update frontend

* fix lint errors

* Adjusted Display implementation of DecCoin to include space between amount and denom

* Adding separate base and display denoms for account

* Fixed account constructor

* Using CurrencyDenom for display_mix_denom

* uppercase denom on frontend

* Changed AutoFeeGrant constructor

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2022-07-15 15:30:40 +01:00
Raphaël Walther f29200431f Amended nightly build trigger 2022-07-15 13:37:43 +02:00
Bogdan-Ștefan Neacşu 0912627e1f Update cosmrs in lock file, so it points to a valid git hash (#1459) 2022-07-15 14:17:58 +03:00
Raphaël Walther 70fdcc9be0 Added nightly build trigger 2022-07-15 13:12:29 +02:00
Mark Sinclair 90da6f152b typescript validator client: add denom argument to constructor (#1458)
* typescript validator client: add `denom` argument to constructor and a simple test for querying a balance.
Add to yarn workspaces.

* Update CHANGELOG
2022-07-14 15:25:01 +01:00
Bogdan-Ștefan Neacşu ad547e516a Feature/fee grant (#1419)
* Temporarily point cosmrs to PR branch

* Expose grant allowance in client

* Move functions in coconut verifier

* Do execute from gateway

* Explicit fee payment by granter

* Include revoke grant after finishing with the op

* Gateway checks the proposal content before proceeding

* Clippy fixes

* CHANGELOG update
2022-07-14 12:24:46 +03:00
Gala 61c0092f27 Merge pull request #1442 from nymtech/300-fix-ne-validarors-link
using a tag instead of link component for external link
2022-07-12 16:08:22 +02:00
Gala 851b80aaab 261 ne tooltip refactor (#1390)
* creating tooltip component

* using the tooltip component in the node list

* test another storie

* fixing text color in the story
2022-07-12 14:46:48 +01:00
Fouad 1397662fc9 upgrade tauri to latest stable version (#1446)
* upgrade tauri to latest stable version
2022-07-12 14:40:05 +01:00
Raphaël Walther 1847c8fe73 Added env file for qa 2022-07-12 13:59:00 +02:00
Mark Sinclair 8ffe3adf0d Update CHANGELOG.md 2022-07-11 14:48:58 +01:00
Mark Sinclair 3e517b461c nym-wallet: release v1.0.7 with dark mode and new gas fee simulations 2022-07-11 14:45:52 +01:00
Gala 90d8e5305f some refactor 2022-07-06 13:35:54 +02:00
Gala 81a678f9e2 using a tag instead of link component for external link 2022-07-06 10:52:12 +02:00
385 changed files with 10809 additions and 6688 deletions
+12
View File
@@ -1,6 +1,7 @@
name: CI for Network Explorer
on:
workflow_dispatch:
push:
paths:
- 'explorer/**'
@@ -75,3 +76,14 @@ jobs:
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
- name: Deploy
if: github.event_name == 'workflow_dispatch'
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "explorer/dist/"
REMOTE_HOST: ${{ secrets.CD_PROD_NE_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CD_PROD_NE_REMOTE_USER }}
TARGET: ${{ secrets.CD_PROD_NE_REMOTE_TARGET }}
EXCLUDE: "/dist/, /node_modules/"
@@ -0,0 +1,50 @@
[
{
"os":"ubuntu-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"ubuntu-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"ubuntu-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
}
]
@@ -0,0 +1,174 @@
name: Nightly builds on dispatch
on: workflow_dispatch
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_on_dispatch.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
build:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features -- --ignored
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-latest' }}
with:
command: clean
# COCONUT stuff
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets --features=coconut -- -D warnings
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: build
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v2
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+37 -7
View File
@@ -8,10 +8,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Added
- socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353])
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- nym-connect: add ability to select network requester and gateway ([#1427])
- nym-connect: add ability to export gateway keys as JSON
- nym-connect: add auto updater
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
- explorer-api: add apy values to `mix_nodes` endpoint
@@ -24,9 +20,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261])
- validator-api: add `uptime`, `estimated_operator_apy`, `estimated_delegators_apy` to `/mixnodes/detailed` endpoint ([#1393])
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
- mixnode: Added basic mixnode hardware reporting to the HTTP API ([#1308]).
- validator-api: endpoint, in coconut mode, for returning the validator-api cosmos address ([#1404]).
- validator-client: add `denom` argument and add simple test for querying an account balance
- gateway, validator-api: Checks for coconut credential double spending attempts, taking the coconut bandwidth contract as source of truth ([#1457])
- coconut-bandwidth-contract: Record the state of a coconut credential; create specific proposal for releasing funds ([#1457])
### Fixed
@@ -37,10 +35,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- native & socks5 clients: rerun init will now reuse previous gateway configuration instead of failing ([#1353])
- native & socks5 clients: deduplicate big chunks of init logic
- validator: fixed local docker-compose setup to work on Apple M1 ([#1329])
- explorer-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1482]).
- network-requester: fix filter for suffix-only domains ([#1487])
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
- all: updated all `cosmwasm`-related dependencies to `1.0.0` and `cw-storage-plus` to `0.13.4` [[#1318]]
- all: updated `rocket` to `0.5.0-rc.2`.
@@ -48,6 +47,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- gateway: allow to voluntarily send statistical data about the number of active inboxes served by a gateway ([#1376])
- gateway & mixnode: move detailed build info back to `--version` from `--help`.
- socks5 client/websocket client: upgrade to latest clap and switched to declarative commandline parsing.
- validator-api: fee payment for multisig operations comes from the gateway account instead of the validator APIs' accounts ([#1419])
- multisig-contract: Limit the proposal creating functionality to one address (coconut-bandwidth-contract address) ([#1457])
- All binaries and cosmwasm blobs are configured at runtime now; binaries are configured using environment variables or .env files and contracts keep the configuration parameters in storage ([#1463])
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
[#1249]: https://github.com/nymtech/nym/pull/1249
[#1256]: https://github.com/nymtech/nym/pull/1256
@@ -65,11 +69,37 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1329]: https://github.com/nymtech/nym/pull/1329
[#1353]: https://github.com/nymtech/nym/pull/1353
[#1376]: https://github.com/nymtech/nym/pull/1376
[#1388]: https://github.com/nymtech/nym/pull/1388
[#1393]: https://github.com/nymtech/nym/pull/1393
[#1404]: https://github.com/nymtech/nym/pull/1404
[#1419]: https://github.com/nymtech/nym/pull/1419
[#1457]: https://github.com/nymtech/nym/pull/1457
[#1463]: https://github.com/nymtech/nym/pull/1463
[#1478]: https://github.com/nymtech/nym/pull/1478
[#1482]: https://github.com/nymtech/nym/pull/1482
[#1487]: https://github.com/nymtech/nym/pull/1487
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
### Added
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- nym-connect: add ability to select network requester and gateway ([#1427])
- nym-connect: add ability to export gateway keys as JSON
- nym-connect: add auto updater
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection
[#1427]: https://github.com/nymtech/nym/pull/1427
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
- wallet: dark mode
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
[#1388]: https://github.com/nymtech/nym/pull/1388
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
### Added
Generated
+42 -12
View File
@@ -644,6 +644,7 @@ name = "coconut-bandwidth-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"multisig-contract-common",
"schemars",
"serde",
]
@@ -782,9 +783,8 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cosmos-sdk-proto"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f109fe191e73898d74b8020c50f86018364ad19bc30318aa074616c382b52856"
version = "0.12.3"
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
dependencies = [
"prost 0.10.3",
"prost-types 0.10.1",
@@ -793,9 +793,8 @@ dependencies = [
[[package]]
name = "cosmrs"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8413275b23cb5a0734d9d1e3e33f0b5b94547c1e94776dbc3149dbf46588a533"
version = "0.7.1"
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -929,7 +928,6 @@ dependencies = [
"coconut-interface",
"cosmrs",
"crypto",
"network-defaults",
"rand 0.7.3",
"thiserror",
"url",
@@ -1592,6 +1590,7 @@ name = "explorer-api"
version = "1.0.1"
dependencies = [
"chrono",
"clap 3.2.8",
"humantime-serde",
"isocountry",
"itertools",
@@ -1607,6 +1606,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"task",
"thiserror",
"tokio",
"validator-client",
@@ -2872,7 +2872,6 @@ dependencies = [
"cosmwasm-std",
"fixed",
"log",
"network-defaults",
"schemars",
"serde",
"serde_repr",
@@ -2969,6 +2968,7 @@ name = "network-defaults"
version = "0.1.0"
dependencies = [
"cfg-if 1.0.0",
"dotenv",
"hex-literal",
"once_cell",
"serde",
@@ -3066,7 +3066,6 @@ dependencies = [
"credentials",
"crypto",
"dirs",
"dotenv",
"futures",
"gateway-client",
"gateway-requests",
@@ -3233,7 +3232,6 @@ dependencies = [
"credentials",
"crypto",
"dirs",
"dotenv",
"futures",
"gateway-client",
"gateway-requests",
@@ -3300,6 +3298,8 @@ dependencies = [
"credential-storage",
"credentials",
"crypto",
"cw-utils",
"cw3",
"dirs",
"dotenv",
"futures",
@@ -5389,7 +5389,6 @@ version = "1.0.1"
dependencies = [
"async-trait",
"log",
"network-defaults",
"reqwest",
"serde",
"serde_json",
@@ -5398,6 +5397,15 @@ dependencies = [
"tokio",
]
[[package]]
name = "streaming-stats"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d670ce4e348a2081843569e0f79b21c99c91bb9028b3b3ecb0f050306de547"
dependencies = [
"num-traits",
]
[[package]]
name = "stringprep"
version = "0.1.2"
@@ -6285,6 +6293,29 @@ dependencies = [
"vesting-contract-common",
]
[[package]]
name = "validator-client-scripts"
version = "0.1.0"
dependencies = [
"base64",
"bip39",
"bs58",
"clap 3.2.8",
"csv",
"dotenv",
"log",
"mixnet-contract-common",
"network-defaults",
"pretty_env_logger",
"serde",
"serde_json",
"streaming-stats",
"tokio",
"url",
"validator-client",
"vesting-contract-common",
]
[[package]]
name = "valuable"
version = "0.1.0"
@@ -6337,7 +6368,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract"
version = "1.0.1"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"mixnet-contract-common",
+2 -1
View File
@@ -68,7 +68,8 @@ members = [
"service-providers/network-statistics",
"validator-api",
"validator-api/validator-api-requests",
"tools/ts-rs-cli"
"tools/ts-rs-cli",
"tools/validator-client-scripts"
]
default-members = [
+1 -1
View File
@@ -1,5 +1,5 @@
test: clippy-all cargo-test wasm fmt
test-all: test cargo-test-expensive
test: build clippy-all cargo-test wasm fmt
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect
+1 -2
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::*;
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
@@ -366,7 +365,7 @@ impl<T: NymConfig> Default for Client<T> {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
disabled_credentials_mode: true,
validator_api_urls: default_api_endpoints(),
validator_api_urls: vec![],
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
private_encryption_key_file: Default::default(),
+9 -4
View File
@@ -4,7 +4,7 @@
use crate::error::Result;
use crate::{MNEMONIC, NYMD_URL};
use bip39::Mnemonic;
use network_defaults::{DEFAULT_NETWORK, MIX_DENOM, VOUCHER_INFO};
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
use std::str::FromStr;
use url::Url;
use validator_client::nymd;
@@ -13,18 +13,23 @@ use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient};
pub(crate) struct Client {
nymd_client: NymdClient<SigningNymdClient>,
mix_denom_base: String,
}
impl Client {
pub fn new() -> Self {
let nymd_url = Url::from_str(NYMD_URL).unwrap();
let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap();
let config = nymd::Config::try_from_nym_network_details(&DEFAULT_NETWORK.details())
let network_details = NymNetworkDetails::new_from_env();
let config = nymd::Config::try_from_nym_network_details(&network_details)
.expect("failed to construct valid validator client config with the provided network");
let nymd_client =
NymdClient::connect_with_mnemonic(config, nymd_url.as_ref(), mnemonic, None).unwrap();
Client { nymd_client }
Client {
nymd_client,
mix_denom_base: network_details.chain_details.mix_denom.base,
}
}
pub async fn deposit(
@@ -34,7 +39,7 @@ impl Client {
encryption_key: String,
fee: Option<Fee>,
) -> Result<String> {
let amount = Coin::new(amount as u128, MIX_DENOM.base.to_string());
let amount = Coin::new(amount as u128, self.mix_denom_base.clone());
Ok(self
.nymd_client
.deposit(
-1
View File
@@ -22,7 +22,6 @@ url = "2.2"
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
+10 -4
View File
@@ -3,7 +3,6 @@
use crate::client::config::{Config, SocketType};
use clap::{Parser, Subcommand};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
#[cfg(not(feature = "coconut"))]
@@ -28,7 +27,6 @@ fn long_version() -> String {
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
@@ -46,8 +44,6 @@ fn long_version() -> String {
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
@@ -58,6 +54,10 @@ fn long_version_static() -> &'static str {
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
command: Commands,
}
@@ -113,6 +113,12 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
} else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() {
let raw_validators = std::env::var(network_defaults::var_names::API_VALIDATOR)
.expect("api validator not set");
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
}
if args.disable_socket {
+1 -10
View File
@@ -3,7 +3,7 @@
use crate::client::config::{Config, MISSING_VALUE};
use config::{defaults::default_api_endpoints, NymConfig};
use config::NymConfig;
use version_checker::Version;
use clap::Args;
@@ -105,15 +105,6 @@ fn minor_0_12_upgrade(
print_start_upgrade(&config_version, &to_version);
println!(
"Setting validator API endpoints to {:?}",
default_api_endpoints()
);
config
.get_base_mut()
.set_custom_validator_apis(default_api_endpoints());
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
+2 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use network_defaults::setup_env;
pub mod client;
pub mod commands;
@@ -9,11 +10,11 @@ pub mod websocket;
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
}
-1
View File
@@ -13,7 +13,6 @@ path = "src/lib.rs"
[dependencies]
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
dotenv = "0.15.0"
futures = "0.3"
log = "0.4"
pin-project = "1.0"
+9 -5
View File
@@ -3,7 +3,6 @@
use crate::client::config::Config;
use clap::{Parser, Subcommand};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
pub mod init;
@@ -28,7 +27,6 @@ fn long_version() -> String {
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
@@ -46,8 +44,6 @@ fn long_version() -> String {
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
@@ -58,6 +54,10 @@ fn long_version_static() -> &'static str {
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
command: Commands,
}
@@ -96,7 +96,7 @@ pub(crate) async fn execute(args: &Cli) {
}
}
fn parse_validators(raw: &str) -> Vec<Url> {
pub fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
raw_validator
@@ -112,6 +112,10 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
} else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::API_VALIDATOR) {
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
}
if let Some(port) = args.port {
+1 -10
View File
@@ -3,7 +3,7 @@
use crate::client::config::{Config, MISSING_VALUE};
use config::{defaults::default_api_endpoints, NymConfig};
use config::NymConfig;
use version_checker::Version;
use clap::Args;
@@ -104,15 +104,6 @@ fn minor_0_12_upgrade(
print_start_upgrade(&config_version, &to_version);
println!(
"Setting validator API endpoints to {:?}",
default_api_endpoints()
);
config
.get_base_mut()
.set_custom_validator_apis(default_api_endpoints());
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
+2 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use network_defaults::setup_env;
pub mod client;
mod commands;
@@ -9,11 +10,11 @@ pub mod socks;
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
}
+7 -3
View File
@@ -5,7 +5,7 @@ use futures::StreamExt;
use log::*;
use nymsphinx::receiver::ReconstructedMessage;
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
use socks5_requests::Response;
use socks5_requests::Message;
pub(crate) struct MixnetResponseListener {
buffer_requester: ReceivedBufferRequestSender,
@@ -44,12 +44,16 @@ impl MixnetResponseListener {
warn!("this message had a surb - we didn't do anything with it");
}
let response = match Response::try_from_bytes(&raw_message) {
let response = match Message::try_from_bytes(&raw_message) {
Err(err) => {
warn!("failed to parse received response - {:?}", err);
return;
}
Ok(data) => data,
Ok(Message::Request(_)) => {
warn!("unexpected request");
return;
}
Ok(Message::Response(data)) => data,
};
self.controller_sender
+1 -1
View File
@@ -1 +1 @@
15.0.1
16
+4 -1
View File
@@ -31,9 +31,12 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-mocha": "^10.0.3",
"eslint-plugin-prettier": "^4.0.0",
"expect": "^28.1.3",
"mocha": "^10.0.0",
"prettier": "^2.5.1",
"typedoc": "^0.22.13",
"typescript": "^4.1.3"
"ts-mocha": "^10.0.0",
"typescript": "^4.6.2"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.28.0",
+11 -12
View File
@@ -64,23 +64,20 @@ export default class ValidatorClient implements INymClient {
readonly vestingContract: string;
readonly mainnetDenom = "unym";
readonly mainnetDenom = 'unym';
readonly mainnetPrefix = "n";
readonly mainnetPrefix = 'n';
private constructor(
client: SigningClient | QueryClient,
prefix: string,
mixnetContract: string,
vestingContract: string
vestingContract: string,
denom: string,
) {
this.client = client;
this.prefix = prefix;
if (prefix == this.mainnetPrefix) {
this.denom = this.mainnetDenom;
} else {
this.denom = `u${prefix}`;
}
this.denom = `u${denom}`;
this.mixnetContract = mixnetContract;
this.vestingContract = vestingContract;
@@ -93,11 +90,12 @@ export default class ValidatorClient implements INymClient {
prefix: string,
mixnetContract: string,
vestingContract: string,
denom: string,
): Promise<ValidatorClient> {
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix);
return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract);
const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix, denom);
return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract, denom);
}
static async connectForQuery(
@@ -106,9 +104,10 @@ export default class ValidatorClient implements INymClient {
prefix: string,
mixnetContract: string,
vestingContract: string,
denom: string,
): Promise<ValidatorClient> {
const queryClient = await QueryClient.connectWithNym(nymdUrl, validatorApiUrl);
return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract);
return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract, denom);
}
public get address(): string {
@@ -457,4 +456,4 @@ export default class ValidatorClient implements INymClient {
this.assertSigning();
return (this.client as ISigningClient).updateContractStateParams(this.mixnetContract, newParams, fee, memo);
}
}
}
+3 -1
View File
@@ -221,10 +221,12 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
nymdUrl: string,
validatorApiUrl: string,
prefix: string,
denom: string,
): Promise<SigningClient> {
const [{ address }] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
prefix,
gasPrice: nymGasPrice(denom),
};
const tmClient = await Tendermint34Client.connect(nymdUrl);
return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions);
+3 -4
View File
@@ -7,13 +7,12 @@ const mainnetDenom = 'nym';
export function nymGasPrice(prefix: string): GasPrice {
if (typeof prefix === 'string') {
if (prefix === mainnetPrefix) {
prefix = mainnetDenom;
return GasPrice.fromString(`0.025u${mainnetDenom}`);
}
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
}
else {
throw new Error(`${prefix} is not of type string`);
}
throw new Error(`${prefix} is not of type string`);
}
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
@@ -0,0 +1,11 @@
import ValidatorClient from '../../dist';
import expect from 'expect';
describe('Query: balances', () => {
it('can query for an account balance', async () => {
const client = await ValidatorClient.connectForQuery(
'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
}).timeout(5000);
})
+14
View File
@@ -0,0 +1,14 @@
import ValidatorClient from '../../dist';
import expect from 'expect';
// TODO: implement for QA with .env for mnemonics
// describe('Sign: send', () => {
// it('can send tokens', async () => {
// const client = await ValidatorClient.connect(
// '<ADD MNEMONIC HERE>',
// 'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
// await client.send('<ADD ADDRESS HERE>')
// const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
// expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
// }).timeout(5000);
// })
+2 -1
View File
@@ -5,7 +5,8 @@
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist"
"outDir": "./dist",
"skipLibCheck": true
},
"typedocOptions": {
"entryPoints": [
File diff suppressed because it is too large Load Diff
@@ -35,7 +35,7 @@ validator-api-requests = { path = "../../../validator-api/validator-api-requests
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { version = "0.7.0", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
prost = { version = "0.10", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
@@ -5,8 +5,7 @@ use crate::{validator_api, ValidatorClientError};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody,
ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse,
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
@@ -29,7 +28,7 @@ use mixnet_contract_common::{
RewardedSetUpdateDetails,
};
#[cfg(feature = "nymd-client")]
use network_defaults::{all::Network, NymNetworkDetails};
use network_defaults::NymNetworkDetails;
#[cfg(feature = "nymd-client")]
use std::collections::{HashMap, HashSet};
@@ -114,9 +113,6 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
// compatibility : (
pub network: Network,
// TODO: we really shouldn't be storing a mnemonic here, but removing it would be
// non-trivial amount of work and it's out of scope of the current branch
mnemonic: Option<bip39::Mnemonic>,
@@ -135,9 +131,6 @@ pub struct Client<C> {
impl Client<SigningNymdClient> {
pub fn new_signing(
config: Config,
// we need to provide network argument due to compatibility with other components (wallet...)
// that rely on its existence...
network: Network,
mnemonic: bip39::Mnemonic,
) -> Result<Client<SigningNymdClient>, ValidatorClientError> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
@@ -149,7 +142,6 @@ impl Client<SigningNymdClient> {
)?;
Ok(Client {
network,
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -177,18 +169,12 @@ impl Client<SigningNymdClient> {
#[cfg(feature = "nymd-client")]
impl Client<QueryNymdClient> {
pub fn new_query(
config: Config,
// we need to provide network argument due to compatibility with other components (wallet...)
// that rely on its existence...
network: Network,
) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
pub fn new_query(config: Config) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let nymd_client =
NymdClient::connect(config.nymd_config.clone(), config.nymd_url.as_str())?;
Ok(Client {
network,
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -734,6 +720,10 @@ impl ApiClient {
Ok(self.validator_api.get_coconut_verification_key().await?)
}
pub async fn get_cosmos_address(&self) -> Result<CosmosAddressResponse, ValidatorClientError> {
Ok(self.validator_api.get_cosmos_address().await?)
}
pub async fn verify_bandwidth_credential(
&self,
request_body: &VerifyCredentialBody,
@@ -743,24 +733,4 @@ impl ApiClient {
.verify_bandwidth_credential(request_body)
.await?)
}
pub async fn propose_release_funds(
&self,
request_body: &ProposeReleaseFundsRequestBody,
) -> Result<ProposeReleaseFundsResponse, ValidatorClientError> {
Ok(self
.validator_api
.propose_release_funds(request_body)
.await?)
}
pub async fn execute_release_funds(
&self,
request_body: &ExecuteReleaseFundsRequestBody,
) -> Result<(), ValidatorClientError> {
Ok(self
.validator_api
.execute_release_funds(request_body)
.await?)
}
}
@@ -26,7 +26,7 @@ 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 as CosmosCoin, Denom, Tx};
use cosmrs::{tx, AccountId, Coin as CosmosCoin, Tx};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -121,7 +121,7 @@ pub trait CosmWasmClient: rpc::Client {
async fn get_balance(
&self,
address: &AccountId,
search_denom: Denom,
search_denom: String,
) -> Result<Option<Coin>, NymdError> {
let path = Some("/cosmos.bank.v1beta1.Query/Balance".parse().unwrap());
@@ -12,6 +12,9 @@ use crate::nymd::{Coin, GasAdjustable, GasPrice, TxResponse};
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::feegrant::{
AllowedMsgAllowance, BasicAllowance, MsgGrantAllowance, MsgRevokeAllowance,
};
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
@@ -23,7 +26,7 @@ use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use std::convert::TryInto;
use std::time::Duration;
use std::time::{Duration, SystemTime};
const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
@@ -420,6 +423,63 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()
}
#[allow(clippy::too_many_arguments)]
async fn grant_allowance(
&self,
granter: &AccountId,
grantee: &AccountId,
spend_limit: Vec<Coin>,
expiration: Option<SystemTime>,
allowed_messages: Vec<String>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<TxResponse, NymdError> {
let basic_allowance = BasicAllowance {
spend_limit: spend_limit.into_iter().map(Into::into).collect(),
expiration,
}
.to_any()
.map_err(|_| NymdError::SerializationError("BasicAllowance".to_owned()))?;
let allowed_msg_allowance = AllowedMsgAllowance {
allowance: Some(basic_allowance),
allowed_messages,
}
.to_any()
.map_err(|_| NymdError::SerializationError("AllowedMsgAllowance".to_owned()))?;
let grant_allowance_msg = MsgGrantAllowance {
granter: granter.to_owned(),
grantee: grantee.to_owned(),
allowance: Some(allowed_msg_allowance),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgGrantAllowance".to_owned()))?;
self.sign_and_broadcast(granter, vec![grant_allowance_msg], fee, memo)
.await?
.check_response()
}
async fn revoke_allowance(
&self,
granter: &AccountId,
grantee: &AccountId,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<TxResponse, NymdError> {
let revoke_allowance_msg = MsgRevokeAllowance {
granter: granter.to_owned(),
grantee: grantee.to_owned(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgRevokeAllowance".to_owned()))?;
self.sign_and_broadcast(granter, vec![revoke_allowance_msg], fee, memo)
.await?
.check_response()
}
async fn delegate_tokens(
&self,
delegator_address: &AccountId,
@@ -514,10 +574,10 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let fee = match fee {
Fee::Manual(fee) => fee,
Fee::Auto(multiplier) => auto_fee(multiplier).await?,
Fee::PayerGranterAuto(multiplier, payer, granter) => {
let mut fee = auto_fee(multiplier).await?;
fee.payer = payer;
fee.granter = granter;
Fee::PayerGranterAuto(auto_feegrant) => {
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
fee.payer = auto_feegrant.payer;
fee.granter = auto_feegrant.granter;
fee
}
};
@@ -1,9 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::Coin;
use crate::nymd::Gas;
use cosmrs::{tx, AccountId};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
pub mod gas_price;
@@ -11,11 +13,74 @@ pub type GasAdjustment = f32;
pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: GasAdjustment = 1.3;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoFeeGrant {
pub gas_adjustment: Option<GasAdjustment>,
pub payer: Option<AccountId>,
pub granter: Option<AccountId>,
}
impl Display for AutoFeeGrant {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(gas_adjustment) = self.gas_adjustment {
write!(f, "Feegrant in auto mode with {gas_adjustment} simulated multiplier with {:?} payer and {:?} granter", self.payer, self.granter)
} else {
write!(f, "Feegrant in auto mode with no custom simulated multiplier with {:?} payer and {:?} granter", self.payer, self.granter)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Fee {
Manual(#[serde(with = "sealed::TxFee")] tx::Fee),
Auto(Option<GasAdjustment>),
PayerGranterAuto(Option<GasAdjustment>, Option<AccountId>, Option<AccountId>),
PayerGranterAuto(AutoFeeGrant),
}
impl Display for Fee {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Fee::Manual(fee) => {
write!(f, "Fee in manual mode with ")?;
for fee in &fee.amount {
write!(f, "{}{} paid in fees, ", fee.amount, fee.denom)?;
}
write!(f, "{} set as gas limit, ", fee.gas_limit)?;
if let Some(payer) = &fee.payer {
write!(f, "{payer} set as payer, ")?;
}
if let Some(granter) = &fee.granter {
write!(f, "{granter} set as granter")?;
}
Ok(())
}
Fee::Auto(Some(multiplier)) => {
write!(f, "Fee in auto mode with {multiplier} simulated multiplier")
}
Fee::Auto(None) => write!(f, "Fee in auto mode with no custom simulated multiplier"),
Fee::PayerGranterAuto(auto_feegrant) => write!(f, "{}", auto_feegrant),
}
}
}
impl Fee {
pub fn new_payer_granter_auto(
gas_adjustment: Option<GasAdjustment>,
payer: Option<AccountId>,
granter: Option<AccountId>,
) -> Self {
Fee::PayerGranterAuto(AutoFeeGrant {
gas_adjustment,
payer,
granter,
})
}
pub fn try_get_manual_amount(&self) -> Option<Vec<Coin>> {
match self {
Fee::Manual(tx_fee) => Some(tx_fee.amount.iter().cloned().map(Into::into).collect()),
_ => None,
}
}
}
impl From<tx::Fee> for Fee {
@@ -26,6 +26,7 @@ use mixnet_contract_common::{
};
use serde::Serialize;
use std::convert::TryInto;
use std::time::SystemTime;
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
use vesting_contract_common::QueryMsg as VestingQueryMsg;
@@ -388,7 +389,7 @@ impl<C> NymdClient<C> {
pub async fn get_balance(
&self,
address: &AccountId,
denom: Denom,
denom: String,
) -> Result<Option<Coin>, NymdError>
where
C: CosmWasmClient + Sync,
@@ -845,6 +846,49 @@ impl<C> NymdClient<C> {
.await
}
/// Grant a fee allowance from one address to another
pub async fn grant_allowance(
&self,
grantee: &AccountId,
spend_limit: Vec<Coin>,
expiration: Option<SystemTime>,
allowed_messages: Vec<String>,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.grant_allowance(
self.address(),
grantee,
spend_limit,
expiration,
allowed_messages,
fee,
memo,
)
.await
}
/// Revoke a fee allowance from one address to another
pub async fn revoke_allowance(
&self,
grantee: &AccountId,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.revoke_allowance(self.address(), grantee, fee, memo)
.await
}
pub async fn execute<M>(
&self,
contract_address: &AccountId,
@@ -962,6 +1006,29 @@ impl<C> NymdClient<C> {
.await
}
#[execute("mixnet")]
fn _compound_reward(
&self,
operator: Option<String>,
delegator: Option<String>,
mix_identity: Option<IdentityKey>,
proxy: Option<String>,
fee: Option<Fee>,
) -> (ExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
(
ExecuteMsg::CompoundReward {
operator,
delegator,
mix_identity,
proxy,
},
fee,
)
}
#[execute("mixnet")]
fn _compound_operator_reward(&self, fee: Option<Fee>) -> (ExecuteMsg, Option<Fee>)
where
@@ -0,0 +1,33 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::error::NymdError;
use crate::nymd::{CosmWasmClient, NymdClient};
use coconut_bandwidth_contract_common::msg::QueryMsg;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use async_trait::async_trait;
#[async_trait]
pub trait CoconutBandwidthQueryClient {
async fn get_spent_credential(
&self,
blinded_serial_number: String,
) -> Result<SpendCredentialResponse, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> CoconutBandwidthQueryClient for NymdClient<C> {
async fn get_spent_credential(
&self,
blinded_serial_number: String,
) -> Result<SpendCredentialResponse, NymdError> {
let request = QueryMsg::GetSpentCredential {
blinded_serial_number,
};
self.client
.query_contract_smart(self.coconut_bandwidth_contract_address(), &request)
.await
}
}
@@ -5,6 +5,7 @@ pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Coin, Fee, NymdClient};
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialData;
use coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg};
use async_trait::async_trait;
@@ -19,6 +20,13 @@ pub trait CoconutBandwidthSigningClient {
encryption_key: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn spend_credential(
&self,
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
@@ -46,4 +54,30 @@ impl<C: SigningCosmWasmClient + Sync + Send> CoconutBandwidthSigningClient for N
)
.await
}
async fn spend_credential(
&self,
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::SpendCredential {
data: SpendCredentialData::new(
funds.into(),
blinded_serial_number,
gateway_cosmos_address,
),
};
self.client
.execute(
self.address(),
self.coconut_bandwidth_contract_address(),
&req,
fee,
"CoconutBandwidth::SpendCredential",
vec![],
)
.await
}
}
@@ -1,14 +1,16 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client;
mod multisig_query_client;
mod multisig_signing_client;
mod vesting_query_client;
mod vesting_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use multisig_query_client::QueryClient;
pub use multisig_query_client::MultisigQueryClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use vesting_query_client::VestingQueryClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -9,12 +9,12 @@ use multisig_contract_common::msg::{ProposalResponse, QueryMsg};
use async_trait::async_trait;
#[async_trait]
pub trait QueryClient {
pub trait MultisigQueryClient {
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> QueryClient for NymdClient<C> {
impl<C: CosmWasmClient + Sync + Send> MultisigQueryClient for NymdClient<C> {
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError> {
let request = QueryMsg::Proposal { proposal_id };
self.client
@@ -12,7 +12,6 @@ use multisig_contract_common::msg::ExecuteMsg;
use async_trait::async_trait;
use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg};
use cw3::Vote;
use network_defaults::DEFAULT_NETWORK;
#[async_trait]
pub trait MultisigSigningClient {
@@ -49,7 +48,10 @@ impl<C: SigningCosmWasmClient + Sync + Send> MultisigSigningClient for NymdClien
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds {
funds: Coin::new(voucher_value, DEFAULT_NETWORK.mix_denom().base),
funds: Coin::new(
voucher_value,
self.config.chain_details.mix_denom.base.clone(),
),
};
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: self.coconut_bandwidth_contract_address().to_string(),
@@ -207,35 +207,20 @@ mod tests {
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
let prefixes = vec![
MAINNET.bech32_prefix(),
SANDBOX.bech32_prefix(),
QA.bech32_prefix(),
];
let prefix = MAINNET.bech32_prefix();
for prefix in prefixes {
let addrs = match prefix.as_ref() {
"nymt" => vec![
"nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94",
"nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv",
"nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4",
],
"n" => vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
],
_ => panic!("Test needs to be updated with new bech32 prefix"),
};
for (idx, mnemonic) in mnemonics.iter().enumerate() {
let wallet =
DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap())
.unwrap();
assert_eq!(
wallet.try_derive_accounts().unwrap()[0].address,
addrs[idx].parse().unwrap()
)
}
let addrs = vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
];
for (idx, mnemonic) in mnemonics.iter().enumerate() {
let wallet =
DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap()).unwrap();
assert_eq!(
wallet.try_derive_accounts().unwrap()[0].address,
addrs[idx].parse().unwrap()
)
}
}
}
@@ -8,9 +8,8 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse,
ExecuteReleaseFundsRequestBody, ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse,
VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse,
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
@@ -405,40 +404,6 @@ impl Client {
)
.await
}
pub async fn propose_release_funds(
&self,
request_body: &ProposeReleaseFundsRequestBody,
) -> Result<ProposeReleaseFundsResponse, ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_PROPOSE_RELEASE_FUNDS,
],
NO_PARAMS,
request_body,
)
.await
}
pub async fn execute_release_funds(
&self,
request_body: &ExecuteReleaseFundsRequestBody,
) -> Result<(), ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_EXECUTE_RELEASE_FUNDS,
],
NO_PARAMS,
request_body,
)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
@@ -19,8 +19,6 @@ pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-creden
pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
pub const COCONUT_COSMOS_ADDRESS: &str = "cosmos-address";
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
pub const COCONUT_PROPOSE_RELEASE_FUNDS: &str = "propose-release-funds";
pub const COCONUT_EXECUTE_RELEASE_FUNDS: &str = "execute-release-funds";
pub const STATUS_ROUTES: &str = "status";
pub const MIXNODE: &str = "mixnode";
@@ -9,3 +9,4 @@ edition = "2021"
cosmwasm-std = "1.0.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
multisig-contract-common = { path = "../multisig-contract" }
@@ -1,3 +1,4 @@
pub mod deposit;
pub mod events;
pub mod msg;
pub mod spend_credential;
@@ -5,24 +5,34 @@ use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::deposit::DepositData;
use crate::{deposit::DepositData, spend_credential::SpendCredentialData};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct InstantiateMsg {
pub multisig_addr: String,
pub pool_addr: String,
pub mix_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
DepositFunds { data: DepositData },
SpendCredential { data: SpendCredentialData },
ReleaseFunds { funds: Coin },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {}
pub enum QueryMsg {
GetSpentCredential {
blinded_serial_number: String,
},
GetAllSpentCredentials {
limit: Option<u32>,
start_after: Option<String>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -0,0 +1,148 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{from_binary, to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg};
use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::msg::ExecuteMsg;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct SpendCredentialData {
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: String,
}
impl SpendCredentialData {
pub fn new(funds: Coin, blinded_serial_number: String, gateway_cosmos_address: String) -> Self {
SpendCredentialData {
funds,
blinded_serial_number,
gateway_cosmos_address,
}
}
pub fn funds(&self) -> &Coin {
&self.funds
}
pub fn blinded_serial_number(&self) -> &str {
&self.blinded_serial_number
}
pub fn gateway_cosmos_address(&self) -> &str {
&self.gateway_cosmos_address
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub enum SpendCredentialStatus {
InProgress,
Spent,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct SpendCredential {
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: Addr,
status: SpendCredentialStatus,
}
impl SpendCredential {
pub fn new(funds: Coin, blinded_serial_number: String, gateway_cosmos_address: Addr) -> Self {
SpendCredential {
funds,
blinded_serial_number,
gateway_cosmos_address,
status: SpendCredentialStatus::InProgress,
}
}
pub fn blinded_serial_number(&self) -> &str {
&self.blinded_serial_number
}
pub fn status(&self) -> SpendCredentialStatus {
self.status
}
pub fn mark_as_spent(&mut self) {
self.status = SpendCredentialStatus::Spent;
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedSpendCredentialResponse {
pub spend_credentials: Vec<SpendCredential>,
pub per_page: usize,
pub start_next_after: Option<String>,
}
impl PagedSpendCredentialResponse {
pub fn new(
spend_credentials: Vec<SpendCredential>,
per_page: usize,
start_next_after: Option<String>,
) -> Self {
PagedSpendCredentialResponse {
spend_credentials,
per_page,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct SpendCredentialResponse {
pub spend_credential: Option<SpendCredential>,
}
impl SpendCredentialResponse {
pub fn new(spend_credential: Option<SpendCredential>) -> Self {
SpendCredentialResponse { spend_credential }
}
}
pub fn to_cosmos_msg(
funds: Coin,
blinded_serial_number: String,
coconut_bandwidth_addr: String,
multisig_addr: String,
) -> StdResult<CosmosMsg> {
let release_funds_req = ExecuteMsg::ReleaseFunds { funds };
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: coconut_bandwidth_addr,
msg: to_binary(&release_funds_req)?,
funds: vec![],
});
let req = MultisigExecuteMsg::Propose {
title: String::from("Release funds, as ordered by Coconut Bandwidth Contract"),
description: blinded_serial_number,
msgs: vec![release_funds_msg],
latest: None,
};
let msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: multisig_addr,
msg: to_binary(&req)?,
funds: vec![],
});
Ok(msg)
}
pub fn funds_from_cosmos_msgs(msgs: Vec<CosmosMsg>) -> Option<Coin> {
if let Some(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: _,
msg,
funds: _,
})) = msgs.get(0)
{
if let Ok(ExecuteMsg::ReleaseFunds { funds }) = from_binary::<ExecuteMsg>(msg) {
return Some(funds);
}
}
None
}
@@ -3,6 +3,7 @@ name = "mixnet-contract-common"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
rust-version = "1.62"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,7 +14,6 @@ serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
thiserror = "1.0"
network-defaults = { path = "../../network-defaults" }
fixed = { version = "1.1", features = ["serde"] }
az = "1.1"
log = "0.4.14"
@@ -33,7 +33,7 @@ pub struct Delegation {
pub node_identity: IdentityKey,
pub amount: Coin,
pub block_height: u64,
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of anouther address
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of another address
}
impl Eq for Delegation {}
@@ -37,6 +37,16 @@ pub enum DelegationEvent {
Undelegate(PendingUndelegate),
}
impl DelegationEvent {
pub fn delegation_amount(&self) -> Option<Coin> {
match self {
DelegationEvent::Delegate(delegation) => Some(delegation.amount.clone()),
// I think it would be nice to also expose an amount here to know how much we're undelegating
DelegationEvent::Undelegate(_) => None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct PendingUndelegate {
mix_identity: IdentityKey,
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct InstantiateMsg {
pub rewarding_validator_address: String,
pub mixnet_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
@@ -32,6 +33,12 @@ pub enum ExecuteMsg {
CompoundDelegatorReward {
mix_identity: IdentityKey,
},
CompoundReward {
operator: Option<String>,
delegator: Option<String>,
mix_identity: Option<IdentityKey>,
proxy: Option<String>,
},
BondMixnode {
mix_node: MixNode,
owner_signature: String,
@@ -115,6 +122,7 @@ pub enum ExecuteMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetBlacklistedNodes {},
GetCurrentOperatorCost {},
GetRewardingValidatorAddress {},
GetAllDelegationKeys {},
@@ -205,6 +213,31 @@ pub enum QueryMsg {
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
pub struct MigrateMsg {
pub mixnet_denom: String,
nodes_to_remove: Option<Vec<NodeToRemove>>,
}
impl MigrateMsg {
pub fn nodes_to_remove(&self) -> Vec<NodeToRemove> {
self.nodes_to_remove.clone().unwrap_or_default()
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct NodeToRemove {
owner: String,
proxy: Option<String>,
}
impl NodeToRemove {
pub fn owner(&self) -> &str {
&self.owner
}
pub fn proxy(&self) -> Option<&String> {
self.proxy.as_ref()
}
}
@@ -14,6 +14,7 @@ use cw_utils::{Duration, Expiration, Threshold};
pub struct InstantiateMsg {
// this is the group contract that contains the member list
pub group_addr: String,
pub coconut_bandwidth_contract_address: String,
pub threshold: Threshold,
pub max_voting_period: Duration,
}
@@ -77,3 +78,8 @@ pub enum QueryMsg {
limit: Option<u32>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub coconut_bandwidth_address: String,
}
@@ -1,6 +1,5 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::MIX_DENOM;
use cosmwasm_std::{Coin, Timestamp};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -10,8 +9,8 @@ pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
pub mod events;
pub mod messages;
pub fn one_ucoin() -> Coin {
Coin::new(1, MIX_DENOM.base)
pub fn one_ucoin(denom: String) -> Coin {
Coin::new(1, denom)
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
@@ -28,8 +27,8 @@ pub enum Period {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct PledgeData {
amount: Coin,
block_time: Timestamp,
pub amount: Coin,
pub block_time: Timestamp,
}
impl PledgeData {
@@ -48,9 +47,9 @@ impl PledgeData {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct OriginalVestingResponse {
amount: Coin,
number_of_periods: usize,
period_duration: u64,
pub amount: Coin,
pub number_of_periods: usize,
pub period_duration: u64,
}
impl OriginalVestingResponse {
@@ -7,11 +7,14 @@ use serde::{Deserialize, Serialize};
#[serde(rename_all = "snake_case")]
pub struct InitMsg {
pub mixnet_contract_address: String,
pub mix_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
pub struct MigrateMsg {
pub mix_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
pub struct VestingSpecification {
+1 -2
View File
@@ -7,14 +7,13 @@ edition = "2021"
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
cosmrs = { version = "0.7.0", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", optional = true }
thiserror = "1.0"
url = "2.2"
# I guess temporarily until we get serde support in coconut up and running
coconut-interface = { path = "../coconut-interface" }
crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] }
network-defaults = { path = "../network-defaults" }
validator-api-requests = { path = "../../validator-api/validator-api-requests" }
validator-client = { path = "../client-libs/validator-client" }
+11 -7
View File
@@ -1,4 +1,3 @@
use config::defaults::DEFAULT_NETWORK;
use subtle_encoding::bech32;
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -15,16 +14,15 @@ pub fn try_bech32_decode(address: &str) -> Result<String, Bech32Error> {
}
}
pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
pub fn validate_bech32_prefix(bech32_prefix: &str, address: &str) -> Result<(), Bech32Error> {
let prefix = try_bech32_decode(address)?;
if prefix == DEFAULT_NETWORK.bech32_prefix() {
if prefix == bech32_prefix {
Ok(())
} else {
Err(Bech32Error::WrongPrefix(format!(
"your bech32 address prefix should be {}, not {}",
DEFAULT_NETWORK.bech32_prefix(),
prefix
bech32_prefix, prefix
)))
}
}
@@ -33,6 +31,8 @@ pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
mod tests {
use super::*;
const TEST_BECH32_PREFIX: &str = "n";
mod decoding_bech32_addresses {
use super::*;
@@ -71,9 +71,12 @@ mod tests {
assert_eq!(
Err(Bech32Error::WrongPrefix(format!(
"your bech32 address prefix should be {}, not punk",
DEFAULT_NETWORK.bech32_prefix()
TEST_BECH32_PREFIX
))),
validate_bech32_prefix("punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0")
validate_bech32_prefix(
TEST_BECH32_PREFIX,
"punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0"
)
)
}
@@ -82,6 +85,7 @@ mod tests {
assert_eq!(
Ok(()),
validate_bech32_prefix(
TEST_BECH32_PREFIX,
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g"
)
)
+1
View File
@@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
cfg-if = "1.0.0"
dotenv = "0.15.0"
hex-literal = "0.3.3"
once_cell = "1.7.2"
serde = {version = "1.0", features = ["derive"]}
-11
View File
@@ -1,11 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
fn main() {
match option_env!("NETWORK") {
None | Some("mainnet") => println!("cargo:rustc-cfg=network=\"mainnet\"",),
Some("sandbox") => println!("cargo:rustc-cfg=network=\"sandbox\"",),
Some("qa") => println!("cargo:rustc-cfg=network=\"qa\""),
_ => panic!("No such network"),
}
}
+64 -62
View File
@@ -3,6 +3,7 @@
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{env::var, path::PathBuf};
use url::Url;
pub mod all;
@@ -10,36 +11,10 @@ pub mod eth_contract;
pub mod mainnet;
pub mod qa;
pub mod sandbox;
pub mod var_names;
// The set of defaults that are decided at compile time. Ideally we want to reduce these to a
// minimum.
// Keep DENOM around mostly for use in contracts. (TODO: consider moving it there, or renaming?)
cfg_if::cfg_if! {
if #[cfg(network = "mainnet")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::MAINNET;
pub const MIX_DENOM: DenomDetails = mainnet::MIX_DENOM;
pub const STAKE_DENOM: DenomDetails = mainnet::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
} else if #[cfg(network = "qa")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::QA;
pub const MIX_DENOM: DenomDetails = qa::MIX_DENOM;
pub const STAKE_DENOM: DenomDetails = qa::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_ERC20_CONTRACT_ADDRESS;
} else if #[cfg(network = "sandbox")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::SANDBOX;
pub const MIX_DENOM: DenomDetails = sandbox::MIX_DENOM;
pub const STAKE_DENOM: DenomDetails = sandbox::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_ERC20_CONTRACT_ADDRESS;
}
}
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ChainDetails {
@@ -82,23 +57,56 @@ impl NymNetworkDetails {
NymNetworkDetails::default()
}
pub fn new_qa() -> Self {
(&*QA_DEFAULTS).into()
}
pub fn new_sandbox() -> Self {
(&*SANDBOX_DEFAULTS).into()
pub fn new_from_env() -> Self {
NymNetworkDetails::new()
.with_bech32_account_prefix(
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
)
.with_mix_denom(DenomDetailsOwned {
base: var(var_names::MIX_DENOM).expect("mix denomination base not set"),
display: var(var_names::MIX_DENOM_DISPLAY)
.expect("mix denomination display not set"),
display_exponent: var(var_names::DENOMS_EXPONENT)
.expect("denomination exponent not set")
.parse()
.expect("denomination exponent is not u32"),
})
.with_stake_denom(DenomDetailsOwned {
base: var(var_names::STAKE_DENOM).expect("stake denomination base not set"),
display: var(var_names::STAKE_DENOM_DISPLAY)
.expect("stake denomination display not set"),
display_exponent: var(var_names::DENOMS_EXPONENT)
.expect("denomination exponent not set")
.parse()
.expect("denomination exponent is not u32"),
})
.with_validator_endpoint(ValidatorDetails::new(
var(var_names::NYMD_VALIDATOR).expect("nymd validator not set"),
Some(var(var_names::API_VALIDATOR).expect("api validator not set")),
))
.with_mixnet_contract(Some(
var(var_names::MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"),
))
.with_vesting_contract(Some(
var(var_names::VESTING_CONTRACT_ADDRESS).expect("vesting contract not set"),
))
.with_bandwidth_claim_contract(Some(
var(var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS)
.expect("bandwidth claim contract not set"),
))
.with_coconut_bandwidth_contract(Some(
var(var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
.expect("coconut bandwidth contract not set"),
))
.with_multisig_contract(Some(
var(var_names::MULTISIG_CONTRACT_ADDRESS).expect("multisig contract not set"),
))
}
pub fn new_mainnet() -> Self {
(&*MAINNET_DEFAULTS).into()
}
pub fn current_default() -> Self {
// backwards compatibility reasons
DEFAULT_NETWORK.details()
}
pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.chain_details.bech32_account_prefix = prefix.into();
self
@@ -267,7 +275,7 @@ impl DenomDetails {
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[derive(Debug, Serialize, Deserialize, Hash, Clone, PartialEq, Eq)]
pub struct DenomDetailsOwned {
pub base: String,
pub display: String,
@@ -333,27 +341,21 @@ impl ValidatorDetails {
}
}
pub fn default_statistics_service_url() -> Url {
DEFAULT_NETWORK
.statistics_service_url()
.parse()
.expect("the provided statistics service url is invalid!")
}
pub fn default_nymd_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.iter()
.map(ValidatorDetails::nymd_url)
.collect()
}
pub fn default_api_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.iter()
.filter_map(ValidatorDetails::api_url)
.collect()
pub fn setup_env(config_env_file: Option<PathBuf>) {
match std::env::var(var_names::CONFIGURED) {
// if the configuration is not already set in the env vars
Err(std::env::VarError::NotPresent) => {
if let Some(config_env_file) = config_env_file {
dotenv::from_path(config_env_file)
.expect("Invalid path to environment configuration file");
} else {
// if nothing is set, the use mainnet defaults
crate::mainnet::export_to_env();
}
}
Err(_) => crate::mainnet::export_to_env(),
_ => {}
}
}
// Name of the event triggered by the eth contract. If the event name is changed,
+44 -4
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::var_names;
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "n";
@@ -24,9 +25,48 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://127.0.0.1:8090";
pub const NYMD_VALIDATOR: &str = "https://rpc.nyx.nodes.guru/";
pub const API_VALIDATOR: &str = "https://validator.nymtech.net/api/";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://rpc.nyx.nodes.guru/",
Some("https://validator.nymtech.net/api/"),
)]
vec![ValidatorDetails::new(NYMD_VALIDATOR, Some(API_VALIDATOR))]
}
pub fn export_to_env() {
std::env::set_var(var_names::CONFIGURED, "true");
std::env::set_var(var_names::BECH32_PREFIX, BECH32_PREFIX);
std::env::set_var(var_names::MIX_DENOM, MIX_DENOM.base);
std::env::set_var(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
std::env::set_var(var_names::STAKE_DENOM, STAKE_DENOM.base);
std::env::set_var(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
std::env::set_var(
var_names::DENOMS_EXPONENT,
STAKE_DENOM.display_exponent.to_string(),
);
std::env::set_var(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
std::env::set_var(
var_names::VESTING_CONTRACT_ADDRESS,
VESTING_CONTRACT_ADDRESS,
);
std::env::set_var(
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
);
std::env::set_var(
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
std::env::set_var(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
std::env::set_var(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
);
std::env::set_var(
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
STATISTICS_SERVICE_DOMAIN_ADDRESS,
);
std::env::set_var(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
std::env::set_var(var_names::API_VALIDATOR, API_VALIDATOR);
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// Environment variable that, if set, shows the environment is currently configured
pub const CONFIGURED: &str = "CONFIGURED";
pub const BECH32_PREFIX: &str = "BECH32_PREFIX";
pub const MIX_DENOM: &str = "MIX_DENOM";
pub const MIX_DENOM_DISPLAY: &str = "MIX_DENOM_DISPLAY";
pub const STAKE_DENOM: &str = "STAKE_DENOM";
pub const STAKE_DENOM_DISPLAY: &str = "STAKE_DENOM_DISPLAY";
pub const DENOMS_EXPONENT: &str = "DENOMS_EXPONENT";
pub const MIXNET_CONTRACT_ADDRESS: &str = "MIXNET_CONTRACT_ADDRESS";
pub const VESTING_CONTRACT_ADDRESS: &str = "VESTING_CONTRACT_ADDRESS";
pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "BANDWIDTH_CLAIM_CONTRACT_ADDRESS";
pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT_ADDRESS";
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
pub const NYMD_VALIDATOR: &str = "NYMD_VALIDATOR";
pub const API_VALIDATOR: &str = "API_VALIDATOR";
+1 -2
View File
@@ -34,8 +34,7 @@ mod error;
mod impls;
mod proofs;
mod scheme;
#[cfg(test)]
mod tests;
pub mod tests;
mod traits;
mod utils;
+3 -63
View File
@@ -1,23 +1,13 @@
use crate::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, prepare_blind_sign,
prove_bandwidth_credential, setup, ttp_keygen, verify_credential, CoconutError, Signature,
SignatureShare, VerificationKey,
aggregate_verification_keys, setup, tests::helpers::theta_from_keys_and_attributes, ttp_keygen,
verify_credential, CoconutError, VerificationKey,
};
use itertools::izip;
#[test]
fn main() -> Result<(), CoconutError> {
let params = setup(5)?;
let public_attributes = params.n_random_scalars(2);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
// generate commitment
let (commitments_openings, blind_sign_request) =
prepare_blind_sign(&params, &private_attributes, &public_attributes)?;
// generate_keys
let coconut_keypairs = ttp_keygen(&params, 2, 3)?;
@@ -30,58 +20,8 @@ fn main() -> Result<(), CoconutError> {
// aggregate verification keys
let verification_key = aggregate_verification_keys(&verification_keys, Some(&[1, 2, 3]))?;
// generate blinded signatures
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs {
let blinded_signature = blind_sign(
&params,
&keypair.secret_key(),
&blind_sign_request,
&public_attributes,
)?;
blinded_signatures.push(blinded_signature)
}
// Unblind
let unblinded_signatures: Vec<Signature> =
izip!(blinded_signatures.iter(), verification_keys.iter())
.map(|(s, vk)| {
s.unblind(
&params,
vk,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(&public_attributes);
// Randomize credentials and generate any cryptographic material to verify them
let signature =
aggregate_signature_shares(&params, &verification_key, &attributes, &signature_shares)?;
// Generate cryptographic material to verify them
let theta = prove_bandwidth_credential(
&params,
&verification_key,
&signature,
serial_number,
binding_number,
)?;
let theta = theta_from_keys_and_attributes(&params, &coconut_keypairs, &public_attributes)?;
// Verify credentials
assert!(verify_credential(
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::*;
use itertools::izip;
pub fn theta_from_keys_and_attributes(
params: &Parameters,
coconut_keypairs: &Vec<KeyPair>,
public_attributes: &Vec<PublicAttribute>,
) -> Result<Theta, CoconutError> {
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
// generate commitment
let (commitments_openings, blind_sign_request) =
prepare_blind_sign(params, &private_attributes, public_attributes)?;
let verification_keys: Vec<VerificationKey> = coconut_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// aggregate verification keys
let indices: Vec<u64> = coconut_keypairs
.iter()
.enumerate()
.map(|(idx, _)| (idx + 1) as u64)
.collect();
let verification_key = aggregate_verification_keys(&verification_keys, Some(&indices))?;
// generate blinded signatures
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs {
let blinded_signature = blind_sign(
params,
&keypair.secret_key(),
&blind_sign_request,
public_attributes,
)?;
blinded_signatures.push(blinded_signature)
}
// Unblind
let unblinded_signatures: Vec<Signature> =
izip!(blinded_signatures.iter(), verification_keys.iter())
.map(|(s, vk)| {
s.unblind(
params,
vk,
&private_attributes,
public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(public_attributes);
// Randomize credentials and generate any cryptographic material to verify them
let signature =
aggregate_signature_shares(params, &verification_key, &attributes, &signature_shares)?;
// Generate cryptographic material to verify them
let theta = prove_bandwidth_credential(
params,
&verification_key,
&signature,
serial_number,
binding_number,
)?;
Ok(theta)
}
+2
View File
@@ -1 +1,3 @@
#[cfg(test)]
mod e2e;
pub mod helpers;
-2
View File
@@ -17,5 +17,3 @@ serde_json = "1"
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]}
thiserror = "1"
tokio = { version = "1.19.1", features = [ "time" ] }
network-defaults = { path = "../network-defaults" }
+6 -2
View File
@@ -34,12 +34,16 @@ pub enum StatsData {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StatsGatewayData {
pub gateway_id: String,
pub inbox_count: u32,
}
impl StatsGatewayData {
pub fn new(inbox_count: u32) -> Self {
StatsGatewayData { inbox_count }
pub fn new(gateway_id: String, inbox_count: u32) -> Self {
StatsGatewayData {
gateway_id,
inbox_count,
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ url = "2.2"
ts-rs = "6.1.2"
cosmwasm-std = "1.0.0-beta8"
cosmrs = "0.7.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
validator-client = { path = "../../common/client-libs/validator-client", features = [
"nymd-client",
+20 -7
View File
@@ -1,4 +1,5 @@
use crate::currency::{CurrencyDenom, MajorCurrencyAmount};
use crate::currency::{CurrencyDenom, DecCoin};
use config::defaults::DenomDetails;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -9,17 +10,20 @@ use serde::{Deserialize, Serialize};
)]
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct Account {
pub contract_address: String,
pub client_address: String,
pub denom: CurrencyDenom,
pub base_mix_denom: String,
// this should get refactored to just use a String, but for now it's fine as it reduces headache
// for others
pub display_mix_denom: CurrencyDenom,
}
impl Account {
pub fn new(contract_address: String, client_address: String, denom: CurrencyDenom) -> Self {
pub fn new(client_address: String, mix_denom: DenomDetails) -> Self {
Account {
contract_address,
client_address,
denom,
base_mix_denom: mix_denom.base.to_owned(),
display_mix_denom: mix_denom.display.parse().unwrap_or_default(),
}
}
}
@@ -53,6 +57,15 @@ pub struct AccountEntry {
)]
#[derive(Serialize, Deserialize)]
pub struct Balance {
pub amount: MajorCurrencyAmount,
pub amount: DecCoin,
pub printable_balance: String,
}
impl Balance {
pub fn new(amount: DecCoin) -> Self {
Balance {
printable_balance: amount.to_string(),
amount,
}
}
}
+412 -384
View File
@@ -1,24 +1,29 @@
use crate::error::TypesError;
use cosmrs::Denom as CosmosDenom;
use cosmwasm_std::Coin as CosmWasmCoin;
use config::defaults::all::Network;
use config::defaults::{DenomDetails, DenomDetailsOwned};
use cosmwasm_std::Fraction;
use cosmwasm_std::{Decimal, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::ops::{Add, Mul};
use std::str::FromStr;
use strum::{Display, EnumString, EnumVariantNames};
use validator_client::nymd::{Coin, CosmosCoin};
use validator_client::nymd::Coin;
#[cfg(feature = "generate-ts")]
use ts_rs::{Dependency, TS};
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/CurrencyDenom.ts")
)]
#[cfg_attr(feature = "generate-ts", ts(rename_all = "UPPERCASE"))]
#[cfg_attr(feature = "generate-ts", ts(rename_all = "lowercase"))]
#[derive(
Display,
Default,
Serialize,
Deserialize,
Clone,
@@ -29,10 +34,12 @@ use validator_client::nymd::{Coin, CosmosCoin};
Eq,
JsonSchema,
)]
#[serde(rename_all = "UPPERCASE")]
#[strum(serialize_all = "UPPERCASE")]
// TODO: this shouldn't be an enum...
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum CurrencyDenom {
#[strum(ascii_case_insensitive)]
#[default]
Unknown,
#[strum(ascii_case_insensitive)]
Nym,
#[strum(ascii_case_insensitive)]
@@ -43,448 +50,469 @@ pub enum CurrencyDenom {
Nyxt,
}
impl CurrencyDenom {
pub fn parse(value: &str) -> Result<CurrencyDenom, TypesError> {
let mut denom = value.to_string();
if denom.starts_with('u') {
denom = denom[1..].to_string();
pub type Denom = String;
#[derive(Debug, Default)]
pub struct RegisteredCoins(HashMap<Denom, CoinMetadata>);
impl RegisteredCoins {
pub fn default_denoms(network: Network) -> Self {
let mut network_coins = HashMap::new();
network_coins.insert(network.mix_denom().base, network.mix_denom().into());
network_coins.insert(network.stake_denom().base, network.stake_denom().into());
RegisteredCoins(network_coins)
}
pub fn insert(&mut self, denom: Denom, metadata: CoinMetadata) -> Option<CoinMetadata> {
self.0.insert(denom, metadata)
}
pub fn remove(&mut self, denom: &Denom) -> Option<CoinMetadata> {
self.0.remove(denom)
}
pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result<Coin, TypesError> {
// check if this is already in the base denom
if self.0.contains_key(&coin.denom) {
// if we're converting a base DecCoin it CANNOT fail, unless somebody is providing
// bullshit data on purpose : )
return coin.try_into();
} else {
// TODO: this kinda suggests we may need a better data structure
for registered_coin in self.0.values() {
if let Some(exponent) = registered_coin.get_exponent(&coin.denom) {
let amount = try_convert_decimal_to_u128(coin.try_scale_up_value(exponent)?)?;
return Ok(Coin::new(amount, &registered_coin.base));
}
}
}
match CurrencyDenom::from_str(&denom) {
Ok(res) => Ok(res),
Err(_e) => Err(TypesError::InvalidDenom(value.to_string())),
Err(TypesError::UnknownCoinDenom(coin.denom))
}
pub fn attempt_convert_to_display_dec_coin(&self, coin: Coin) -> Result<DecCoin, TypesError> {
for registered_coin in self.0.values() {
if let Some(exponent) = registered_coin.get_exponent(&coin.denom) {
// if this fails it means we haven't registered our display denom which honestly should never be the case
// unless somebody is rocking their own custom network
let display_exponent = registered_coin
.get_exponent(&registered_coin.display)
.ok_or_else(|| TypesError::UnknownCoinDenom(coin.denom.clone()))?;
return match exponent.cmp(&display_exponent) {
Ordering::Greater => {
// we need to scale up, unlikely to ever be needed but included for completion sake,
// for example if we decided to created knym with exponent 9 and wanted to convert to nym with exponent 6
Ok(DecCoin::new_scaled_up(
coin.amount,
&registered_coin.display,
exponent - display_exponent,
)?)
}
// we're already in the display denom
Ordering::Equal => Ok(coin.into()),
Ordering::Less => {
// we need to scale down, the most common case, for example we're in base unym with exponent 0 and want to convert to nym with exponent 6
Ok(DecCoin::new_scaled_down(
coin.amount,
&registered_coin.display,
display_exponent - exponent,
)?)
}
};
}
}
Err(TypesError::UnknownCoinDenom(coin.denom))
}
}
impl TryFrom<CosmosDenom> for CurrencyDenom {
type Error = TypesError;
// TODO: should this live here?
// attempts to replicate cosmos-sdk's coin metadata
// https://docs.cosmos.network/master/architecture/adr-024-coin-metadata.html
// this way we could more easily handle multiple coin types simultaneously (like nym/nyx/nymt/nyx + local currencies)
#[derive(Debug)]
pub struct DenomUnit {
pub denom: Denom,
pub exponent: u32,
// pub aliases: Vec<String>,
}
fn try_from(value: CosmosDenom) -> Result<Self, Self::Error> {
CurrencyDenom::parse(value.as_ref())
impl DenomUnit {
pub fn new(denom: Denom, exponent: u32) -> Self {
DenomUnit { denom, exponent }
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/CurrencyStringMajorAmount.ts")
)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct MajorAmountString(String); // see https://github.com/Aleph-Alpha/ts-rs/issues/51 for exporting type aliases
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/Currency.ts")
)]
// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct MajorCurrencyAmount {
// temporarly going back to original impl to speed up merge
pub amount: MajorAmountString,
pub denom: CurrencyDenom,
// // temporary...
// #[cfg_attr(feature = "generate-ts", ts(skip))]
// pub coin: Coin,
#[derive(Debug)]
pub struct CoinMetadata {
pub denom_units: Vec<DenomUnit>,
pub base: Denom,
pub display: Denom,
}
// impl JsonSchema for MajorCurrencyAmount {
// fn schema_name() -> String {
// todo!()
// }
//
// fn json_schema(gen: &mut SchemaGenerator) -> Schema {
// todo!()
// }
// }
impl CoinMetadata {
pub fn new(denom_units: Vec<DenomUnit>, base: Denom, display: Denom) -> Self {
CoinMetadata {
denom_units,
base,
display,
}
}
pub fn get_exponent(&self, denom: &str) -> Option<u32> {
self.denom_units
.iter()
.find(|denom_unit| denom_unit.denom == denom)
.map(|denom_unit| denom_unit.exponent)
}
}
impl From<DenomDetails> for CoinMetadata {
fn from(denom_details: DenomDetails) -> Self {
CoinMetadata::new(
vec![
DenomUnit::new(denom_details.base.into(), 0),
DenomUnit::new(denom_details.display.into(), denom_details.display_exponent),
],
denom_details.base.into(),
denom_details.display.into(),
)
}
}
impl From<DenomDetailsOwned> for CoinMetadata {
fn from(denom_details: DenomDetailsOwned) -> Self {
CoinMetadata::new(
vec![
DenomUnit::new(denom_details.base.clone(), 0),
DenomUnit::new(
denom_details.display.clone(),
denom_details.display_exponent,
),
],
denom_details.base,
denom_details.display,
)
}
}
// tries to semi-replicate cosmos-sdk's DecCoin for being able to handle tokens with decimal amounts
// https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/types/dec_coin.go
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)]
pub struct DecCoin {
//
pub denom: Denom,
// Decimal is already serialized to string and using string in its schema, so lets also go straight to string for ts_rs
// todo: is `Decimal` the correct type to use? Do we want to depend on cosmwasm_std here?
pub amount: Decimal,
}
impl MajorCurrencyAmount {
pub fn new(amount: &str, denom: CurrencyDenom) -> MajorCurrencyAmount {
MajorCurrencyAmount {
amount: MajorAmountString(amount.to_string()),
denom,
// I had to implement it manually to correctly set dependencies
#[cfg(feature = "generate-ts")]
impl TS for DecCoin {
const EXPORT_TO: Option<&'static str> = Some("ts-packages/types/src/types/rust/DecCoin.ts");
fn decl() -> String {
format!("type {} = {};", Self::name(), Self::inline())
}
fn name() -> String {
"DecCoin".into()
}
fn inline() -> String {
"{ denom: CurrencyDenom, amount: string }".into()
}
fn dependencies() -> Vec<Dependency> {
vec![Dependency::from_ty::<CurrencyDenom>()
.expect("TS was incorrectly defined on `CurrencyDenom`")]
}
fn transparent() -> bool {
false
}
}
impl DecCoin {
pub fn new_base<S: Into<String>>(amount: impl Into<Uint128>, denom: S) -> Self {
DecCoin {
denom: denom.into(),
amount: Decimal::from_atomics(amount, 0).unwrap(),
}
}
pub fn zero(denom: &CurrencyDenom) -> MajorCurrencyAmount {
MajorCurrencyAmount::new("0", denom.clone())
pub fn zero<S: Into<String>>(denom: S) -> Self {
DecCoin {
denom: denom.into(),
amount: Decimal::zero(),
}
}
//
// pub fn from_cosmrs_coin(coin: &CosmosCoin) -> Result<MajorCurrencyAmount, TypesError> {
// MajorCurrencyAmount::from_cosmrs_decimal_and_denom(coin.amount, coin.denom.to_string())
// }
//
// pub fn from_minor_uint128_and_denom(
// amount_minor: Uint128,
// denom_minor: &str,
// ) -> Result<MajorCurrencyAmount, TypesError> {
// MajorCurrencyAmount::from_minor_decimal_and_denom(
// Decimal::from_atomics(amount_minor, 0)?,
// denom_minor,
// )
// }
//
// pub fn from_minor_decimal_and_denom(
// amount_minor: Decimal,
// denom_minor: &str,
// ) -> Result<MajorCurrencyAmount, TypesError> {
// if !(denom_minor.starts_with('u') || denom_minor.starts_with('U')) {
// return Err(TypesError::InvalidDenom(denom_minor.to_string()));
// }
// let major = amount_minor / Uint128::from(1_000_000u64);
// if let Ok(denom) = CurrencyDenom::from_str(&denom_minor[1..].to_string()) {
// return Ok(MajorCurrencyAmount {
// amount: MajorAmountString(major.to_string()),
// denom,
// });
// }
// Err(TypesError::InvalidDenom(denom_minor.to_string()))
// }
// pub fn from_decimal_and_denom(
// amount: Decimal,
// denom: String,
// ) -> Result<MajorCurrencyAmount, TypesError> {
// if denom.starts_with('u') || denom.starts_with('U') {
// return MajorCurrencyAmount::from_minor_decimal_and_denom(amount, &denom);
// }
// if let Ok(denom) = CurrencyDenom::from_str(denom.as_str()) {
// return Ok(MajorCurrencyAmount {
// amount: MajorAmountString(amount.to_string()),
// denom,
// });
// }
// Err(TypesError::InvalidDenom(denom))
// }
// pub fn from_cosmrs_decimal_and_denom(
// amount: CosmosDecimal,
// denom: String,
// ) -> Result<MajorCurrencyAmount, TypesError> {
// if denom.starts_with('u') || denom.starts_with('U') {
// return match Decimal::from_str(&amount.to_string()) {
// Ok(amount) => MajorCurrencyAmount::from_minor_decimal_and_denom(amount, &denom),
// Err(_e) => Err(TypesError::InvalidAmount(amount.to_string())),
// };
// }
//
// if let Ok(denom) = CurrencyDenom::from_str(denom.as_str()) {
// return Ok(MajorCurrencyAmount {
// amount: MajorAmountString(amount.to_string()),
// denom,
// });
// }
// Err(TypesError::InvalidDenom(denom))
// }
//
// pub fn into_cosmos_coin(self) -> CosmosCoin {
// self.coin.into()
// }
//
// pub fn to_minor_uint128(&self) -> Result<Uint128, TypesError> {
// if self.amount.0.contains('.') {
// // has a decimal point (Cosmos assumes "." is the decimal separator)
// let parts = self.amount.0.split('.');
// let str = parts.collect_vec();
// if str.is_empty() || str.len() > 2 {
// return Err(TypesError::InvalidAmount("Amount is invalid".to_string()));
// }
// if str.len() == 2 {
// // has a decimal, so check decimal places first
// if str[1].len() > 6 {
// return Err(TypesError::InvalidDenom(
// "Amount is invalid, only 6 decimal places of precision are allowed"
// .to_string(),
// ));
// }
//
// // so multiple whole part by 1e6 and add decimal part
// let whole_part = Uint128::from_str(str[0])? * Uint128::from(1_000_000u64);
//
// // TODO: has Rust got anything that deals with fixed point values, or parsing from format strings? Leading zeroes are causing issues
// return match format!("0.{}", str[1]).parse::<f64>() {
// Ok(decimal_part_float) => {
// // this makes an assumption that 6 decimal places of f64 can never lose precision
// let truncated = (decimal_part_float * 1_000_000.).trunc() as u32;
// let decimal_part = Uint128::from(truncated);
// let sum = whole_part + decimal_part;
// Ok(sum)
// }
// Err(_e) => Err(TypesError::InvalidAmount(
// "Amount decimal part is invalid".to_string(),
// )),
// };
// }
// }
//
// let major = Uint128::from_str(&self.amount.0)?;
// let scaled = major * Uint128::new(1_000_000u128);
// Ok(scaled)
// }
// pub fn denom_to_string(&self) -> String {
// self.denom.to_string()
// }
pub fn new_scaled_up<S: Into<String>>(
base_amount: impl Into<Uint128>,
denom: S,
exponent: u32,
) -> Result<Self, TypesError> {
let base_amount = Decimal::from_atomics(base_amount, 0).unwrap();
Ok(DecCoin {
denom: denom.into(),
amount: try_scale_up_decimal(base_amount, exponent)?,
})
}
pub fn new_scaled_down<S: Into<String>>(
base_amount: impl Into<Uint128>,
denom: S,
exponent: u32,
) -> Result<Self, TypesError> {
let base_amount = Decimal::from_atomics(base_amount, 0).unwrap();
Ok(DecCoin {
denom: denom.into(),
amount: try_scale_down_decimal(base_amount, exponent)?,
})
}
pub fn try_scale_down_value(&self, exponent: u32) -> Result<Decimal, TypesError> {
try_scale_down_decimal(self.amount, exponent)
}
pub fn try_scale_up_value(&self, exponent: u32) -> Result<Decimal, TypesError> {
try_scale_up_decimal(self.amount, exponent)
}
}
impl Display for MajorCurrencyAmount {
// TODO: should thoese live here?
pub fn try_scale_down_decimal(dec: Decimal, exponent: u32) -> Result<Decimal, TypesError> {
let rhs = 10u128
.checked_pow(exponent)
.ok_or(TypesError::UnsupportedExponent(exponent))?;
let denominator = dec
.denominator()
.checked_mul(rhs.into())
.map_err(|_| TypesError::UnsupportedExponent(exponent))?;
Ok(Decimal::from_ratio(dec.numerator(), denominator))
}
pub fn try_scale_up_decimal(dec: Decimal, exponent: u32) -> Result<Decimal, TypesError> {
let rhs = 10u128
.checked_pow(exponent)
.ok_or(TypesError::UnsupportedExponent(exponent))?;
let denominator = dec
.denominator()
.checked_div(rhs.into())
.map_err(|_| TypesError::UnsupportedExponent(exponent))?;
Ok(Decimal::from_ratio(dec.numerator(), denominator))
}
pub fn try_convert_decimal_to_u128(dec: Decimal) -> Result<u128, TypesError> {
let whole = dec.numerator() / dec.denominator();
// unwrap is fine as we're not dividing by zero here
let fractional = (dec.numerator()).checked_rem(dec.denominator()).unwrap();
// we cannot convert as we'd lose our decimal places
// (for example if somebody attempted to represent our gas price (WHICH YOU SHOULDN'T DO) as DecCoin)
if fractional != Uint128::zero() {
return Err(TypesError::LossyCoinConversion);
}
Ok(whole.u128())
}
impl Display for DecCoin {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.amount.0, self.denom)
write!(f, "{} {}", self.amount, self.denom)
}
}
// TODO: cleanup after merge
impl From<CosmosCoin> for MajorCurrencyAmount {
fn from(c: CosmosCoin) -> Self {
MajorCurrencyAmount::from(Coin::from(c))
}
}
impl From<CosmWasmCoin> for MajorCurrencyAmount {
fn from(c: CosmWasmCoin) -> Self {
MajorCurrencyAmount::from(Coin::from(c))
}
}
impl From<Coin> for MajorCurrencyAmount {
impl From<Coin> for DecCoin {
fn from(coin: Coin) -> Self {
// current assumption: MajorCurrencyAmount is represented as decimal with 6 decimal points
// unwrap is fine as we haven't exceeded decimal range since our coins are at max 1B in value
// (this is a weak assumption, but for solving this merge conflict it's good enough temporary workaround)
let amount = Decimal::from_atomics(coin.amount, 6).unwrap();
MajorCurrencyAmount {
amount: MajorAmountString(amount.to_string()),
denom: CurrencyDenom::parse(&coin.denom).expect("this will go away after the merge..."),
}
DecCoin::new_base(coin.amount, coin.denom)
}
}
// temporary...
impl From<MajorCurrencyAmount> for CosmosCoin {
fn from(c: MajorCurrencyAmount) -> CosmosCoin {
let c: Coin = c.into();
c.into()
}
}
// this conversion assumes same denomination
impl TryFrom<DecCoin> for Coin {
type Error = TypesError;
impl From<MajorCurrencyAmount> for CosmWasmCoin {
fn from(c: MajorCurrencyAmount) -> CosmWasmCoin {
let c: Coin = c.into();
c.into()
}
}
impl From<MajorCurrencyAmount> for Coin {
fn from(c: MajorCurrencyAmount) -> Coin {
let decimal: Decimal = c
.amount
.0
.parse()
.expect("stringified amount should have been a valid decimal");
// again, temporary
let exp = Uint128::new(1000000);
let val = decimal.mul(exp);
// again, terrible assumption for denom, but it works temporarily...
Coin {
amount: val.u128(),
denom: format!("u{}", c.denom).to_lowercase(),
}
}
}
impl Add for MajorCurrencyAmount {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
// again, temporary workaround to help with merge
(Coin::from(self).try_add(&Coin::from(rhs)))
.expect("provided coins had different denoms")
.into()
fn try_from(value: DecCoin) -> Result<Self, Self::Error> {
Ok(Coin {
amount: try_convert_decimal_to_u128(value.try_scale_down_value(0)?)?,
denom: value.denom,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use cosmrs::Coin as CosmosCoin;
use cosmrs::Decimal as CosmosDecimal;
use cosmrs::Denom as CosmosDenom;
use cosmwasm_std::Coin as CosmWasmCoin;
use cosmwasm_std::Decimal as CosmWasmDecimal;
use serde_json::json;
use std::str::FromStr;
use std::convert::TryFrom;
use std::string::ToString;
#[test]
fn json_to_major_currency_amount() {
let nym = json!({
"amount": "1",
"denom": "NYM"
});
let nymt = json!({
"amount": "1",
"denom": "NYMT"
});
fn dec_value_scale_down() {
let dec = DecCoin {
denom: "foo".to_string(),
amount: "1234007000".parse().unwrap(),
};
let test_nym_amount = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
let test_nymt_amount = MajorCurrencyAmount::new("1", CurrencyDenom::Nymt);
let nym_amount = serde_json::from_value::<MajorCurrencyAmount>(nym).unwrap();
let nymt_amount = serde_json::from_value::<MajorCurrencyAmount>(nymt).unwrap();
assert_eq!(nym_amount, test_nym_amount);
assert_eq!(nymt_amount, test_nymt_amount);
}
#[test]
fn minor_amount_json_to_major_currency_amount() {
let one_micro_nym = json!({
"amount": "0.000001",
"denom": "NYM"
});
let expected_nym_amount = MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym);
let actual_nym_amount =
serde_json::from_value::<MajorCurrencyAmount>(one_micro_nym).unwrap();
assert_eq!(expected_nym_amount, actual_nym_amount);
}
#[test]
fn denom_from_str() {
assert_eq!(CurrencyDenom::from_str("nym").unwrap(), CurrencyDenom::Nym);
assert_eq!(
CurrencyDenom::from_str("nymt").unwrap(),
CurrencyDenom::Nymt
);
assert_eq!(CurrencyDenom::from_str("NYM").unwrap(), CurrencyDenom::Nym);
assert_eq!(
CurrencyDenom::from_str("NYMT").unwrap(),
CurrencyDenom::Nymt
);
assert_eq!(CurrencyDenom::from_str("NyM").unwrap(), CurrencyDenom::Nym);
assert_eq!(
CurrencyDenom::from_str("NYmt").unwrap(),
CurrencyDenom::Nymt
);
assert!(matches!(
CurrencyDenom::from_str("foo").unwrap_err(),
strum::ParseError::VariantNotFound,
));
// denominations must all be major
assert!(matches!(
CurrencyDenom::from_str("unym").unwrap_err(),
strum::ParseError::VariantNotFound,
));
assert!(matches!(
CurrencyDenom::from_str("unymt").unwrap_err(),
strum::ParseError::VariantNotFound,
));
}
#[test]
fn to_string() {
assert_eq!(
MajorCurrencyAmount::new("1", CurrencyDenom::Nym).to_string(),
"1 NYM"
"1234007000".parse::<Decimal>().unwrap(),
dec.try_scale_down_value(0).unwrap()
);
assert_eq!(
MajorCurrencyAmount::new("1", CurrencyDenom::Nymt).to_string(),
"1 NYMT"
"123400700".parse::<Decimal>().unwrap(),
dec.try_scale_down_value(1).unwrap()
);
assert_eq!(
MajorCurrencyAmount::new("1000000000000", CurrencyDenom::Nym).to_string(),
"1000000000000 NYM"
"12340070".parse::<Decimal>().unwrap(),
dec.try_scale_down_value(2).unwrap()
);
assert_eq!(
"123400.7".parse::<Decimal>().unwrap(),
dec.try_scale_down_value(4).unwrap()
);
let dec = DecCoin {
denom: "foo".to_string(),
amount: "10000000000".parse().unwrap(),
};
assert_eq!(
"100".parse::<Decimal>().unwrap(),
dec.try_scale_down_value(8).unwrap()
);
assert_eq!(
"1".parse::<Decimal>().unwrap(),
dec.try_scale_down_value(10).unwrap()
);
assert_eq!(
"0.01".parse::<Decimal>().unwrap(),
dec.try_scale_down_value(12).unwrap()
);
}
#[test]
fn minor_coin_to_major_currency() {
let cosmos_coin = CosmosCoin {
amount: CosmosDecimal::from(1u64),
denom: CosmosDenom::from_str("unym").unwrap(),
fn dec_value_scale_up() {
let dec = DecCoin {
denom: "foo".to_string(),
amount: "1234.56".parse().unwrap(),
};
let c = MajorCurrencyAmount::from(cosmos_coin);
assert_eq!(c, MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym));
}
#[test]
fn minor_cosmwasm_coin_to_major_currency() {
let coin = CosmWasmCoin {
amount: Uint128::from(1u64),
denom: "unym".to_string(),
};
println!(
"from_atomics = {}",
CosmWasmDecimal::from_atomics(coin.amount, 6).unwrap()
assert_eq!(
"1234.56".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(0).unwrap()
);
let c: MajorCurrencyAmount = coin.into();
assert_eq!(c, MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym));
}
#[test]
fn minor_cosmwasm_coin_to_major_currency_2() {
let coin = CosmWasmCoin {
amount: Uint128::from(1_000_000u64),
denom: "unym".to_string(),
};
println!(
"from_atomics = {:?}",
CosmWasmDecimal::from_atomics(coin.amount, 6)
.unwrap()
.to_string()
assert_eq!(
"12345.6".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(1).unwrap()
);
assert_eq!(
"123456".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(2).unwrap()
);
assert_eq!(
"1234560".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(3).unwrap()
);
assert_eq!(
"12345600".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(4).unwrap()
);
let c: MajorCurrencyAmount = coin.into();
assert_eq!(c, MajorCurrencyAmount::new("1", CurrencyDenom::Nym));
}
#[test]
fn major_currency_to_minor_cosmos_coin() {
let expected_cosmos_coin = CosmosCoin {
amount: CosmosDecimal::from(1u64),
denom: CosmosDenom::from_str("unym").unwrap(),
let dec = DecCoin {
denom: "foo".to_string(),
amount: "0.00000123".parse().unwrap(),
};
let c = MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym);
let minor_cosmos_coin = c.into();
assert_eq!(expected_cosmos_coin, minor_cosmos_coin);
assert_eq!("unym", minor_cosmos_coin.denom.to_string());
assert_eq!(
"0.0000123".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(1).unwrap()
);
assert_eq!(
"0.000123".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(2).unwrap()
);
assert_eq!(
"123".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(8).unwrap()
);
assert_eq!(
"1230".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(9).unwrap()
);
assert_eq!(
"12300".parse::<Decimal>().unwrap(),
dec.try_scale_up_value(10).unwrap()
);
}
#[test]
fn major_currency_to_minor_cosmos_coin_2() {
let expected_cosmos_coin = CosmosCoin {
amount: CosmosDecimal::from(1000000u64),
denom: CosmosDenom::from_str("unym").unwrap(),
fn coin_to_dec_coin() {
let coin = Coin::new(123, "foo");
let dec = DecCoin::from(coin.clone());
assert_eq!(coin.denom, dec.denom);
assert_eq!(dec.amount, Decimal::from_atomics(coin.amount, 0).unwrap());
}
#[test]
fn dec_coin_to_coin() {
let dec = DecCoin {
denom: "foo".to_string(),
amount: "123".parse().unwrap(),
};
let c = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
let minor_cosmos_coin = c.into();
assert_eq!(expected_cosmos_coin, minor_cosmos_coin);
assert_eq!("unym", minor_cosmos_coin.denom.to_string());
let coin = Coin::try_from(dec.clone()).unwrap();
assert_eq!(dec.denom, coin.denom);
assert_eq!(coin.amount, 123u128);
}
#[test]
fn minor_cosmos_coin_to_major_currency_string() {
// check minor cosmos coin is converted to major value
let cosmos_coin = CosmosCoin {
amount: CosmosDecimal::from(1u64),
denom: CosmosDenom::from_str("unym").unwrap(),
};
let c = MajorCurrencyAmount::from(cosmos_coin);
assert_eq!(c.to_string(), "0.000001 NYM");
fn converting_to_display() {
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
let values = vec![
(1u128, "0.000001"),
(10u128, "0.00001"),
(100u128, "0.0001"),
(1000u128, "0.001"),
(10000u128, "0.01"),
(100000u128, "0.1"),
(1000000u128, "1"),
(1234567u128, "1.234567"),
(123456700u128, "123.4567"),
];
for (raw, expected) in values {
let coin = Coin::new(raw, Network::MAINNET.mix_denom().base);
let display = reg.attempt_convert_to_display_dec_coin(coin).unwrap();
assert_eq!(Network::MAINNET.mix_denom().display, display.denom);
assert_eq!(expected, display.amount.to_string());
}
}
#[test]
fn denom_to_string() {
let c = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
let denom = c.denom.to_string();
assert_eq!(denom, "NYM".to_string());
fn converting_to_base() {
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
let values = vec![
(1u128, "0.000001"),
(10u128, "0.00001"),
(100u128, "0.0001"),
(1000u128, "0.001"),
(10000u128, "0.01"),
(100000u128, "0.1"),
(1000000u128, "1"),
(1234567u128, "1.234567"),
(123456700u128, "123.4567"),
];
for (expected, raw_display) in values {
let coin = DecCoin {
denom: Network::MAINNET.mix_denom().display.into(),
amount: raw_display.parse().unwrap(),
};
let base = reg.attempt_convert_to_base_coin(coin).unwrap();
assert_eq!(Network::MAINNET.mix_denom().base, base.denom);
assert_eq!(expected, base.amount);
}
}
}
+41 -106
View File
@@ -1,13 +1,10 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use log::error;
use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use mixnet_contract_common::mixnode::DelegationEvent as ContractDelegationEvent;
use mixnet_contract_common::mixnode::PendingUndelegate as ContractPendingUndelegate;
use mixnet_contract_common::Delegation as MixnetContractDelegation;
use crate::currency::MajorCurrencyAmount;
use crate::error::TypesError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
@@ -18,31 +15,22 @@ use crate::error::TypesError;
pub struct Delegation {
pub owner: String,
pub node_identity: String,
pub amount: MajorCurrencyAmount,
pub amount: DecCoin,
pub block_height: u64,
pub proxy: Option<String>, // proxy address used to delegate the funds on behalf of another address
}
impl TryFrom<MixnetContractDelegation> for Delegation {
type Error = TypesError;
fn try_from(value: MixnetContractDelegation) -> Result<Self, Self::Error> {
let MixnetContractDelegation {
owner,
node_identity,
amount,
block_height,
proxy,
} = value;
let amount: MajorCurrencyAmount = amount.into();
impl Delegation {
pub fn from_mixnet_contract(
delegation: MixnetContractDelegation,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
Ok(Delegation {
owner: owner.into_string(),
node_identity,
amount,
block_height,
proxy: proxy.map(|p| p.into_string()),
owner: delegation.owner.to_string(),
node_identity: delegation.node_identity,
amount: reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?,
block_height: delegation.block_height,
proxy: delegation.proxy.map(|d| d.to_string()),
})
}
}
@@ -54,7 +42,7 @@ impl TryFrom<MixnetContractDelegation> for Delegation {
)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct DelegationRecord {
pub amount: MajorCurrencyAmount,
pub amount: DecCoin,
pub block_height: u64,
pub delegated_on_iso_datetime: String,
pub uses_vesting_contract_tokens: bool,
@@ -69,16 +57,16 @@ pub struct DelegationRecord {
pub struct DelegationWithEverything {
pub owner: String,
pub node_identity: String,
pub amount: MajorCurrencyAmount,
pub total_delegation: Option<MajorCurrencyAmount>,
pub pledge_amount: Option<MajorCurrencyAmount>,
pub amount: DecCoin,
pub total_delegation: Option<DecCoin>,
pub pledge_amount: Option<DecCoin>,
pub block_height: u64,
pub delegated_on_iso_datetime: String,
pub profit_margin_percent: Option<u8>,
pub avg_uptime_percent: Option<u8>,
pub stake_saturation: Option<f32>,
pub uses_vesting_contract_tokens: bool,
pub accumulated_rewards: Option<MajorCurrencyAmount>,
pub accumulated_rewards: Option<DecCoin>,
pub pending_events: Vec<DelegationEvent>,
pub history: Vec<DelegationRecord>,
}
@@ -92,34 +80,7 @@ pub struct DelegationWithEverything {
pub struct DelegationResult {
source_address: String,
target_address: String,
amount: Option<MajorCurrencyAmount>,
}
impl DelegationResult {
pub fn new(
source_address: &str,
target_address: &str,
amount: Option<MajorCurrencyAmount>,
) -> DelegationResult {
DelegationResult {
source_address: source_address.to_string(),
target_address: target_address.to_string(),
amount,
}
}
}
impl TryFrom<MixnetContractDelegation> for DelegationResult {
type Error = TypesError;
fn try_from(delegation: MixnetContractDelegation) -> Result<Self, Self::Error> {
let amount: MajorCurrencyAmount = delegation.amount.clone().into();
Ok(DelegationResult {
source_address: delegation.owner().to_string(),
target_address: delegation.node_identity(),
amount: Some(amount),
})
}
amount: Option<DecCoin>,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
@@ -143,36 +104,34 @@ pub struct DelegationEvent {
pub kind: DelegationEventKind,
pub node_identity: String,
pub address: String,
pub amount: Option<MajorCurrencyAmount>,
pub amount: Option<DecCoin>,
pub block_height: u64,
pub proxy: Option<String>,
}
impl TryFrom<ContractDelegationEvent> for DelegationEvent {
type Error = TypesError;
fn try_from(event: ContractDelegationEvent) -> Result<Self, Self::Error> {
match event {
ContractDelegationEvent::Delegate(delegation) => {
let amount: MajorCurrencyAmount = delegation.amount.into();
Ok(DelegationEvent {
kind: DelegationEventKind::Delegate,
block_height: delegation.block_height,
address: delegation.owner.into_string(),
node_identity: delegation.node_identity,
amount: Some(amount),
proxy: delegation.proxy.map(|p| p.into_string()),
})
}
ContractDelegationEvent::Undelegate(pending_undelegate) => Ok(DelegationEvent {
impl DelegationEvent {
pub fn from_mixnet_contract(
event: ContractDelegationEvent,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
Ok(match event {
ContractDelegationEvent::Delegate(delegation) => DelegationEvent {
kind: DelegationEventKind::Delegate,
block_height: delegation.block_height,
address: delegation.owner.into_string(),
node_identity: delegation.node_identity,
amount: Some(reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?),
proxy: delegation.proxy.map(|p| p.into_string()),
},
ContractDelegationEvent::Undelegate(pending_undelegate) => DelegationEvent {
kind: DelegationEventKind::Undelegate,
block_height: pending_undelegate.block_height(),
address: pending_undelegate.delegate().into_string(),
node_identity: pending_undelegate.mix_identity(),
amount: None,
proxy: pending_undelegate.proxy().map(|p| p.into_string()),
}),
}
},
})
}
}
@@ -200,30 +159,6 @@ impl From<ContractPendingUndelegate> for PendingUndelegate {
}
}
pub fn from_contract_delegation_events(
events: Vec<ContractDelegationEvent>,
) -> Result<Vec<DelegationEvent>, TypesError> {
let (events, errors): (Vec<_>, Vec<_>) = events
.into_iter()
.map(|delegation_event| delegation_event.try_into())
.partition(Result::is_ok);
if errors.is_empty() {
let events = events
.into_iter()
.filter_map(|e| e.ok())
.collect::<Vec<DelegationEvent>>();
return Ok(events);
}
let errors = errors
.into_iter()
.filter_map(|e| e.err())
.collect::<Vec<TypesError>>();
error!("Failed to convert delegations: {:?}", errors);
Err(TypesError::DelegationsInvalid)
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
@@ -232,6 +167,6 @@ pub fn from_contract_delegation_events(
#[derive(Deserialize, Serialize)]
pub struct DelegationsSummaryResponse {
pub delegations: Vec<DelegationWithEverything>,
pub total_delegations: MajorCurrencyAmount,
pub total_rewards: MajorCurrencyAmount,
pub total_delegations: DecCoin,
pub total_rewards: DecCoin,
}
+7
View File
@@ -4,6 +4,7 @@ use thiserror::Error;
use validator_client::validator_api::error::ValidatorAPIError;
use validator_client::{nymd::error::NymdError, ValidatorClientError};
// TODO: ask @MS why this even exists
#[derive(Error, Debug)]
pub enum TypesError {
#[error("{source}")]
@@ -63,6 +64,12 @@ pub enum TypesError {
InvalidGatewayBond(),
#[error("Invalid delegations")]
DelegationsInvalid,
#[error("Attempted to use too huge currency exponent ({0})")]
UnsupportedExponent(u32),
#[error("Attempted to convert coin that would have resulted in loss of precision")]
LossyCoinConversion,
#[error("The provided coin has an unknown denomination - {0}")]
UnknownCoinDenom(String),
}
impl Serialize for TypesError {
+10 -5
View File
@@ -1,4 +1,4 @@
use crate::currency::MajorCurrencyAmount;
use crate::currency::DecCoin;
use serde::{Deserialize, Serialize};
use validator_client::nymd::Fee;
@@ -8,10 +8,16 @@ use ts_rs::{Dependency, TS};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeDetails {
// expected to be used by the wallet in order to display detailed fee information to the user
pub amount: Option<MajorCurrencyAmount>,
pub amount: Option<DecCoin>,
pub fee: Fee,
}
impl FeeDetails {
pub fn new(amount: Option<DecCoin>, fee: Fee) -> Self {
FeeDetails { amount, fee }
}
}
#[cfg(feature = "generate-ts")]
impl TS for FeeDetails {
const EXPORT_TO: Option<&'static str> = Some("ts-packages/types/src/types/rust/FeeDetails.ts");
@@ -25,13 +31,12 @@ impl TS for FeeDetails {
}
fn inline() -> String {
"{ amount: MajorCurrencyAmount | null, fee: Fee }".into()
"{ amount: DecCoin | null, fee: Fee }".into()
}
fn dependencies() -> Vec<Dependency> {
vec![
Dependency::from_ty::<MajorCurrencyAmount>()
.expect("TS was incorrectly defined on `CurrencyDenom`"),
Dependency::from_ty::<DecCoin>().expect("TS was incorrectly defined on `DecCoin`"),
Dependency::from_ty::<ts_type_helpers::Fee>()
.expect("TS was incorrectly defined on `ts_type_helpers::Fee`"),
]
+26 -60
View File
@@ -1,48 +1,29 @@
use crate::currency::MajorCurrencyAmount;
use crate::error::TypesError;
use cosmrs::tx::Gas as CosmrsGas;
use serde::{Deserialize, Serialize};
use validator_client::nymd::cosmwasm_client::types::GasInfo as ValidatorClientGasInfo;
use validator_client::nymd::GasPrice;
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/Gas.ts")
)]
#[derive(Deserialize, Serialize, Clone)]
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
pub struct Gas {
/// units of gas used
pub gas_units: u64,
//
// /// gas units converted to fee as major coin amount
// pub amount: MajorCurrencyAmount,
}
impl Gas {
pub fn from_cosmrs_gas(value: CosmrsGas, _denom_minor: &str) -> Result<Gas, TypesError> {
Ok(Gas {
gas_units: value.value(),
})
// // TODO: use simulator struct to do conversion to fee
// let value_u128 = Uint128::from(value.value());
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
// Ok(Gas {
// gas_units: value.value(),
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
// })
pub fn from_u64(value: u64) -> Gas {
Gas { gas_units: value }
}
pub fn from_u64(value: u64, _denom_minor: &str) -> Result<Gas, TypesError> {
Ok(Gas { gas_units: value })
// todo!()
// // TODO: use simulator struct to do conversion to fee
// let value_u128 = Uint128::from(value);
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
// Ok(Gas {
// gas_units: value,
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
// })
}
impl From<CosmrsGas> for Gas {
fn from(gas: CosmrsGas) -> Self {
Gas {
gas_units: gas.value(),
}
}
}
@@ -51,44 +32,29 @@ impl Gas {
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/GasInfo.ts")
)]
#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
pub struct GasInfo {
/// GasWanted is the maximum units of work we allow this tx to perform.
pub gas_wanted: u64,
pub gas_wanted: Gas,
/// GasUsed is the amount of gas actually consumed.
pub gas_used: u64,
pub gas_used: Gas,
}
/// gas units converted to fee as major coin amount
pub fee: MajorCurrencyAmount,
impl From<ValidatorClientGasInfo> for GasInfo {
fn from(info: ValidatorClientGasInfo) -> Self {
GasInfo {
gas_wanted: info.gas_wanted.into(),
gas_used: info.gas_used.into(),
}
}
}
impl GasInfo {
pub fn from_validator_client_gas_info(
value: ValidatorClientGasInfo,
denom_minor: &str,
) -> Result<GasInfo, TypesError> {
// terrible workaround, but I don't want to break the current flow (just yet)
let gas_price = GasPrice::new_with_default_price(denom_minor)?;
let fee = (&gas_price) * value.gas_used;
Ok(GasInfo {
gas_wanted: value.gas_wanted.value(),
gas_used: value.gas_used.value(),
fee: fee.into(),
})
}
pub fn from_u64(
gas_wanted: u64,
gas_used: u64,
denom_minor: &str,
) -> Result<GasInfo, TypesError> {
// terrible workaround, but I don't want to break the current flow (just yet)
let gas_price = GasPrice::new_with_default_price(denom_minor)?;
let fee = (&gas_price) * CosmrsGas::from(gas_used);
Ok(GasInfo {
gas_wanted,
gas_used,
fee: fee.into(),
})
pub fn from_u64(gas_wanted: u64, gas_used: u64) -> GasInfo {
GasInfo {
gas_wanted: Gas::from_u64(gas_wanted),
gas_used: Gas::from_u64(gas_used),
}
}
}
+10 -33
View File
@@ -1,4 +1,4 @@
use crate::currency::MajorCurrencyAmount;
use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use mixnet_contract_common::{
Gateway as MixnetContractGateway, GatewayBond as MixnetContractGatewayBond,
@@ -54,7 +54,7 @@ impl From<MixnetContractGateway> for Gateway {
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayBond {
pub pledge_amount: MajorCurrencyAmount,
pub pledge_amount: DecCoin,
pub owner: String,
pub block_height: u64,
pub gateway: Gateway,
@@ -63,38 +63,15 @@ pub struct GatewayBond {
impl GatewayBond {
pub fn from_mixnet_contract_gateway_bond(
bond: Option<MixnetContractGatewayBond>,
) -> Result<Option<GatewayBond>, TypesError> {
match bond {
Some(bond) => {
let bond: GatewayBond = bond.try_into()?;
Ok(Some(bond))
}
None => Ok(None),
}
}
}
impl TryFrom<MixnetContractGatewayBond> for GatewayBond {
type Error = TypesError;
fn try_from(value: MixnetContractGatewayBond) -> Result<Self, Self::Error> {
let MixnetContractGatewayBond {
pledge_amount,
owner,
block_height,
gateway,
proxy,
} = value;
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
bond: MixnetContractGatewayBond,
reg: &RegisteredCoins,
) -> Result<GatewayBond, TypesError> {
Ok(GatewayBond {
pledge_amount,
owner: owner.into_string(),
block_height,
gateway: gateway.into(),
proxy: proxy.map(|p| p.into_string()),
pledge_amount: reg.attempt_convert_to_display_dec_coin(bond.pledge_amount.into())?,
owner: bond.owner.to_string(),
block_height: bond.block_height,
gateway: bond.gateway.into(),
proxy: bond.proxy.map(|p| p.into_string()),
})
}
}
+26 -54
View File
@@ -1,11 +1,11 @@
use crate::currency::MajorCurrencyAmount;
use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use mixnet_contract_common::{
Coin as CosmWasmCoin, MixNode as MixnetContractMixNode,
MixNodeBond as MixnetContractMixNodeBond,
MixNode as MixnetContractMixNode, MixNodeBond as MixnetContractMixNodeBond,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use validator_client::nymd::Coin;
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
@@ -58,67 +58,39 @@ impl From<MixnetContractMixNode> for MixNode {
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub pledge_amount: MajorCurrencyAmount,
pub total_delegation: MajorCurrencyAmount,
pub pledge_amount: DecCoin,
pub total_delegation: DecCoin,
pub owner: String,
pub layer: String,
pub block_height: u64,
pub mix_node: MixNode,
pub proxy: Option<String>,
pub accumulated_rewards: Option<MajorCurrencyAmount>,
pub accumulated_rewards: Option<DecCoin>,
}
impl MixNodeBond {
pub fn from_mixnet_contract_mixnode_bond(
bond: Option<MixnetContractMixNodeBond>,
) -> Result<Option<MixNodeBond>, TypesError> {
match bond {
Some(bond) => {
let bond: MixNodeBond = bond.try_into()?;
Ok(Some(bond))
}
None => Ok(None),
}
}
}
impl TryFrom<MixnetContractMixNodeBond> for MixNodeBond {
type Error = TypesError;
fn try_from(value: MixnetContractMixNodeBond) -> Result<Self, Self::Error> {
let MixnetContractMixNodeBond {
pledge_amount,
total_delegation,
owner,
layer,
block_height,
mix_node,
proxy,
accumulated_rewards,
} = value;
if pledge_amount.denom != total_delegation.denom {
return Err(TypesError::InvalidDenom(
"The pledge and delegation denominations do not match".to_string(),
));
}
let denom = total_delegation.denom.clone();
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
let total_delegation: MajorCurrencyAmount = total_delegation.into();
let accumulated_rewards: Option<MajorCurrencyAmount> =
accumulated_rewards.map(|r| CosmWasmCoin::new(r.u128(), denom).into());
bond: MixnetContractMixNodeBond,
reg: &RegisteredCoins,
) -> Result<MixNodeBond, TypesError> {
let denom = bond.pledge_amount.denom.clone();
Ok(MixNodeBond {
pledge_amount,
total_delegation,
owner: owner.into_string(),
layer: layer.into(),
block_height,
mix_node: mix_node.into(),
proxy: proxy.map(|p| p.into_string()),
accumulated_rewards,
pledge_amount: reg.attempt_convert_to_display_dec_coin(bond.pledge_amount.into())?,
total_delegation: reg
.attempt_convert_to_display_dec_coin(bond.total_delegation.into())?,
owner: bond.owner.into_string(),
layer: bond.layer.into(),
block_height: bond.block_height,
mix_node: bond.mix_node.into(),
proxy: bond.proxy.map(|p| p.to_string()),
accumulated_rewards: bond
.accumulated_rewards
.map(|reward| {
// here we're making an assumption that rewards always use the same denom as the pledge
// (which I think is a reasonable assumption)
reg.attempt_convert_to_display_dec_coin(Coin::new(reward.u128(), denom))
})
.transpose()?,
})
}
}
+37 -43
View File
@@ -1,6 +1,6 @@
use crate::currency::MajorCurrencyAmount;
use crate::currency::DecCoin;
use crate::error::TypesError;
use crate::gas::GasInfo;
use crate::gas::{Gas, GasInfo};
use serde::{Deserialize, Serialize};
use validator_client::nymd::cosmwasm_client::types::ExecuteResult;
use validator_client::nymd::TxResponse;
@@ -15,31 +15,23 @@ pub struct SendTxResult {
pub block_height: u64,
pub code: u32,
pub details: TransactionDetails,
pub gas_used: u64,
pub gas_wanted: u64,
pub gas_used: Gas,
pub gas_wanted: Gas,
pub tx_hash: String,
// pub fee: MajorCurrencyAmount,
pub fee: Option<DecCoin>,
}
impl SendTxResult {
pub fn new(
t: TxResponse,
details: TransactionDetails,
_denom_minor: &str,
) -> Result<SendTxResult, TypesError> {
Ok(SendTxResult {
pub fn new(t: TxResponse, details: TransactionDetails, fee: Option<DecCoin>) -> SendTxResult {
SendTxResult {
block_height: t.height.value(),
code: t.tx_result.code.value(),
details,
gas_used: t.tx_result.gas_used.value(),
gas_wanted: t.tx_result.gas_wanted.value(),
gas_used: t.tx_result.gas_used.into(),
gas_wanted: t.tx_result.gas_wanted.into(),
tx_hash: t.hash.to_string(),
// that is completely wrong: fee is what you told the validator to use beforehand
// fee: MajorCurrencyAmount::from_decimal_and_denom(
// Decimal::new(Uint128::from(t.tx_result.gas_used.value())),
// denom_minor.to_string(),
// )?,
})
fee,
}
}
}
@@ -50,11 +42,21 @@ impl SendTxResult {
)]
#[derive(Deserialize, Serialize, Debug)]
pub struct TransactionDetails {
pub amount: MajorCurrencyAmount,
pub amount: DecCoin,
pub from_address: String,
pub to_address: String,
}
impl TransactionDetails {
pub fn new(amount: DecCoin, from_address: String, to_address: String) -> Self {
TransactionDetails {
amount,
from_address,
to_address,
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
@@ -66,18 +68,16 @@ pub struct TransactionExecuteResult {
pub data_json: String,
pub transaction_hash: String,
pub gas_info: GasInfo,
pub fee: MajorCurrencyAmount,
pub fee: Option<DecCoin>,
}
impl TransactionExecuteResult {
pub fn from_execute_result(
value: ExecuteResult,
denom_minor: &str,
fee: Option<DecCoin>,
) -> Result<TransactionExecuteResult, TypesError> {
let gas_info = GasInfo::from_validator_client_gas_info(value.gas_info, denom_minor)?;
let fee = gas_info.fee.clone();
Ok(TransactionExecuteResult {
gas_info,
gas_info: value.gas_info.into(),
transaction_hash: value.transaction_hash.to_string(),
data_json: ::serde_json::to_string_pretty(&value.data)?,
logs_json: ::serde_json::to_string_pretty(&value.logs)?,
@@ -97,30 +97,24 @@ pub struct RpcTransactionResponse {
pub tx_result_json: String,
pub block_height: u64,
pub transaction_hash: String,
pub gas_info: GasInfo,
// pub fee: MajorCurrencyAmount,
pub gas_used: Gas,
pub gas_wanted: Gas,
pub fee: Option<DecCoin>,
}
impl RpcTransactionResponse {
pub fn from_tx_response(
value: &TxResponse,
denom_minor: &str,
t: &TxResponse,
fee: Option<DecCoin>,
) -> Result<RpcTransactionResponse, TypesError> {
Ok(RpcTransactionResponse {
index: value.index,
gas_info: GasInfo::from_u64(
value.tx_result.gas_wanted.value(),
value.tx_result.gas_used.value(),
denom_minor,
)?,
transaction_hash: value.hash.to_string(),
tx_result_json: ::serde_json::to_string_pretty(&value.tx_result)?,
block_height: value.height.value(),
// wrong
// fee: MajorCurrencyAmount::from_decimal_and_denom(
// Decimal::new(Uint128::from(value.tx_result.gas_used.value())),
// denom_minor.to_string(),
// )?,
index: t.index,
gas_used: t.tx_result.gas_used.into(),
gas_wanted: t.tx_result.gas_wanted.into(),
transaction_hash: t.hash.to_string(),
tx_result_json: ::serde_json::to_string_pretty(&t.tx_result)?,
block_height: t.height.value(),
fee,
})
}
}
+35 -45
View File
@@ -1,10 +1,10 @@
use crate::currency::MajorCurrencyAmount;
use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use serde::{Deserialize, Serialize};
use vesting_contract::vesting::Account as VestingAccount;
use vesting_contract::vesting::VestingPeriod as VestingVestingPeriod;
use vesting_contract_common::OriginalVestingResponse as VestingOriginalVestingResponse;
use vesting_contract_common::PledgeData as VestingPledgeData;
use vesting_contract::vesting::Account as ContractVestingAccount;
use vesting_contract::vesting::VestingPeriod as ContractVestingPeriod;
use vesting_contract_common::OriginalVestingResponse as ContractOriginalVestingResponse;
use vesting_contract_common::PledgeData as ContractPledgeData;
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
@@ -13,25 +13,19 @@ use vesting_contract_common::PledgeData as VestingPledgeData;
)]
#[derive(Serialize, Deserialize, Debug)]
pub struct PledgeData {
pub amount: MajorCurrencyAmount,
pub amount: DecCoin,
pub block_time: u64,
}
impl TryFrom<VestingPledgeData> for PledgeData {
type Error = TypesError;
fn try_from(data: VestingPledgeData) -> Result<Self, Self::Error> {
let amount: MajorCurrencyAmount = data.amount().into();
Ok(Self {
amount,
block_time: data.block_time().seconds(),
})
}
}
impl PledgeData {
pub fn and_then(data: VestingPledgeData) -> Option<Self> {
data.try_into().ok()
pub fn from_vesting_contract(
pledge: ContractPledgeData,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
Ok(PledgeData {
amount: reg.attempt_convert_to_display_dec_coin(pledge.amount.into())?,
block_time: pledge.block_time.seconds(),
})
}
}
@@ -42,20 +36,20 @@ impl PledgeData {
)]
#[derive(Serialize, Deserialize, Debug)]
pub struct OriginalVestingResponse {
amount: MajorCurrencyAmount,
amount: DecCoin,
number_of_periods: usize,
period_duration: u64,
}
impl TryFrom<VestingOriginalVestingResponse> for OriginalVestingResponse {
type Error = TypesError;
fn try_from(data: VestingOriginalVestingResponse) -> Result<Self, Self::Error> {
let amount = data.amount().into();
Ok(Self {
amount,
number_of_periods: data.number_of_periods(),
period_duration: data.period_duration(),
impl OriginalVestingResponse {
pub fn from_vesting_contract(
res: ContractOriginalVestingResponse,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
Ok(OriginalVestingResponse {
amount: reg.attempt_convert_to_display_dec_coin(res.amount.into())?,
number_of_periods: res.number_of_periods,
period_duration: res.period_duration,
})
}
}
@@ -71,24 +65,20 @@ pub struct VestingAccountInfo {
staking_address: Option<String>,
start_time: u64,
periods: Vec<VestingPeriod>,
amount: MajorCurrencyAmount,
amount: DecCoin,
}
impl TryFrom<VestingAccount> for VestingAccountInfo {
type Error = TypesError;
fn try_from(account: VestingAccount) -> Result<Self, Self::Error> {
let mut periods = Vec::new();
for period in account.periods() {
periods.push(period.into());
}
let amount: MajorCurrencyAmount = account.coin().into();
Ok(Self {
impl VestingAccountInfo {
pub fn from_vesting_contract(
account: ContractVestingAccount,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
Ok(VestingAccountInfo {
owner_address: account.owner_address().to_string(),
staking_address: account.staking_address().map(|a| a.to_string()),
start_time: account.start_time().seconds(),
periods,
amount,
periods: account.periods().into_iter().map(Into::into).collect(),
amount: reg.attempt_convert_to_display_dec_coin(account.coin.into())?,
})
}
}
@@ -104,8 +94,8 @@ pub struct VestingPeriod {
period_seconds: u64,
}
impl From<VestingVestingPeriod> for VestingPeriod {
fn from(period: VestingVestingPeriod) -> Self {
impl From<ContractVestingPeriod> for VestingPeriod {
fn from(period: ContractVestingPeriod) -> Self {
Self {
start_time: period.start_time,
period_seconds: period.period_seconds,
+34 -9
View File
@@ -50,7 +50,6 @@ name = "bandwidth-claim"
version = "1.0.0"
dependencies = [
"bandwidth-claim-contract",
"config",
"cosmwasm-std",
"cosmwasm-storage",
"schemars",
@@ -221,12 +220,11 @@ version = "0.1.0"
dependencies = [
"bandwidth-claim-contract",
"coconut-bandwidth-contract-common",
"config",
"cosmwasm-std",
"cosmwasm-storage",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"multisig-contract-common",
"schemars",
"serde",
"thiserror",
@@ -237,10 +235,32 @@ name = "coconut-bandwidth-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"multisig-contract-common",
"schemars",
"serde",
]
[[package]]
name = "coconut-test"
version = "0.1.0"
dependencies = [
"bandwidth-claim-contract",
"coconut-bandwidth",
"coconut-bandwidth-contract-common",
"cosmwasm-std",
"cosmwasm-storage",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw3-flex-multisig",
"cw4-group",
"multisig-contract-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -592,6 +612,12 @@ dependencies = [
"generic-array 0.14.5",
]
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dyn-clone"
version = "1.0.4"
@@ -1056,7 +1082,6 @@ dependencies = [
"cosmwasm-std",
"fixed",
"log",
"network-defaults",
"schemars",
"serde",
"serde_repr",
@@ -1082,6 +1107,7 @@ name = "network-defaults"
version = "0.1.0"
dependencies = [
"cfg-if",
"dotenv",
"hex-literal",
"once_cell",
"serde",
@@ -1366,18 +1392,18 @@ dependencies = [
[[package]]
name = "regex"
version = "1.5.4"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rfc6979"
@@ -1828,7 +1854,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract"
version = "1.0.1"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"mixnet-contract-common",
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace]
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group"]
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test"]
[profile.release]
opt-level = 3
-2
View File
@@ -9,8 +9,6 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dev-dependencies]
config = { path = "../../common/config"}
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
+4 -3
View File
@@ -62,10 +62,11 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respon
pub mod tests {
use super::*;
use bandwidth_claim_contract::payment::PagedPaymentResponse;
use config::defaults::MIX_DENOM;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
const TEST_MIX_DENOM: &str = "unym";
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
@@ -91,11 +92,11 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, MIX_DENOM.base),
coins(0, TEST_MIX_DENOM),
vec![deps
.as_ref()
.querier
.query_balance(env.contract.address, MIX_DENOM.base)
.query_balance(env.contract.address, TEST_MIX_DENOM)
.unwrap()]
);
}
+1 -4
View File
@@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
config = { path = "../../common/config"}
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
@@ -21,6 +21,3 @@ cw-controllers = "0.13.4"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
[dev-dependencies]
cw-multi-test = { version = "0.13.2" }
+23 -77
View File
@@ -1,11 +1,14 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
};
use coconut_bandwidth_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::error::ContractError;
use crate::queries::{query_all_spent_credentials_paged, query_spent_credential};
use crate::state::{Config, ADMIN, CONFIG};
use crate::transactions;
@@ -22,12 +25,14 @@ pub fn instantiate(
) -> Result<Response, ContractError> {
let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?;
let pool_addr = deps.api.addr_validate(&msg.pool_addr)?;
let mix_denom = msg.mix_denom;
ADMIN.set(deps.branch(), Some(multisig_addr.clone()))?;
let cfg = Config {
multisig_addr,
pool_addr,
mix_denom,
};
CONFIG.save(deps.storage, &cfg)?;
@@ -44,13 +49,23 @@ pub fn execute(
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::DepositFunds { data } => transactions::deposit_funds(deps, env, info, data),
ExecuteMsg::SpendCredential { data } => {
transactions::spend_credential(deps, env, info, data)
}
ExecuteMsg::ReleaseFunds { funds } => transactions::release_funds(deps, env, info, funds),
}
}
#[entry_point]
pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult<Binary> {
unimplemented!();
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetAllSpentCredentials { limit, start_after } => to_binary(
&query_all_spent_credentials_paged(deps, start_after, limit)?,
),
QueryMsg::GetSpentCredential {
blinded_serial_number,
} => to_binary(&query_spent_credential(deps, blinded_serial_number)?),
}
}
#[entry_point]
@@ -61,12 +76,10 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respon
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use crate::support::tests::helpers::*;
use coconut_bandwidth_contract_common::deposit::DepositData;
use config::defaults::MIX_DENOM;
use cosmwasm_std::coins;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, Addr};
use cw_multi_test::Executor;
#[test]
fn initialize_contract() {
@@ -75,6 +88,7 @@ mod tests {
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let info = mock_info("creator", &[]);
@@ -83,80 +97,12 @@ mod tests {
// Contract balance should be 0
assert_eq!(
coins(0, MIX_DENOM.base),
coins(0, TEST_MIX_DENOM),
vec![deps
.as_ref()
.querier
.query_balance(env.contract.address, MIX_DENOM.base)
.query_balance(env.contract.address, TEST_MIX_DENOM)
.unwrap()]
);
}
#[test]
fn deposit_and_release() {
let init_funds = coins(10, MIX_DENOM.base);
let deposit_funds = coins(1, MIX_DENOM.base);
let release_funds = coins(2, MIX_DENOM.base);
let mut app = mock_app(&init_funds);
let multisig_addr = String::from(MULTISIG_CONTRACT);
let pool_addr = String::from(POOL_CONTRACT);
let code_id = app.store_code(contract_bandwidth());
let msg = InstantiateMsg {
multisig_addr: multisig_addr.clone(),
pool_addr: pool_addr.clone(),
};
let contract_addr = app
.instantiate_contract(
code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"bandwidth",
None,
)
.unwrap();
let msg = ExecuteMsg::DepositFunds {
data: DepositData::new(
String::from("info"),
String::from("id"),
String::from("enc"),
),
};
app.execute_contract(
Addr::unchecked(OWNER),
contract_addr.clone(),
&msg,
&deposit_funds,
)
.unwrap();
// try to release more then it's in the contract
let msg = ExecuteMsg::ReleaseFunds {
funds: release_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(multisig_addr.clone()),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(ContractError::NotEnoughFunds, err.downcast().unwrap());
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
app.execute_contract(
Addr::unchecked(multisig_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap();
let pool_bal = app.wrap().query_balance(pool_addr, MIX_DENOM.base).unwrap();
assert_eq!(pool_bal, deposit_funds[0]);
}
}
+5 -4
View File
@@ -5,8 +5,6 @@ use cosmwasm_std::StdError;
use cw_controllers::AdminError;
use thiserror::Error;
use config::defaults::MIX_DENOM;
/// Custom errors for contract failure conditions.
///
/// Add any other custom errors you like here.
@@ -22,12 +20,15 @@ pub enum ContractError {
#[error("No coin was sent for voucher")]
NoCoin,
#[error("Wrong coin denomination, you must send {}", MIX_DENOM.base)]
WrongDenom,
#[error("Wrong coin denomination, you must send {mix_denom}")]
WrongDenom { mix_denom: String },
#[error("There aren't enough funds in the contract")]
NotEnoughFunds,
#[error("Credential already spent or in process of spending")]
DuplicateBlindedSerialNumber,
#[error("{0}")]
Admin(#[from] AdminError),
}
+3 -1
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
pub mod contract;
mod error;
pub mod error;
mod queries;
mod state;
mod storage;
mod support;
mod transactions;
+178
View File
@@ -0,0 +1,178 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{
PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse,
};
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT};
pub(crate) fn query_all_spent_credentials_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedSpendCredentialResponse> {
let limit = limit
.unwrap_or(SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT)
.min(SPEND_CREDENTIAL_PAGE_MAX_LIMIT) as usize;
let start = start_after.as_deref().map(Bound::exclusive);
let nodes = storage::spent_credentials()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<SpendCredential>>>()?;
let start_next_after = nodes
.last()
.map(|spend_credential| spend_credential.blinded_serial_number().to_string());
Ok(PagedSpendCredentialResponse::new(
nodes,
limit,
start_next_after,
))
}
pub(crate) fn query_spent_credential(
deps: Deps<'_>,
blinded_serial_number: String,
) -> StdResult<SpendCredentialResponse> {
let spend_credential =
storage::spent_credentials().may_load(deps.storage, &blinded_serial_number)?;
Ok(SpendCredentialResponse::new(spend_credential))
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::support::tests::fixtures::spend_credential_data_fixture;
use crate::support::tests::helpers::init_contract;
use crate::transactions::spend_credential;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn spent_credentials_empty_on_init() {
let deps = init_contract();
let response =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.spend_credentials.len());
}
#[test]
fn spent_credentials_paged_retrieval_obeys_limits() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let limit = 2;
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.spend_credentials.len() as u32);
}
#[test]
fn spent_credentials_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
// query without explicitly setting a limit
let page1 = query_all_spent_credentials_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(
SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT,
page1.spend_credentials.len() as u32
);
}
#[test]
fn spent_credentials_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(crazy_limit))
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.spend_credentials.len() as u32);
}
#[test]
fn spent_credentials_pagination_works() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let data = spend_credential_data_fixture("blinded_serial_number1");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
let per_page = 2;
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.spend_credentials.len());
// save another
let data = spend_credential_data_fixture("blinded_serial_number2");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
// page1 should have 2 results on it
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.spend_credentials.len());
let data = spend_credential_data_fixture("blinded_serial_number3");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
// page1 still has 2 results
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.spend_credentials.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_spent_credentials_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.spend_credentials.len());
let data = spend_credential_data_fixture("blinded_serial_number4");
spend_credential(deps.as_mut(), env, info, data).unwrap();
let page2 = query_all_spent_credentials_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.spend_credentials.len());
}
}
+1
View File
@@ -13,6 +13,7 @@ pub const ADMIN: Admin = Admin::new("admin");
pub struct Config {
pub multisig_addr: Addr,
pub pool_addr: Addr,
pub mix_denom: String,
}
pub const CONFIG: Item<Config> = Item::new("config");
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::SpendCredential;
use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex};
// storage prefixes
const SPEND_CREDENTIAL_PK_NAMESPACE: &str = "sc";
const SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE: &str = "scn";
// paged retrieval limits for all queries and transactions
pub(crate) const SPEND_CREDENTIAL_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) struct SpendCredentialIndex<'a> {
pub(crate) blinded_serial_number: UniqueIndex<'a, String, SpendCredential>,
}
// 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<SpendCredential> for SpendCredentialIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<SpendCredential>> + '_> {
let v: Vec<&dyn Index<SpendCredential>> = vec![&self.blinded_serial_number];
Box::new(v.into_iter())
}
}
// gateways() is the storage access function.
pub(crate) fn spent_credentials<'a>(
) -> IndexedMap<'a, &'a str, SpendCredential, SpendCredentialIndex<'a>> {
let indexes = SpendCredentialIndex {
blinded_serial_number: UniqueIndex::new(
|d| d.blinded_serial_number().to_string(),
SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE,
),
};
IndexedMap::new(SPEND_CREDENTIAL_PK_NAMESPACE, indexes)
}
// currently not used outside tests
#[cfg(test)]
mod tests {
use super::super::storage;
use crate::storage::SpendCredential;
use crate::support::tests::fixtures;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
#[test]
fn spend_credential_single_read_retrieval() {
let mut storage = MockStorage::new();
let blind_serial_number1 = "number1";
let blind_serial_number2 = "number2";
let spend1 = fixtures::spend_credential_fixture(blind_serial_number1);
let spend2 = fixtures::spend_credential_fixture(blind_serial_number2);
storage::spent_credentials()
.save(&mut storage, blind_serial_number1, &spend1)
.unwrap();
storage::spent_credentials()
.save(&mut storage, blind_serial_number2, &spend2)
.unwrap();
let res1 = storage::spent_credentials()
.load(&storage, blind_serial_number1)
.unwrap();
let res2 = storage::spent_credentials()
.load(&storage, blind_serial_number2)
.unwrap();
assert_eq!(spend1, res1);
assert_eq!(spend2, res2);
}
#[test]
fn mark_as_spent_credential() {
let mut mock_storage = MockStorage::new();
let funds = Coin::new(100, TEST_MIX_DENOM);
let blind_serial_number = "blind_serial_number";
let gateway_cosmos_address: Addr = Addr::unchecked("gateway_cosmos_address");
let res = storage::spent_credentials()
.may_load(&mock_storage, blind_serial_number)
.unwrap();
assert!(res.is_none());
let mut spend_credential = SpendCredential::new(
funds.clone(),
blind_serial_number.to_string(),
gateway_cosmos_address.clone(),
);
spend_credential.mark_as_spent();
storage::spent_credentials()
.save(&mut mock_storage, blind_serial_number, &spend_credential)
.unwrap();
let ret = storage::spent_credentials()
.load(&mock_storage, blind_serial_number)
.unwrap();
assert_eq!(ret, spend_credential);
assert_eq!(ret.status(), SpendCredentialStatus::Spent);
}
}
@@ -1,3 +1,5 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod tests;
@@ -1,45 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod helpers {
pub const OWNER: &str = "admin0001";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
use crate::contract::instantiate;
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Addr, Coin, Empty, MemoryStorage, OwnedDeps};
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
pub fn mock_app(init_funds: &[Coin]) -> App {
AppBuilder::new().build(|router, _, storage| {
router
.bank
.init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec())
.unwrap();
})
}
pub fn contract_bandwidth() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
crate::contract::execute,
crate::contract::instantiate,
crate::contract::query,
);
Box::new(contract)
}
}
@@ -0,0 +1,23 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{SpendCredential, SpendCredentialData};
use cosmwasm_std::{Addr, Coin};
pub const TEST_MIX_DENOM: &str = "unym";
pub fn spend_credential_fixture(blinded_serial_number: &str) -> SpendCredential {
SpendCredential::new(
Coin::new(100, TEST_MIX_DENOM),
blinded_serial_number.to_string(),
Addr::unchecked("gateway_owner_addr"),
)
}
pub fn spend_credential_data_fixture(blinded_serial_number: &str) -> SpendCredentialData {
SpendCredentialData::new(
Coin::new(100, TEST_MIX_DENOM),
blinded_serial_number.to_string(),
"gateway_owner_addr".to_string(),
)
}
@@ -0,0 +1,25 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
use crate::contract::instantiate;
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
use super::fixtures::TEST_MIX_DENOM;
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
@@ -0,0 +1,5 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod fixtures;
pub mod helpers;
+161 -16
View File
@@ -1,20 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{
to_cosmos_msg, SpendCredential, SpendCredentialData,
};
use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response};
use crate::error::ContractError;
use crate::state::{ADMIN, CONFIG};
use crate::storage;
use coconut_bandwidth_contract_common::deposit::DepositData;
use coconut_bandwidth_contract_common::events::{
DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO,
DEPOSIT_VALUE,
};
use config::defaults::MIX_DENOM;
pub(crate) fn deposit_funds(
_deps: DepsMut<'_>,
deps: DepsMut<'_>,
_env: Env,
info: MessageInfo,
data: DepositData,
@@ -25,8 +28,9 @@ pub(crate) fn deposit_funds(
if info.funds.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
if info.funds[0].denom != MIX_DENOM.base {
return Err(ContractError::WrongDenom);
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if info.funds[0].denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
let voucher_value = info.funds.last().unwrap();
@@ -39,18 +43,55 @@ pub(crate) fn deposit_funds(
Ok(Response::new().add_event(event))
}
pub(crate) fn spend_credential(
deps: DepsMut<'_>,
env: Env,
_info: MessageInfo,
data: SpendCredentialData,
) -> Result<Response, ContractError> {
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if data.funds().denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
if storage::spent_credentials().has(deps.storage, data.blinded_serial_number()) {
return Err(ContractError::DuplicateBlindedSerialNumber);
}
let cfg = CONFIG.load(deps.storage)?;
let gateway_cosmos_address = deps.api.addr_validate(data.gateway_cosmos_address())?;
storage::spent_credentials().save(
deps.storage,
data.blinded_serial_number(),
&SpendCredential::new(
data.funds().to_owned(),
data.blinded_serial_number().to_owned(),
gateway_cosmos_address,
),
)?;
let msg = to_cosmos_msg(
data.funds().clone(),
data.blinded_serial_number().to_string(),
env.contract.address.into_string(),
cfg.multisig_addr.into_string(),
)?;
Ok(Response::new().add_message(msg))
}
pub(crate) fn release_funds(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
funds: Coin,
) -> Result<Response, ContractError> {
if funds.denom != MIX_DENOM.base {
return Err(ContractError::WrongDenom);
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if funds.denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
let current_balance = deps
.querier
.query_balance(env.contract.address, MIX_DENOM.base)?;
.query_balance(env.contract.address, mix_denom)?;
if funds.amount > current_balance.amount {
return Err(ContractError::NotEnoughFunds);
}
@@ -70,11 +111,13 @@ pub(crate) fn release_funds(
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::helpers;
use crate::support::tests::helpers::{MULTISIG_CONTRACT, POOL_CONTRACT};
use crate::support::tests::fixtures::spend_credential_data_fixture;
use crate::support::tests::helpers::{self, MULTISIG_CONTRACT, POOL_CONTRACT};
use coconut_bandwidth_contract_common::msg::ExecuteMsg;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{Coin, CosmosMsg};
use cosmwasm_std::{from_binary, Coin, CosmosMsg, WasmMsg};
use cw_controllers::AdminError;
use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
#[test]
fn invalid_deposit() {
@@ -92,7 +135,7 @@ mod tests {
Err(ContractError::NoCoin)
);
let coin = Coin::new(1000000, MIX_DENOM.base);
let coin = Coin::new(1000000, crate::support::tests::fixtures::TEST_MIX_DENOM);
let second_coin = Coin::new(1000000, "some_denom");
let info = mock_info("requester", &[coin, second_coin.clone()]);
@@ -104,7 +147,9 @@ mod tests {
let info = mock_info("requester", &[second_coin]);
assert_eq!(
deposit_funds(deps.as_mut(), env, info, data),
Err(ContractError::WrongDenom)
Err(ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
})
);
}
@@ -122,7 +167,10 @@ mod tests {
verification_key.clone(),
encryption_key.clone(),
);
let coin = Coin::new(deposit_value, MIX_DENOM.base);
let coin = Coin::new(
deposit_value,
crate::support::tests::fixtures::TEST_MIX_DENOM,
);
let info = mock_info("requester", &[coin]);
let tx = deposit_funds(deps.as_mut(), env.clone(), info, data).unwrap();
@@ -171,7 +219,7 @@ mod tests {
let mut deps = helpers::init_contract();
let env = mock_env();
let invalid_admin = "invalid admin";
let funds = Coin::new(1, MIX_DENOM.base);
let funds = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
let err = release_funds(
deps.as_mut(),
@@ -180,7 +228,12 @@ mod tests {
Coin::new(1, "invalid denom"),
)
.unwrap_err();
assert_eq!(err, ContractError::WrongDenom);
assert_eq!(
err,
ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
}
);
let err = release_funds(
deps.as_mut(),
@@ -207,7 +260,7 @@ mod tests {
fn valid_release() {
let mut deps = helpers::init_contract();
let env = mock_env();
let coin = Coin::new(1, MIX_DENOM.base);
let coin = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
deps.querier
.update_balance(env.contract.address.clone(), vec![coin.clone()]);
@@ -226,4 +279,96 @@ mod tests {
})
);
}
#[test]
fn valid_spend() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let data = spend_credential_data_fixture("blinded_serial_number");
let res = spend_credential(deps.as_mut(), env.clone(), info, data.clone()).unwrap();
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &res.messages[0].msg
{
assert_eq!(contract_addr, MULTISIG_CONTRACT);
assert!(funds.is_empty());
let multisig_msg: MultisigExecuteMsg = from_binary(&msg).unwrap();
if let MultisigExecuteMsg::Propose {
title: _,
description,
msgs,
latest,
} = multisig_msg
{
assert_eq!(description, data.blinded_serial_number().to_string());
assert!(latest.is_none());
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msgs[0]
{
assert_eq!(*contract_addr, env.contract.address.into_string());
assert!(funds.is_empty());
let release_funds_req: ExecuteMsg = from_binary(&msg).unwrap();
if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req {
assert_eq!(funds, *data.funds());
} else {
panic!("Could not extract release funds message from proposal");
}
}
} else {
panic!("Could not extract proposal from binary blob");
}
} else {
panic!("Wasm execute message not found");
}
}
#[test]
fn invalid_spend_attempts() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let invalid_data = SpendCredentialData::new(
Coin::new(1, "invalid_denom".to_string()),
String::new(),
String::new(),
);
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
assert_eq!(
ret.unwrap_err(),
ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
}
);
let invalid_data = SpendCredentialData::new(
Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM),
String::new(),
"Blinded Serial Number".to_string(),
);
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
assert_eq!(
ret.unwrap_err().to_string(),
"Generic error: Invalid input: address not normalized".to_string()
);
let invalid_data = spend_credential_data_fixture("blined_serial_number");
spend_credential(
deps.as_mut(),
env.clone(),
info.clone(),
invalid_data.clone(),
)
.unwrap();
let ret = spend_credential(deps.as_mut(), env, info, invalid_data);
assert_eq!(
ret.unwrap_err(),
ContractError::DuplicateBlindedSerialNumber
);
}
}
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "coconut-test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
cw-storage-plus = "0.13.4"
cw-controllers = "0.13.4"
cw-utils = "0.13.4"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
coconut-bandwidth = { path = "../coconut-bandwidth" }
cw-multi-test = { version = "0.13.2" }
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" }
cw4-group = { path = "../multisig/cw4-group" }
[[test]]
name = "coconut-test"
path = "src/tests.rs"
@@ -0,0 +1,101 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::*;
use coconut_bandwidth::error::ContractError;
use coconut_bandwidth_contract_common::{
deposit::DepositData,
msg::{ExecuteMsg, InstantiateMsg},
};
use cosmwasm_std::{coins, Addr};
use cw_controllers::AdminError;
use cw_multi_test::Executor;
const TEST_MIX_DENOM: &str = "unym";
#[test]
fn deposit_and_release() {
let init_funds = coins(10, TEST_MIX_DENOM);
let deposit_funds = coins(1, TEST_MIX_DENOM);
let release_funds = coins(2, TEST_MIX_DENOM);
let mut app = mock_app(&init_funds);
let multisig_addr = String::from(MULTISIG_CONTRACT);
let pool_addr = String::from(POOL_CONTRACT);
let random_addr = String::from(RANDOM_ADDRESS);
let code_id = app.store_code(contract_bandwidth());
let msg = InstantiateMsg {
multisig_addr: multisig_addr.clone(),
pool_addr: pool_addr.clone(),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let contract_addr = app
.instantiate_contract(
code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"bandwidth",
None,
)
.unwrap();
let msg = ExecuteMsg::DepositFunds {
data: DepositData::new(
String::from("info"),
String::from("id"),
String::from("enc"),
),
};
app.execute_contract(
Addr::unchecked(OWNER),
contract_addr.clone(),
&msg,
&deposit_funds,
)
.unwrap();
// try to release more then it's in the contract
let msg = ExecuteMsg::ReleaseFunds {
funds: release_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(multisig_addr.clone()),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(ContractError::NotEnoughFunds, err.downcast().unwrap());
// try to call release from non-admin
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(random_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(
ContractError::Admin(AdminError::NotAdmin {}),
err.downcast().unwrap()
);
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
app.execute_contract(
Addr::unchecked(multisig_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap();
let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap();
assert_eq!(pool_bal, deposit_funds[0]);
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
use cw3_flex_multisig::{state::CONFIG, ContractError};
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub const OWNER: &str = "admin0001";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
pub const RANDOM_ADDRESS: &str = "random address";
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub coconut_bandwidth_address: String,
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
let mut cfg = CONFIG.load(deps.storage)?;
cfg.coconut_bandwidth_addr = deps.api.addr_validate(&msg.coconut_bandwidth_address)?;
CONFIG.save(deps.storage, &cfg)?;
Ok(Default::default())
}
pub fn mock_app(init_funds: &[Coin]) -> App {
AppBuilder::new().build(|router, _, storage| {
router
.bank
.init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec())
.unwrap();
})
}
pub fn contract_bandwidth() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
coconut_bandwidth::contract::execute,
coconut_bandwidth::contract::instantiate,
coconut_bandwidth::contract::query,
);
Box::new(contract)
}
pub fn contract_multisig() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
cw3_flex_multisig::contract::execute,
cw3_flex_multisig::contract::instantiate,
cw3_flex_multisig::contract::query,
)
.with_migrate(migrate);
Box::new(contract)
}
pub fn contract_group() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
cw4_group::contract::execute,
cw4_group::contract::instantiate,
cw4_group::contract::query,
);
Box::new(contract)
}
@@ -0,0 +1,160 @@
use crate::helpers::*;
use coconut_bandwidth::error::ContractError;
use coconut_bandwidth_contract_common::{
msg::{
ExecuteMsg as CoconutBandwidthExecuteMsg, InstantiateMsg as CoconutBandwidthInstantiateMsg,
},
spend_credential::SpendCredentialData,
};
use cosmwasm_std::{coins, Addr, Coin, Decimal};
use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg;
use cw_multi_test::Executor;
use cw_utils::{Duration, Threshold};
use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg;
pub const TEST_COIN_DENOM: &str = "unym";
pub const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
#[test]
fn spend_credential_creates_proposal() {
let init_funds = coins(10, TEST_COIN_DENOM);
let mut app = mock_app(&init_funds);
let pool_addr = String::from(POOL_CONTRACT);
let group_code_id = app.store_code(contract_group());
let msg = GroupInstantiateMsg {
admin: Some(OWNER.to_string()),
members: vec![],
};
let group_contract_addr = app
.instantiate_contract(
group_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"group",
None,
)
.unwrap();
let multisig_code_id = app.store_code(contract_multisig());
let msg = MultisigInstantiateMsg {
group_addr: group_contract_addr.into_string(),
threshold: Threshold::AbsolutePercentage {
percentage: Decimal::from_ratio(2u128, 3u128),
},
max_voting_period: Duration::Height(1000),
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
};
let multisig_contract_addr = app
.instantiate_contract(
multisig_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"multisig",
Some(OWNER.to_string()),
)
.unwrap();
let coconut_bandwidth_code_id = app.store_code(contract_bandwidth());
let msg = CoconutBandwidthInstantiateMsg {
multisig_addr: multisig_contract_addr.to_string(),
pool_addr,
mix_denom: TEST_COIN_DENOM.to_string(),
};
let coconut_bandwidth_contract_addr = app
.instantiate_contract(
coconut_bandwidth_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"coconut bandwidth",
None,
)
.unwrap();
let msg = MigrateMsg {
coconut_bandwidth_address: coconut_bandwidth_contract_addr.to_string(),
};
app.migrate_contract(
Addr::unchecked(OWNER),
multisig_contract_addr,
&msg,
multisig_code_id,
)
.unwrap();
let msg = CoconutBandwidthExecuteMsg::SpendCredential {
data: SpendCredentialData::new(
Coin::new(1, TEST_COIN_DENOM),
String::from("blinded_serial_number"),
String::from("gateway_cosmos_address"),
),
};
let res = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap();
let proposal_id = res
.events
.into_iter()
.find(|e| &e.ty == "wasm")
.unwrap()
.attributes
.into_iter()
.find(|attr| &attr.key == "proposal_id")
.unwrap()
.value
.parse::<u64>()
.unwrap();
assert_eq!(1, proposal_id);
// Trying with the same blinded serial number will detect the double spend attempt
let err = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap_err();
assert_eq!(
ContractError::DuplicateBlindedSerialNumber,
err.downcast().unwrap()
);
let msg = CoconutBandwidthExecuteMsg::SpendCredential {
data: SpendCredentialData::new(
Coin::new(1, TEST_COIN_DENOM),
String::from("blinded_serial_number2"),
String::from("gateway_cosmos_address"),
),
};
let res = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap();
let proposal_id = res
.events
.into_iter()
.find(|e| &e.ty == "wasm")
.unwrap()
.attributes
.into_iter()
.find(|attr| &attr.key == "proposal_id")
.unwrap()
.value
.parse::<u64>()
.unwrap();
assert_eq!(2, proposal_id);
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod deposit_and_release;
mod helpers;
mod spend_credential_creates_proposal;
+87 -12
View File
@@ -27,16 +27,18 @@ use crate::mixnodes::bonding_queries::{
query_checkpoints_for_mixnode, query_mixnode_at_height, query_mixnodes_paged,
};
use crate::mixnodes::layer_queries::query_layer_distribution;
use crate::mixnodes::transactions::_try_remove_mixnode;
use crate::queued_migrations::migrate_config_from_env;
use crate::rewards::queries::{
query_circulating_supply, query_reward_pool, query_rewarding_status, query_staking_supply,
};
use crate::rewards::storage as rewards_storage;
use cosmwasm_std::{
entry_point, to_binary, Addr, Api, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
Uint128,
Storage, Uint128,
};
use mixnet_contract_common::{
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, NodeToRemove, QueryMsg,
};
use time::OffsetDateTime;
@@ -61,9 +63,14 @@ pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
}
fn default_initial_state(owner: Addr, rewarding_validator_address: Addr) -> ContractState {
fn default_initial_state(
owner: Addr,
mix_denom: String,
rewarding_validator_address: Addr,
) -> ContractState {
ContractState {
owner,
mix_denom,
rewarding_validator_address,
params: ContractStateParams {
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
@@ -88,7 +95,7 @@ pub fn instantiate(
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
let state = default_initial_state(info.sender, rewarding_validator_address);
let state = default_initial_state(info.sender, msg.mixnet_denom, rewarding_validator_address);
init_epoch(deps.storage, env)?;
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
@@ -107,6 +114,19 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::CompoundReward {
operator,
delegator,
mix_identity,
proxy,
} => crate::rewards::transactions::try_compound_reward(
deps,
env,
operator,
delegator,
mix_identity,
proxy,
),
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
try_update_rewarding_validator_address(deps, info, address)
}
@@ -122,7 +142,7 @@ pub fn execute(
owner_signature,
),
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(env, deps, info)
crate::mixnodes::transactions::try_remove_mixnode(&env, deps.storage, deps.api, info)
}
ExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
@@ -216,7 +236,13 @@ pub fn execute(
owner_signature,
),
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(env, deps, info, owner)
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(
&env,
deps.storage,
deps.api,
info,
owner,
)
}
ExecuteMsg::BondGatewayOnBehalf {
gateway,
@@ -275,7 +301,7 @@ pub fn execute(
)
}
ExecuteMsg::ReconcileDelegations {} => {
crate::delegations::transactions::try_reconcile_all_delegation_events(deps, info)
crate::delegations::transactions::try_reconcile_all_delegation_events(deps)
}
ExecuteMsg::CheckpointMixnodes {} => {
crate::mixnodes::transactions::try_checkpoint_mixnodes(
@@ -316,6 +342,9 @@ pub fn execute(
#[entry_point]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetBlacklistedNodes {} => to_binary(
&crate::mixnodes::bonding_queries::get_blacklisted_nodes(deps),
),
QueryMsg::GetRewardingValidatorAddress {} => {
to_binary(&query_rewarding_validator_address(deps)?)
}
@@ -443,16 +472,61 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
Ok(query_res?)
}
fn blacklist_malicious_node(storage: &mut dyn Storage, owner: &Addr) -> Result<(), ContractError> {
let mixnode_bond = match crate::mixnodes::storage::mixnodes()
.idx
.owner
.item(storage, owner.clone())?
{
Some(record) => record.1,
None => {
return Err(ContractError::NoAssociatedMixNodeBond {
owner: owner.to_owned(),
})
}
};
crate::mixnodes::storage::MIXNODES_BOND_BLACKLIST.save(storage, mixnode_bond.identity(), &0)?;
Ok(())
}
// Removes nodes we've deemed malicious, returns the pledge to the owners, but does not send any rewards
fn remove_malicious_node(
storage: &mut dyn Storage,
api: &dyn Api,
env: &Env,
node: &NodeToRemove,
) -> Result<Response, ContractError> {
let proxy = node.proxy().map(|p| {
api.addr_validate(p)
.unwrap_or_else(|_| panic!("Invalid address: {}", p))
});
let owner_addr = api.addr_validate(node.owner())?;
blacklist_malicious_node(storage, &owner_addr)?;
_try_remove_mixnode(env, storage, api, node.owner(), proxy, false)
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
migrate_config_from_env(deps.storage, &msg)?;
let mut response = Response::new();
for node in msg.nodes_to_remove().iter() {
let mut sub_response = remove_malicious_node(deps.storage, deps.api, &env, node)
.unwrap_or_else(|_| panic!("Could not remove node: {:?}", node));
response.messages.append(&mut sub_response.messages);
response.attributes.append(&mut sub_response.attributes);
response.events.append(&mut sub_response.events);
}
Ok(response)
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::support::tests;
use config::defaults::{DEFAULT_NETWORK, MIX_DENOM};
use crate::support::tests::fixtures::{TEST_COIN_DENOM, TEST_REWARDING_VALIDATOR_ADDRESS};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
use mixnet_contract_common::PagedMixnodeResponse;
@@ -462,7 +536,8 @@ pub mod tests {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {
rewarding_validator_address: DEFAULT_NETWORK.rewarding_validator_address().to_string(),
rewarding_validator_address: TEST_REWARDING_VALIDATOR_ADDRESS.to_string(),
mixnet_denom: TEST_COIN_DENOM.to_string(),
};
let info = mock_info("creator", &[]);
@@ -484,7 +559,7 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, MIX_DENOM.base),
coins(0, TEST_COIN_DENOM),
tests::queries::query_contract_balance(env.contract.address, deps)
);
}
+4 -5
View File
@@ -199,8 +199,7 @@ pub(crate) fn query_mixnode_delegations_paged(
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::support::tests::test_helpers;
use config::defaults::MIX_DENOM;
use crate::support::tests::{fixtures::TEST_COIN_DENOM, test_helpers};
use cosmwasm_std::{coin, Addr, Storage};
use rand::Rng;
@@ -385,7 +384,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner.clone(),
node_identity.clone(),
coin(1234, MIX_DENOM.base),
coin(1234, TEST_COIN_DENOM),
1234,
None,
);
@@ -433,7 +432,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner2,
node_identity1.clone(),
coin(1234, MIX_DENOM.base),
coin(1234, TEST_COIN_DENOM),
1234,
None,
);
@@ -460,7 +459,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner1.clone(),
node_identity2,
coin(1234, MIX_DENOM.base),
coin(1234, TEST_COIN_DENOM),
1234,
None,
);

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